diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml new file mode 100644 index 0000000..4ca63d2 --- /dev/null +++ b/.bazelci/presubmit.yml @@ -0,0 +1,56 @@ +--- +buildifier: latest + +matrix: + platform: + - ubuntu2004 + - macos + # TODO: Enable Windows once the private API it allowlists rules_shell. + # - windows + +tasks: + test_module_bzlmod: + name: "Test module (Bazel 7.3.2, Bzlmod)" + working_directory: "tests/bcr" + bazel: 7.3.2 + platform: ${{ platform }} + build_flags: + - "--enable_bzlmod" + - "--noenable_workspace" + build_targets: + - "//..." + test_flags: + - "--enable_bzlmod" + - "--noenable_workspace" + test_targets: + - "//..." + test_module_workspace: + name: "Test module (Bazel 7.3.2, WORKSPACE)" + working_directory: "tests/bcr" + bazel: 7.3.2 + platform: ${{ platform }} + build_flags: + - "--noenable_bzlmod" + - "--enable_workspace" + build_targets: + - "//..." + test_flags: + - "--noenable_bzlmod" + - "--enable_workspace" + test_targets: + - "//..." + test_module_head: + name: "Test module (Bazel@HEAD, Bzlmod)" + working_directory: "tests/bcr" + bazel: last_green + platform: ${{ platform }} + build_flags: + - "--enable_bzlmod" + - "--noenable_workspace" + build_targets: + - "//..." + test_flags: + - "--enable_bzlmod" + - "--noenable_workspace" + test_targets: + - "//..." diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000..23758d5 --- /dev/null +++ b/.bazelignore @@ -0,0 +1 @@ +tests/bcr diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json new file mode 100644 index 0000000..7de9c0d --- /dev/null +++ b/.bcr/metadata.template.json @@ -0,0 +1,20 @@ +{ + "homepage": "https://github.com/bazelbuild/rules_shell", + "maintainers": [ + { + "email": "pcloudy@google.com", + "github": "meteorcloudy", + "name": "Yun Peng" + }, + { + "email": "fabian@meumertzhe.im", + "github": "fmeum", + "name": "Fabian Meumertzheim" + } + ], + "repository": [ + "github:bazelbuild/rules_shell" + ], + "versions": [], + "yanked_versions": {} +} diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml new file mode 100644 index 0000000..fd5f3ba --- /dev/null +++ b/.bcr/presubmit.yml @@ -0,0 +1,17 @@ +bcr_test_module: + module_path: tests/bcr + matrix: + platform: + - centos7 + - debian10 + - ubuntu2004 + - macos + - windows + bazel: [6.x, 7.x] + tasks: + run_test_module: + name: Run test module + platform: ${{ platform }} + bazel: ${{ bazel }} + test_targets: + - "//..." diff --git a/.bcr/source.template.json b/.bcr/source.template.json new file mode 100644 index 0000000..acee4b1 --- /dev/null +++ b/.bcr/source.template.json @@ -0,0 +1,5 @@ +{ + "integrity": "**leave this alone**", + "strip_prefix": "{REPO}-{VERSION}", + "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz" +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b5f94b5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,18 @@ +# Cut a release whenever a new tag is pushed to the repo. +# You should use an annotated tag, like `git tag -a v1.2.3` +# and put the release notes into the commit message for the tag. +name: Release + +on: + push: + tags: + - "v*.*.*" + +permissions: + contents: write + +jobs: + release: + uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v6 + with: + release_files: rules_shell-*.tar.gz diff --git a/.github/workflows/release_prep.sh b/.github/workflows/release_prep.sh new file mode 100755 index 0000000..2b1534a --- /dev/null +++ b/.github/workflows/release_prep.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o pipefail + +# Set by GH actions, see +# https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables +TAG=${GITHUB_REF_NAME} +# The prefix is chosen to match what GitHub generates for source archives +# This guarantees that users can easily switch from a released artifact to a source archive +# with minimal differences in their code (e.g. strip_prefix remains the same) +PREFIX="rules_shell-${TAG:1}" +ARCHIVE="rules_shell-$TAG.tar.gz" + +# NB: configuration for 'git archive' is in /.gitattributes +git archive --format=tar --prefix="${PREFIX}/" "${TAG}" | gzip > "$ARCHIVE" +SHA=$(shasum -a 256 "$ARCHIVE" | awk '{print $1}') + +cat << EOF +## Using Bzlmod with Bazel 6 or greater + +1. (Bazel 6 only) Enable with \`common --enable_bzlmod\` in \`.bazelrc\`. +2. Add to your \`MODULE.bazel\` file: + +\`\`\`starlark +bazel_dep(name = "rules_shell", version = "${TAG:1}") +\`\`\` + +## Using WORKSPACE + +Paste this snippet into your \`WORKSPACE.bazel\` file: + +\`\`\`starlark +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "rules_shell", + sha256 = "${SHA}", + strip_prefix = "${PREFIX}", + url = "https://github.com/bazelbuild/rules_shell/releases/download/${TAG}/${ARCHIVE}", +) + +load("@rules_shell//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") +rules_shell_dependencies() +rules_shell_toolchains() +\`\`\` +EOF diff --git a/.gitignore b/.gitignore index 57c461f..0d4fed2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ bazel-* - +MODULE.bazel.lock diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..ab05338 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,11 @@ +module( + name = "rules_shell", + version = "0.0.0", +) + +bazel_dep(name = "platforms", version = "0.0.10") + +sh_configure = use_extension("//shell/private/extensions:sh_configure.bzl", "sh_configure") +use_repo(sh_configure, "local_config_shell") + +register_toolchains("@local_config_shell//:all") diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/shell/BUILD b/shell/BUILD new file mode 100644 index 0000000..2e2f8ca --- /dev/null +++ b/shell/BUILD @@ -0,0 +1,15 @@ +# A runtime toolchain for shell scripts. +# +# Use `sh_toolchain` to register a toolchain for this type. +# +# Every toolchain registered for this type has the following attributes: +# - `path`: The path to the shell interpreter for the target platform. +# +# Other attribute may be present but are considered implementation details of +# Bazel's sh_* rules. +# +# Toolchains registered for this type should have target constraints. +toolchain_type( + name = "toolchain_type", + visibility = ["//visibility:public"], +) diff --git a/shell/private/BUILD b/shell/private/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/shell/private/extensions/BUILD b/shell/private/extensions/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/shell/private/extensions/sh_configure.bzl b/shell/private/extensions/sh_configure.bzl new file mode 100644 index 0000000..71ff021 --- /dev/null +++ b/shell/private/extensions/sh_configure.bzl @@ -0,0 +1,23 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The sh_configure module extension.""" + +load("//shell/private/repositories:sh_config.bzl", "sh_config") + +def _sh_configure_impl(module_ctx): + sh_config(name = "local_config_shell") + return module_ctx.extension_metadata(reproducible = True) + +sh_configure = module_extension(implementation = _sh_configure_impl) diff --git a/shell/private/repositories/BUILD b/shell/private/repositories/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/shell/private/repositories/sh_config.bzl b/shell/private/repositories/sh_config.bzl new file mode 100644 index 0000000..0b8c1c7 --- /dev/null +++ b/shell/private/repositories/sh_config.bzl @@ -0,0 +1,140 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Configure sh_toolchains based on the local machine.""" + +visibility(["//shell", "//shell/private/extensions"]) + +_DEFAULT_SHELL_PATHS = { + "windows": "c:/msys64/usr/bin/bash.exe", + "linux": "/bin/bash", + "osx": "/bin/bash", + "freebsd": "/usr/local/bin/bash", + "openbsd": "/usr/local/bin/bash", +} + +_UNIX_SH_TOOLCHAIN_TEMPLATE = """ +sh_toolchain( + name = "{os}_sh", + path = {sh_path}, +) +""" + +_WINDOWS_SH_TOOLCHAIN_TEMPLATE = """ +sh_toolchain( + name = "{os}_sh", + path = {sh_path}, + launcher = "@bazel_tools//tools/launcher", + launcher_maker = "@bazel_tools//tools/launcher:launcher_maker", +) +""" + +_TOOLCHAIN_TEMPLATE = """ +toolchain( + name = "{os}_sh_toolchain", + toolchain = ":{os}_sh", + toolchain_type = "@rules_shell//shell:toolchain_type", + target_compatible_with = [ + "@platforms//os:{os}", + ], +) +""" + +def _sh_config_impl(repository_ctx): + """sh_config rule implementation. + + Creates sh_toolchains for commonly supported target platforms. + For the target platform matching the local machine, it detects the path of + the shell interpreter instead of using the default path. + + Args: + repository_ctx: the repository rule context object + """ + toolchains = [] + for os, default_shell_path in _DEFAULT_SHELL_PATHS.items(): + is_host = repository_ctx.os.name.startswith(os) + if is_host: + # This toolchain was first added before optional toolchains were + # available, so instead of not registering a toolchain if we + # couldn't find the shell, we register a toolchain with an empty + # path. + sh_path = _detect_local_shell_path(repository_ctx) or "" + else: + sh_path = default_shell_path + + sh_toolchain_template = _WINDOWS_SH_TOOLCHAIN_TEMPLATE if os == "windows" else _UNIX_SH_TOOLCHAIN_TEMPLATE + toolchains.append(sh_toolchain_template.format( + os = os, + sh_path = repr(sh_path), + )) + toolchains.append(_TOOLCHAIN_TEMPLATE.format( + os = os, + )) + + repository_ctx.file("BUILD", """ +load("@rules_shell//shell/toolchains:sh_toolchain.bzl", "sh_toolchain") +""" + "\n".join(toolchains)) + +sh_config = repository_rule( + environ = [ + "WINDIR", + "PATH", + ], + # TODO: Replace this with configure = True and add BAZEL_SH to the + # environ list above for consistency with CC and other repo rules. + # This would make discovery differ from --shell_executable. + local = True, + implementation = _sh_config_impl, +) + +def _detect_local_shell_path(repository_ctx): + if repository_ctx.os.name.startswith("windows"): + return _detect_local_shell_path_windows(repository_ctx) + else: + return _detect_local_shell_path_unix(repository_ctx) + +def _detect_local_shell_path_windows(repository_ctx): + sh_path = repository_ctx.os.environ.get("BAZEL_SH") + if sh_path: + return sh_path.replace("\\", "/") + + sh_path_obj = repository_ctx.which("bash.exe") + if sh_path_obj: + # repository_ctx.which returns a path object, convert that to + # string so we can call string.startswith on it. + sh_path = str(sh_path_obj) + + # When the Windows Subsystem for Linux is installed there's a + # bash.exe under %WINDIR%\system32\bash.exe that launches Ubuntu + # Bash which cannot run native Windows programs so it's not what + # we want. + windir = repository_ctx.os.environ.get("WINDIR") + if not windir or not sh_path.startswith(windir): + return sh_path.replace("\\", "/") + + return None + +def _detect_local_shell_path_unix(repository_ctx): + sh_path = repository_ctx.os.environ.get("BAZEL_SH") + if sh_path: + return sh_path + + sh_path_obj = repository_ctx.which("bash") + if sh_path_obj: + return str(sh_path_obj) + + sh_path_obj = repository_ctx.which("sh") + if sh_path_obj: + return str(sh_path_obj) + + return None diff --git a/shell/private/sh_executable.bzl b/shell/private/sh_executable.bzl new file mode 100644 index 0000000..1306c80 --- /dev/null +++ b/shell/private/sh_executable.bzl @@ -0,0 +1,197 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Common code for sh_binary and sh_test rules.""" + +visibility(["//shell"]) + +_SH_TOOLCHAIN_TYPE = "//shell:toolchain_type" + +def _sh_executable_impl(ctx): + if len(ctx.files.srcs) != 1: + fail("you must specify exactly one file in 'srcs'", attr = "srcs") + + symlink = ctx.actions.declare_file(ctx.label.name) + src = ctx.files.srcs[0] + + ctx.actions.symlink( + output = symlink, + target_file = src, + is_executable = True, + progress_message = "Symlinking %{label}", + ) + + direct_files = [src, symlink] + + # TODO: Consider extracting this logic into a function provided by + # sh_toolchain to allow users to inject launcher creation logic for + # non-Windows platforms. + if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]): + main_executable = _launcher_for_windows(ctx, symlink, src) + direct_files.append(main_executable) + else: + main_executable = symlink + + files = depset(direct = direct_files) + runfiles = ctx.runfiles(transitive_files = files, collect_default = True) + default_info = DefaultInfo( + executable = main_executable, + files = files, + runfiles = runfiles, + ) + + instrumented_files_info = coverage_common.instrumented_files_info( + ctx, + source_attributes = ["srcs"], + dependency_attributes = ["deps", "data"], + ) + + run_environment_info = RunEnvironmentInfo( + environment = { + key: ctx.expand_make_variables( + "env", + ctx.expand_location(value, ctx.attr.data, short_paths = True), + {}, + ) + for key, value in ctx.attr.env.items() + }, + inherited_environment = ctx.attr.env_inherit, + ) + + return [ + default_info, + instrumented_files_info, + run_environment_info, + ] + +_WINDOWS_EXECUTABLE_EXTENSIONS = [ + "exe", + "cmd", + "bat", +] + +def _is_windows_executable(file): + return file.extension in _WINDOWS_EXECUTABLE_EXTENSIONS + +def _create_windows_exe_launcher(ctx, sh_toolchain, primary_output): + if not sh_toolchain.launcher or not sh_toolchain.launcher_maker: + fail("Windows sh_toolchain requires both 'launcher' and 'launcher_maker' to be set") + + bash_launcher = ctx.actions.declare_file(ctx.label.name + ".exe") + + launch_info = ctx.actions.args().use_param_file("%s", use_always = True).set_param_file_format("multiline") + launch_info.add("binary_type=Bash") + launch_info.add(ctx.workspace_name, format = "workspace_name=%s") + launch_info.add("1" if ctx.configuration.runfiles_enabled() else "0", format = "symlink_runfiles_enabled=%s") + launch_info.add(sh_toolchain.path, format = "bash_bin_path=%s") + bash_file_short_path = primary_output.short_path + if bash_file_short_path.startswith("../"): + bash_file_rlocationpath = bash_file_short_path[3:] + else: + bash_file_rlocationpath = ctx.workspace_name + "/" + bash_file_short_path + launch_info.add(bash_file_rlocationpath, format = "bash_file_rlocationpath=%s") + + launcher_artifact = sh_toolchain.launcher + ctx.actions.run( + executable = sh_toolchain.launcher_maker, + inputs = [launcher_artifact], + outputs = [bash_launcher], + arguments = [launcher_artifact.path, launch_info, bash_launcher.path], + use_default_shell_env = True, + toolchain = _SH_TOOLCHAIN_TYPE, + ) + return bash_launcher + +def _launcher_for_windows(ctx, primary_output, main_file): + if _is_windows_executable(main_file): + if main_file.extension == primary_output.extension: + return primary_output + else: + fail("Source file is a Windows executable file, target name extension should match source file extension") + + # bazel_tools should always registers a toolchain for Windows, but it may have an empty path. + sh_toolchain = ctx.toolchains[_SH_TOOLCHAIN_TYPE] + if not sh_toolchain or not sh_toolchain.path: + fail("""No suitable shell toolchain found: +* if you are running Bazel on Windows, set the BAZEL_SH environment variable to the path of bash.exe +* if you are running Bazel on a non-Windows platform but are targeting Windows, register an sh_toolchain for the {} toolchain type +""".format(_SH_TOOLCHAIN_TYPE)) + + return _create_windows_exe_launcher(ctx, sh_toolchain, primary_output) + +def make_sh_executable_rule(extra_attrs = {}, **kwargs): + return rule( + _sh_executable_impl, + doc = """ +

+ The sh_binary rule is used to declare executable shell scripts. + (sh_binary is a misnomer: its outputs aren't necessarily binaries.) This rule ensures + that all dependencies are built, and appear in the runfiles area at execution time. + We recommend that you name your sh_binary() rules after the name of the script minus + the extension (e.g. .sh); the rule name and the file name must be distinct. + sh_binary respects shebangs, so any available interpreter may be used (eg. + #!/bin/zsh) +

+

Example

+

For a simple shell script with no dependencies and some data files: +

+
+sh_binary(
+    name = "foo",
+    srcs = ["foo.sh"],
+    data = glob(["datafiles/*.txt"]),
+)
+
+""", + attrs = { + "srcs": attr.label_list( + allow_files = True, + doc = """ +The list of input files. +

+ This attribute should be used to list shell script source files that belong to + this library. Scripts can load other scripts using the shell's source + or . command. +

+""", + ), + "data": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + ), + "deps": attr.label_list( + allow_rules = ["sh_library"], + doc = """ +The list of "library" targets to be aggregated into this target. +See general comments about deps +at Typical attributes defined by +most build rules. +

+ This attribute should be used to list other sh_library rules that provide + interpreted program source code depended on by the code in srcs. The files + provided by these rules will be present among the runfiles of this target. +

+""", + ), + "env": attr.string_dict(), + "env_inherit": attr.string_list(), + "_windows_constraint": attr.label( + default = "@platforms//os:windows", + ), + } | extra_attrs, + toolchains = [ + config_common.toolchain_type(_SH_TOOLCHAIN_TYPE, mandatory = False), + ], + **kwargs + ) diff --git a/shell/repositories.bzl b/shell/repositories.bzl new file mode 100644 index 0000000..81b1cc0 --- /dev/null +++ b/shell/repositories.bzl @@ -0,0 +1,30 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""WORKSPACE dependency macros for the shell rules.""" + +load("//shell/private/repositories:sh_config.bzl", "sh_config") + +visibility("public") + +# buildifier: disable=unnamed-macro +def rules_shell_dependencies(): + """Instantiates repositories required by rules_shell.""" + pass + +# buildifier: disable=unnamed-macro +def rules_shell_toolchains(): + """Register sh_toolchains for the host and common other platforms.""" + sh_config(name = "local_config_shell") + native.register_toolchains("@local_config_shell//:all") diff --git a/shell/sh_binary.bzl b/shell/sh_binary.bzl new file mode 100644 index 0000000..5e3a422 --- /dev/null +++ b/shell/sh_binary.bzl @@ -0,0 +1,21 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""sh_binary rule definition.""" + +load("//shell/private:sh_executable.bzl", "make_sh_executable_rule") + +visibility("public") + +sh_binary = make_sh_executable_rule(executable = True) diff --git a/shell/sh_library.bzl b/shell/sh_library.bzl new file mode 100644 index 0000000..fd6aef9 --- /dev/null +++ b/shell/sh_library.bzl @@ -0,0 +1,117 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""sh_library rule definition.""" + +visibility("public") + +def _sh_library_impl(ctx): + transitive_files = [] + for target in ctx.attr.srcs: + transitive_files.append(target[DefaultInfo].files) + for target in ctx.attr.deps: + transitive_files.append(target[DefaultInfo].files) + for target in ctx.attr.data: + transitive_files.append(target[DefaultInfo].files) + files = depset(transitive = transitive_files) + + runfiles = ctx.runfiles(transitive_files = files, collect_default = True) + + instrumented_files_info = coverage_common.instrumented_files_info( + ctx, + source_attributes = ["srcs"], + dependency_attributes = ["deps", "data"], + ) + + return [ + DefaultInfo( + files = files, + runfiles = runfiles, + ), + instrumented_files_info, + ] + +sh_library = rule( + _sh_library_impl, + doc = """ +

+ The main use for this rule is to aggregate together a logical + "library" consisting of related scripts—programs in an + interpreted language that does not require compilation or linking, + such as the Bourne shell—and any data those programs need at + run-time. Such "libraries" can then be used from + the data attribute of one or + more sh_binary rules. +

+ +

+ You can use the filegroup rule to aggregate data + files. +

+ +

+ In interpreted programming languages, there's not always a clear + distinction between "code" and "data": after all, the program is + just "data" from the interpreter's point of view. For this reason + this rule has three attributes which are all essentially equivalent: + srcs, deps and data. + The current implementation does not distinguish between the elements of these lists. + All three attributes accept rules, source files and generated files. + It is however good practice to use the attributes for their usual purpose (as with other rules). +

+ +

Examples

+ +
+sh_library(
+    name = "foo",
+    data = [
+        ":foo_service_script",  # an sh_binary with srcs
+        ":deploy_foo",  # another sh_binary with srcs
+    ],
+)
+
+""", + attrs = { + "srcs": attr.label_list( + allow_files = True, + doc = """ +The list of input files. +

+ This attribute should be used to list shell script source files that belong to + this library. Scripts can load other scripts using the shell's source + or . command. +

+""", + ), + "data": attr.label_list( + allow_files = True, + flags = ["SKIP_CONSTRAINTS_OVERRIDE"], + ), + "deps": attr.label_list( + allow_rules = ["sh_library"], + doc = """ +The list of "library" targets to be aggregated into this target. +See general comments about deps +at Typical attributes defined by +most build rules. +

+ This attribute should be used to list other sh_library rules that provide + interpreted program source code depended on by the code in srcs. The files + provided by these rules will be present among the runfiles of this target. +

+""", + ), + }, +) diff --git a/shell/sh_test.bzl b/shell/sh_test.bzl new file mode 100644 index 0000000..ea5b5a9 --- /dev/null +++ b/shell/sh_test.bzl @@ -0,0 +1,38 @@ +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""sh_test rule definition.""" + +load("//shell/private:sh_executable.bzl", "make_sh_executable_rule") + +visibility("public") + +sh_test = make_sh_executable_rule( + test = True, + fragments = ["coverage"], + extra_attrs = { + "_lcov_merger": attr.label( + cfg = "exec", + default = configuration_field(fragment = "coverage", name = "output_generator"), + executable = True, + ), + # Add the script as an attribute in order for sh_test to output code coverage results for + # code covered by CC binaries invocations. + "_collect_cc_coverage": attr.label( + cfg = "exec", + default = "@bazel_tools//tools/test:collect_cc_coverage", + executable = True, + ), + }, +) diff --git a/shell/toolchains/BUILD b/shell/toolchains/BUILD new file mode 100644 index 0000000..e69de29 diff --git a/shell/toolchains/sh_toolchain.bzl b/shell/toolchains/sh_toolchain.bzl new file mode 100644 index 0000000..64d2d3c --- /dev/null +++ b/shell/toolchains/sh_toolchain.bzl @@ -0,0 +1,49 @@ +# Copyright 2018 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Define a toolchain rule for the shell.""" + +visibility("public") + +def _sh_toolchain_impl(ctx): + """sh_toolchain rule implementation.""" + return [ + platform_common.ToolchainInfo( + path = ctx.attr.path, + launcher = ctx.executable.launcher, + launcher_maker = ctx.executable.launcher_maker, + ), + ] + +sh_toolchain = rule( + doc = "A runtime toolchain for shell targets.", + attrs = { + "path": attr.string( + doc = "Absolute path to the shell interpreter.", + mandatory = True, + ), + "launcher": attr.label( + doc = "The generic launcher binary to use to run sh_binary/sh_test targets (only used when targeting Windows).", + cfg = "target", + allow_single_file = True, + executable = True, + ), + "launcher_maker": attr.label( + doc = "The tool to use to create a target-specific launcher from the generic launcher binary (only used when targeting Windows).", + cfg = "exec", + allow_single_file = True, + executable = True, + ), + }, + implementation = _sh_toolchain_impl, +) diff --git a/tests/bcr/BUILD b/tests/bcr/BUILD new file mode 100644 index 0000000..f5f3edd --- /dev/null +++ b/tests/bcr/BUILD @@ -0,0 +1,26 @@ +load("@rules_shell//shell:sh_binary.bzl", "sh_binary") +load("@rules_shell//shell:sh_library.bzl", "sh_library") +load("@rules_shell//shell:sh_test.bzl", "sh_test") + +sh_library( + name = "lib", + srcs = ["lib.sh"], + data = ["greeting.txt"], + deps = ["@bazel_tools//tools/bash/runfiles"], +) + +sh_binary( + name = "bin", + srcs = ["bin.sh"], + deps = [ + ":lib", + "@bazel_tools//tools/bash/runfiles", + ], +) + +sh_test( + name = "test", + srcs = ["test.sh"], + data = [":bin"], + deps = ["@bazel_tools//tools/bash/runfiles"], +) diff --git a/tests/bcr/MODULE.bazel b/tests/bcr/MODULE.bazel new file mode 100644 index 0000000..cf3cfc8 --- /dev/null +++ b/tests/bcr/MODULE.bazel @@ -0,0 +1,7 @@ +module(name = "rules_shell_tests") + +bazel_dep(name = "rules_shell") +local_path_override( + module_name = "rules_shell", + path = "../..", +) diff --git a/tests/bcr/WORKSPACE b/tests/bcr/WORKSPACE new file mode 100644 index 0000000..859c4a7 --- /dev/null +++ b/tests/bcr/WORKSPACE @@ -0,0 +1,12 @@ +workspace(name = "rules_shell_tests") + +local_repository( + name = "rules_shell", + path = "../..", +) + +load("@rules_shell//shell:repositories.bzl", "rules_shell_dependencies", "rules_shell_toolchains") + +rules_shell_dependencies() + +rules_shell_toolchains() diff --git a/tests/bcr/WORKSPACE.bzlmod b/tests/bcr/WORKSPACE.bzlmod new file mode 100644 index 0000000..e69de29 diff --git a/tests/bcr/bin.sh b/tests/bcr/bin.sh new file mode 100755 index 0000000..f76d4a0 --- /dev/null +++ b/tests/bcr/bin.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +# shellcheck disable=SC1090 +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +source "$(rlocation "rules_shell_tests/lib.sh")" + +get_greeting diff --git a/tests/bcr/greeting.txt b/tests/bcr/greeting.txt new file mode 100644 index 0000000..d0cf519 --- /dev/null +++ b/tests/bcr/greeting.txt @@ -0,0 +1 @@ +hello from rules_shell diff --git a/tests/bcr/lib.sh b/tests/bcr/lib.sh new file mode 100755 index 0000000..16fa810 --- /dev/null +++ b/tests/bcr/lib.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +# shellcheck disable=SC1090 +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +function get_greeting() { + greeting_path=$(rlocation "rules_shell_tests/greeting.txt") + cat "${greeting_path}" +} diff --git a/tests/bcr/test.sh b/tests/bcr/test.sh new file mode 100755 index 0000000..ce37244 --- /dev/null +++ b/tests/bcr/test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Copyright 2024 The Bazel Authors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +# shellcheck disable=SC1090 +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +bin_path="$(rlocation "rules_shell_tests/bin.sh")" +if [[ ! -x "${bin_path}" ]]; then + echo "Expected '${bin_path}' to be an executable" + exit 1 +fi + +runfiles_export_envvars + +greeting=$("${bin_path}") +if [[ "${greeting}" != "hello from rules_shell" ]]; then + echo "Expected 'hello from rules_shell', got '${greeting}'" + exit 1 +fi