├── .bcr ├── config.yml ├── source.template.json ├── presubmit.yml └── metadata.template.json ├── bzlmod ├── BUILD.bazel └── extensions.bzl ├── sh ├── docs │ ├── empty.vm │ ├── func.vm │ ├── header.vm │ └── BUILD.bazel ├── posix │ └── BUILD.bazel ├── experimental │ ├── BUILD.bazel │ └── posix_hermetic.bzl ├── private │ ├── BUILD.bazel │ ├── defs.bzl │ ├── get_cpu_value.bzl │ └── posix.bzl ├── BUILD.bazel ├── repositories.bzl ├── import.bzl ├── posix.bzl └── sh.bzl ├── .bazelignore ├── tests ├── tools ├── bzlmod │ ├── BUILD.bazel │ └── extensions.bzl ├── sh_binaries │ ├── empty.exe │ ├── hello_data.txt │ ├── hello_world.sh │ ├── BUILD.bazel │ ├── hello_data.sh │ ├── custom_rule_test.sh │ ├── genrule_test.sh │ └── sh_binaries_test.bzl ├── posix_hermetic │ ├── echo_data.txt │ ├── unrecognized.sh │ ├── false.cc │ ├── true.cc │ ├── BUILD.bazel │ ├── echo.sh │ ├── genrule_path_output_test.sh │ ├── genrule_explicit_output_test.sh │ ├── runfiles_genrule_test.sh │ ├── runfiles_custom_rule_test.sh │ ├── custom_rule_output_test.sh │ └── posix_hermetic_test.bzl ├── WORKSPACE.bzlmod ├── import │ ├── get_cpu_value.bzl │ ├── BUILD.bazel │ ├── invoke_shim_test.sh │ ├── create_shim_test.sh │ └── import_test.bzl ├── .bazelrc ├── BUILD.bazel ├── MODULE.bazel ├── WORKSPACE └── posix_tests.bzl ├── .github ├── CODEOWNERS ├── renovate.json ├── actions │ └── run_bazel_test │ │ ├── action.yaml │ │ └── run_bazel_test.sh ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── settings.yml └── workflows │ ├── release_prep.sh │ ├── release.yaml │ └── workflow.yaml ├── .bazeliskrc ├── BUILD.bazel ├── WORKSPACE.bzlmod ├── .gitignore ├── AUTHORS ├── .bazelci └── presubmit.yml ├── tools ├── bazel.bat └── bazel ├── MODULE.bazel ├── CONTRIBUTORS ├── .mergify.yml ├── .bazelrc ├── WORKSPACE ├── MAINTAINERS.md ├── CONTRIBUTING.md ├── README.md ├── CHANGELOG.md └── LICENSE /.bcr/config.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bzlmod/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sh/docs/empty.vm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.bazelignore: -------------------------------------------------------------------------------- 1 | tests 2 | -------------------------------------------------------------------------------- /tests/tools: -------------------------------------------------------------------------------- 1 | ../tools/ -------------------------------------------------------------------------------- /tests/bzlmod/BUILD.bazel: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/sh_binaries/empty.exe: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @avdv 2 | -------------------------------------------------------------------------------- /.bazeliskrc: -------------------------------------------------------------------------------- 1 | USE_BAZEL_VERSION=7.4.1 2 | -------------------------------------------------------------------------------- /sh/docs/func.vm: -------------------------------------------------------------------------------- 1 | ${funcInfo.docString} 2 | -------------------------------------------------------------------------------- /tests/sh_binaries/hello_data.txt: -------------------------------------------------------------------------------- 1 | File 2 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | exports_files(["README.md"]) 2 | -------------------------------------------------------------------------------- /WORKSPACE.bzlmod: -------------------------------------------------------------------------------- 1 | workspace(name = "rules_sh") 2 | -------------------------------------------------------------------------------- /tests/posix_hermetic/echo_data.txt: -------------------------------------------------------------------------------- 1 | ECHO SUFFIX 2 | -------------------------------------------------------------------------------- /tests/WORKSPACE.bzlmod: -------------------------------------------------------------------------------- 1 | workspace(name = "rules_sh_tests") 2 | -------------------------------------------------------------------------------- /tests/import/get_cpu_value.bzl: -------------------------------------------------------------------------------- 1 | ../../sh/private/get_cpu_value.bzl -------------------------------------------------------------------------------- /tests/posix_hermetic/unrecognized.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "Unrecognized tool" 3 | -------------------------------------------------------------------------------- /tests/.bazelrc: -------------------------------------------------------------------------------- 1 | import %workspace%/../.bazelrc 2 | try-import %workspace%/../.bazelrc.local 3 | -------------------------------------------------------------------------------- /tests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":posix_tests.bzl", "posix_test_suite") 2 | 3 | posix_test_suite() 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/bazel-* 2 | 3 | # These files are specific to the host. 4 | .bazelrc.auth 5 | .bazelrc.local 6 | -------------------------------------------------------------------------------- /tests/import/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":import_test.bzl", "import_test_suite") 2 | 3 | import_test_suite(name = "import_test") 4 | -------------------------------------------------------------------------------- /tests/sh_binaries/hello_world.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ $# -ge 1 ]]; then 3 | exec >"$1" 4 | fi 5 | echo "Hello World" 6 | -------------------------------------------------------------------------------- /tests/posix_hermetic/false.cc: -------------------------------------------------------------------------------- 1 | int main() { 2 | // Intentionally inverted to distinguish from the builtin false command. 3 | return 0; 4 | } 5 | -------------------------------------------------------------------------------- /tests/posix_hermetic/true.cc: -------------------------------------------------------------------------------- 1 | int main() { 2 | // Intentionally inverted to distinguish from the builtin true command. 3 | return 1; 4 | } 5 | -------------------------------------------------------------------------------- /tests/sh_binaries/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":sh_binaries_test.bzl", "sh_binaries_test_suite") 2 | 3 | sh_binaries_test_suite(name = "sh_binaries_test") 4 | -------------------------------------------------------------------------------- /tests/posix_hermetic/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":posix_hermetic_test.bzl", "posix_hermetic_test_suite") 2 | 3 | posix_hermetic_test_suite(name = "posix_hermetic_test") 4 | -------------------------------------------------------------------------------- /.bcr/source.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrity": "", 3 | "strip_prefix": "{REPO}-{VERSION}", 4 | "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{VERSION}.tar.gz" 5 | } 6 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>tweag/renovate-presets:ruleset_base", 4 | "github>tweag/renovate-presets:mergify" 5 | ], 6 | "gitAuthor": "Renovate Bot " 7 | } 8 | -------------------------------------------------------------------------------- /sh/posix/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("//sh:posix.bzl", "sh_posix_make_variables") 2 | 3 | toolchain_type( 4 | name = "toolchain_type", 5 | visibility = ["//visibility:public"], 6 | ) 7 | 8 | sh_posix_make_variables( 9 | name = "make_variables", 10 | visibility = ["//visibility:public"], 11 | ) 12 | -------------------------------------------------------------------------------- /sh/docs/header.vm: -------------------------------------------------------------------------------- 1 | # Shell rules for Bazel 2 | 3 | [![Continuous Integration](https://github.com/tweag/rules_sh/actions/workflows/workflow.yaml/badge.svg?event=schedule)](https://github.com/tweag/rules_sh/actions/workflows/workflow.yaml) 4 | 5 | This project extends Bazel with a toolchain for common shell commands. 6 | -------------------------------------------------------------------------------- /tests/bzlmod/extensions.bzl: -------------------------------------------------------------------------------- 1 | load("//import:import_test.bzl", "import_test_repositories") 2 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") 3 | 4 | def _tests_configure_impl(ctx): 5 | import_test_repositories() 6 | 7 | tests_configure = module_extension(implementation = _tests_configure_impl) 8 | -------------------------------------------------------------------------------- /.bcr/presubmit.yml: -------------------------------------------------------------------------------- 1 | bcr_test_module: 2 | module_path: tests 3 | matrix: 4 | platform: 5 | - debian10 6 | - ubuntu2004 7 | - macos 8 | - macos_arm64 9 | - windows 10 | tasks: 11 | run_test_module: 12 | name: Run test module 13 | platform: ${{ platform }} 14 | test_targets: 15 | - //... 16 | 17 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Bazel authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Tweag I/O Limited 10 | -------------------------------------------------------------------------------- /sh/experimental/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | 3 | bzl_library( 4 | name = "posix_hermetic", 5 | srcs = [ 6 | "posix_hermetic.bzl", 7 | ], 8 | visibility = ["//visibility:public"], 9 | deps = [ 10 | "//sh:bazel_tools", 11 | "@bazel_skylib//lib:paths", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /.bazelci/presubmit.yml: -------------------------------------------------------------------------------- 1 | platforms: 2 | centos7: 3 | build_targets: 4 | - '@rules_sh//...' 5 | debian10: 6 | build_targets: 7 | - '@rules_sh//...' 8 | macos: 9 | build_targets: 10 | - '@rules_sh//...' 11 | ubuntu2004: 12 | build_targets: 13 | - '@rules_sh//...' 14 | windows: 15 | build_targets: 16 | - '@rules_sh//...' 17 | -------------------------------------------------------------------------------- /.github/actions/run_bazel_test/action.yaml: -------------------------------------------------------------------------------- 1 | name: Execute Bazel test 2 | description: Handles platform-specific settings. 3 | 4 | inputs: 5 | working-directory: 6 | type: string 7 | 8 | runs: 9 | using: composite 10 | steps: 11 | - shell: bash 12 | env: 13 | RBT_WORKING_DIR: ${{ inputs.working-directory }} 14 | run: ${GITHUB_ACTION_PATH}/run_bazel_test.sh 15 | -------------------------------------------------------------------------------- /tools/bazel.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: Check if bash is available 4 | where bash >nul 2>nul 5 | if errorlevel 1 ( 6 | echo Bash is not installed or not in PATH. Please install WSL or add bash to PATH. 7 | exit /b 1 8 | ) 9 | 10 | # disable POSIX-to-Windows path conversion for MINGW / MSYS2 11 | set MSYS_NO_PATHCONV=1 12 | 13 | :: Call the Bash wrapper 14 | bash --noprofile --norc -o errexit -o nounset -o pipefail "%~dp0bazel" %* 15 | 16 | -------------------------------------------------------------------------------- /.bcr/metadata.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://github.com/tweag/rules_sh#readme", 3 | "maintainers": [ 4 | { 5 | "email": "claudio.bley@tweag.io", 6 | "github": "avdv", 7 | "name": "Claudio Bley" 8 | }, 9 | { 10 | "email": "andreas.herrmann@tweag.io", 11 | "github": "aherrmann", 12 | "name": "Andreas Herrmann" 13 | } 14 | ], 15 | "versions": [], 16 | "yanked_versions": {} 17 | } 18 | -------------------------------------------------------------------------------- /MODULE.bazel: -------------------------------------------------------------------------------- 1 | module( 2 | name = "rules_sh", 3 | version = "0.5.0", 4 | compatibility_level = 0, 5 | ) 6 | 7 | bazel_dep(name = "bazel_skylib", version = "1.2.1") 8 | bazel_dep(name = "platforms", version = "0.0.8") 9 | 10 | bazel_dep(name = "stardoc", version = "0.6.2", dev_dependency = True, repo_name = "io_bazel_stardoc") 11 | 12 | sh_configure = use_extension("//bzlmod:extensions.bzl", "sh_configure") 13 | use_repo(sh_configure, "local_posix_config", "rules_sh_shim_exe") 14 | 15 | register_toolchains("@local_posix_config//...") 16 | -------------------------------------------------------------------------------- /.github/actions/run_bazel_test/run_bazel_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit -o nounset -o pipefail 4 | 5 | is_windows() { 6 | local os_name 7 | os_name="$( uname )" 8 | # Examples: MINGW64_NT-10.0-17763 9 | [[ "${os_name}" =~ ^MING ]] 10 | } 11 | 12 | working_dir="${RBT_WORKING_DIR:-}" 13 | 14 | if is_windows; then 15 | export BAZEL_SH='C:\msys64\usr\bin\bash.exe' 16 | bzl_pkgs='///...' 17 | else 18 | bzl_pkgs='//...' 19 | fi 20 | 21 | if [[ -n "${working_dir:-}" ]]; then 22 | cd "${working_dir}" 23 | fi 24 | 25 | bazel test "${bzl_pkgs}" 26 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Mathieu Boespflug 13 | Andreas Herrmann 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us fix it. 4 | labels: 'type: bug' 5 | 6 | --- 7 | 8 | **Describe the bug** 9 | A clear and concise description of what the bug is. 10 | 11 | **To Reproduce** 12 | Steps to reproduce the behavior. 13 | 14 | **Expected behavior** 15 | A clear and concise description of what you expected to happen. 16 | 17 | **Environment** 18 | - OS name + version: 19 | - Bazel version: 20 | - Version of the rules: 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | labels: 'type: feature request' 5 | 6 | --- 7 | 8 | **Is your feature request related to a problem? Please describe.** 9 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 10 | 11 | **Describe the solution you'd like** 12 | A clear and concise description of what you want to happen. 13 | 14 | **Describe alternatives you've considered** 15 | A clear and concise description of any alternative solutions or features you've considered. 16 | 17 | **Additional context** 18 | Add any other context or screenshots about the feature request here. 19 | -------------------------------------------------------------------------------- /sh/private/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | load(":defs.bzl", "bool_constant") 3 | 4 | bool_constant( 5 | name = "is_windows", 6 | value = select({ 7 | "@platforms//os:windows": True, 8 | "//conditions:default": False, 9 | }), 10 | visibility = ["//visibility:public"], 11 | ) 12 | 13 | bzl_library( 14 | name = "defs", 15 | srcs = [ 16 | "defs.bzl", 17 | "get_cpu_value.bzl", 18 | ], 19 | deps = [ 20 | "@bazel_skylib//lib:dicts", 21 | ], 22 | visibility = ["//sh:__subpackages__"], 23 | ) 24 | 25 | bzl_library( 26 | name = "posix", 27 | srcs = [ 28 | "posix.bzl", 29 | ], 30 | visibility = ["//sh:__subpackages__"], 31 | ) 32 | -------------------------------------------------------------------------------- /tools/bazel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | bazel_version=$( 6 | "${BAZEL_REAL}" --version | ( 7 | read -r -a v 8 | echo "${v[1]}" 9 | ) 10 | ) 11 | 12 | # enable config per each major version, i.e. bazel6, bazel7, bazel8, ... 13 | config="--config=bazel${bazel_version%%.*}" 14 | 15 | declare -a args=() 16 | 17 | while [ "$#" -gt 0 ]; do 18 | option="$1" 19 | if [[ "$option" = -* ]]; then 20 | args+=( "$option" ) 21 | shift 22 | else 23 | break 24 | fi 25 | done 26 | 27 | if [ "$#" -gt 0 ]; then 28 | command="$1" ; shift 29 | args+=( "$command" "$config" "$@" ) 30 | elif [ "${#args[@]}" -eq 0 ]; then 31 | # no startup options, no command 32 | exec "$BAZEL_REAL" 33 | fi 34 | 35 | exec "$BAZEL_REAL" "${args[@]}" 36 | -------------------------------------------------------------------------------- /tests/MODULE.bazel: -------------------------------------------------------------------------------- 1 | module(name = "rules_sh_tests") 2 | 3 | bazel_dep(name = "rules_sh", version = "0.0.0") 4 | local_path_override( 5 | module_name = "rules_sh", 6 | path = "..", 7 | ) 8 | 9 | bazel_dep(name = "bazel_skylib", version = "1.8.1") 10 | bazel_dep(name = "platforms", version = "1.0.0") 11 | bazel_dep(name = "stardoc", version = "0.6.2", repo_name = "io_bazel_stardoc") 12 | 13 | sh_configure = use_extension("@rules_sh//bzlmod:extensions.bzl", "sh_configure") 14 | use_repo(sh_configure, "local_posix_config", "rules_sh_shim_exe") 15 | 16 | tests_configure = use_extension("//bzlmod:extensions.bzl", "tests_configure") 17 | use_repo( 18 | tests_configure, 19 | "rules_sh_import_test_create_shim_test_source", 20 | "rules_sh_import_test_create_shim_test_shim", 21 | "rules_sh_import_test_invoke_shim_test_powershell", 22 | ) 23 | -------------------------------------------------------------------------------- /tests/posix_hermetic/echo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | SUFFIX="$(cat "$(rlocation rules_sh_tests/posix_hermetic/echo_data.txt)")" 15 | echo "$@" "$SUFFIX" 16 | -------------------------------------------------------------------------------- /tests/sh_binaries/hello_data.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | DATA="$(tr -d '\r\n' <"$(rlocation rules_sh_tests/sh_binaries/hello_data.txt)")" 13 | if [[ $# -ge 1 ]]; then 14 | exec >"$1" 15 | fi 16 | echo "Hello $DATA" 17 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | queue_rules: 2 | - name: default 3 | queue_conditions: 4 | - 'check-success~=Build & Test \(ubuntu-.*\)' 5 | - 'check-success~=Build & Test \(macos-.*\)' 6 | - 'check-success~=Build & Test \(windows-.*\)' 7 | - "#approved-reviews-by>=1" 8 | - "label=merge-queue" 9 | - "base=master" 10 | merge_conditions: 11 | - 'check-success~=Build & Test \(ubuntu-.*\)' 12 | - 'check-success~=Build & Test \(macos-.*\)' 13 | - 'check-success~=Build & Test \(windows-.*\)' 14 | merge_method: merge 15 | 16 | pull_request_rules: 17 | - name: delete head branch after merge 18 | conditions: 19 | - merged 20 | - closed 21 | actions: 22 | delete_head_branch: {} 23 | - name: remove from merge-queue after merge 24 | conditions: 25 | - merged 26 | actions: 27 | label: 28 | remove: 29 | - "merge-queue" 30 | - name: automatic merge 31 | conditions: [] 32 | actions: 33 | queue: 34 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | has_wiki: false 3 | 4 | labels: 5 | - name: "duplicate" 6 | color: cfd3d7 7 | - name: "good first issue" 8 | color: 7057ff 9 | - name: "invalid" 10 | color: cfd3d7 11 | - name: "more data needed" 12 | color: bfdadc 13 | - name: "P0" 14 | color: b60205 15 | description: "blocker: fix immediately!" 16 | - name: "P1" 17 | color: d93f0b 18 | description: "critical: next release" 19 | - name: "P2" 20 | color: e99695 21 | description: "major: an upcoming release" 22 | - name: "P3" 23 | color: fbca04 24 | description: "minor: not priorized" 25 | - name: "P4" 26 | color: fef2c0 27 | description: "unimportant: consider wontfix or other priority" 28 | - name: "question" 29 | color: d876e3 30 | - name: "type: bug" 31 | color: 0052cc 32 | - name: "type: documentation" 33 | color: 0052cc 34 | - name: "type: feature request" 35 | color: 0052cc 36 | - name: "wontfix" 37 | color: ffffff 38 | - name: "merge-queue" 39 | color: 0e8a16 40 | description: "merge on green CI" 41 | -------------------------------------------------------------------------------- /tests/posix_hermetic/genrule_path_output_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED="1 22 | 0" 23 | 24 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/genrule_path.txt)")" 25 | assert_eq "$EXPECTED" "$ACTUAL" "genrule_path.txt" 26 | -------------------------------------------------------------------------------- /tests/posix_hermetic/genrule_explicit_output_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED="1 22 | 0" 23 | 24 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/genrule_explicit.txt)")" 25 | assert_eq "$EXPECTED" "$ACTUAL" "genrule_explicit.txt" 26 | -------------------------------------------------------------------------------- /tests/import/invoke_shim_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ "$1" != "$2" ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED_OUTPUT="Hello World" 22 | OUTPUT="$(cat "$(rlocation "rules_sh_tests/import/invoke_shim.out")" | tr -d '\376\377')" # Remove BOM 23 | assert_eq "$EXPECTED_OUTPUT" "$OUTPUT" "invoke_shim" 24 | -------------------------------------------------------------------------------- /tests/posix_hermetic/runfiles_genrule_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED="message ECHO SUFFIX" 22 | 23 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/runfiles_genrule.txt)")" 24 | assert_eq "$EXPECTED" "$ACTUAL" "runfiles_genrule.txt" 25 | -------------------------------------------------------------------------------- /tests/posix_hermetic/runfiles_custom_rule_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED="message ECHO SUFFIX" 22 | 23 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/runfiles_custom_rule.txt)")" 24 | assert_eq "$EXPECTED" "$ACTUAL" "runfiles_custom_rule.txt" 25 | -------------------------------------------------------------------------------- /tests/posix_hermetic/custom_rule_output_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED="1 22 | 0" 23 | 24 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/custom_rule_explicit.txt)")" 25 | assert_eq "$EXPECTED" "$ACTUAL" "custom_rule_explicit.txt" 26 | 27 | ACTUAL="$(cat "$(rlocation rules_sh_tests/posix_hermetic/custom_rule_path.txt)")" 28 | assert_eq "$EXPECTED" "$ACTUAL" "custom_rule_path.txt" 29 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | TAG="$1" 5 | 6 | REPO="${GITHUB_REPOSITORY#*/}" 7 | 8 | # The prefix is chosen to match what GitHub generates for source archives 9 | PREFIX="${REPO}-${TAG:1}" 10 | ARCHIVE="${REPO}-${TAG:1}.tar.gz" 11 | 12 | git archive --format=tar.gz --prefix="${PREFIX}/" -o $ARCHIVE HEAD 13 | 14 | SHA=$(shasum -a 256 "$ARCHIVE" | awk '{print $1}') 15 | 16 | cat << EOF 17 | ## Using Bzlmod with Bazel 6+ 18 | 19 | 1. Enable with \`common --enable_bzlmod\` in \`.bazelrc\`. 20 | 2. Add to your \`MODULE.bazel\` file: 21 | 22 | ### For the core module 23 | 24 | \`\`\`starlark 25 | bazel_dep(name = "rules_sh", version = "${TAG:1}") 26 | \`\`\` 27 | 28 | ## Using WORKSPACE 29 | 30 | Paste this snippet into your \`WORKSPACE.bazel\` file: 31 | 32 | \`\`\`starlark 33 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 34 | 35 | http_archive( 36 | name = "rules_sh", 37 | sha256 = "${SHA}", 38 | strip_prefix = "$PREFIX", 39 | urls = ["https://github.com/tweag/rules_sh/releases/download/$TAG/$ARCHIVE"], 40 | ) 41 | 42 | load("@rules_sh//sh:repositories.bzl", "rules_sh_dependencies") 43 | 44 | rules_sh_dependencies() 45 | \`\`\` 46 | EOF 47 | 48 | echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT" 49 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | # Compatibility options (per bazel version) 2 | common:bazel8 --config=common 3 | 4 | common:bazel7 --config=common 5 | 6 | common:bazel6 --config=common 7 | 8 | common:common --noverbose_failures 9 | 10 | # Remote Cache Configuration 11 | build:remote-cache --bes_results_url=https://app.buildbuddy.io/invocation/ 12 | build:remote-cache --bes_backend=grpcs://remote.buildbuddy.io 13 | build:remote-cache --remote_cache=grpcs://remote.buildbuddy.io 14 | build:remote-cache --remote_timeout=3600 15 | # Avoid failures of the form `deadline exceeded after 14999958197ns DEADLINE_EXCEEDED`. 16 | # See https://github.com/tweag/rules_haskell/issues/1498. 17 | build:remote-cache --keep_backend_build_event_connections_alive=false 18 | # All clients except CI should be configured as read-only 19 | build:remote-cache --noremote_upload_local_results 20 | 21 | # CI Configuration 22 | build:ci --config=remote-cache 23 | build:ci --remote_upload_local_results 24 | test:ci --test_output=errors 25 | 26 | # No Stardoc 27 | build:no-stardoc --build_tag_filters=-stardoc_generation 28 | test:no-stardoc --test_tag_filters=-stardoc_generation 29 | 30 | # Try to load a file that includes the remote cache authentication flag 31 | try-import %workspace%/.bazelrc.auth 32 | 33 | # Try to load any configuration that is specific for this host 34 | try-import %workspace%/.bazelrc.local 35 | -------------------------------------------------------------------------------- /tests/sh_binaries/custom_rule_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ $1 != $2 ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED_RUN_OUTPUT="Hello File" 22 | RUN_OUTPUT="$(cat "$(rlocation rules_sh_tests/sh_binaries/custom_rule_run_output)")" 23 | assert_eq "$EXPECTED_RUN_OUTPUT" "$RUN_OUTPUT" "custom_rule_run_output" 24 | 25 | EXPECTED_RUN_SHELL_OUTPUT="Hello World"$'\n'"Hello File" 26 | RUN_SHELL_OUTPUT="$(cat "$(rlocation rules_sh_tests/sh_binaries/custom_rule_run_shell_output)")" 27 | assert_eq "$EXPECTED_RUN_SHELL_OUTPUT" "$RUN_SHELL_OUTPUT" "custom_rule_run_shell_output" 28 | -------------------------------------------------------------------------------- /sh/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//:bzl_library.bzl", "bzl_library") 2 | 3 | # @bazel_tools//tools does not define a bzl_library itself, instead we are 4 | # supposed to define our own using the @bazel_tools//tools:bzl_srcs filegroup. 5 | # See https://github.com/bazelbuild/skydoc/issues/166 6 | bzl_library( 7 | name = "bazel_tools", 8 | srcs = [ 9 | "@bazel_tools//tools:bzl_srcs", 10 | ], 11 | visibility = ["//:__subpackages__"], 12 | ) 13 | 14 | bzl_library( 15 | name = "repositories", 16 | srcs = [ 17 | "repositories.bzl", 18 | ], 19 | visibility = ["//visibility:public"], 20 | deps = [ 21 | ":bazel_tools", 22 | ], 23 | ) 24 | 25 | bzl_library( 26 | name = "sh", 27 | srcs = [ 28 | "sh.bzl", 29 | ], 30 | visibility = ["//visibility:public"], 31 | deps = [ 32 | ":bazel_tools", 33 | "@bazel_skylib//lib:paths", 34 | "@bazel_skylib//lib:dicts", 35 | "//sh/private:defs", 36 | ], 37 | ) 38 | 39 | bzl_library( 40 | name = "posix", 41 | srcs = [ 42 | "posix.bzl", 43 | ], 44 | visibility = ["//visibility:public"], 45 | deps = [ 46 | ":bazel_tools", 47 | "//sh/private:defs", 48 | "//sh/private:posix", 49 | "@bazel_skylib//lib:paths", 50 | ], 51 | ) 52 | 53 | exports_files([ 54 | "posix.bzl", 55 | "repositories.bzl", 56 | ]) 57 | -------------------------------------------------------------------------------- /tests/sh_binaries/genrule_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # --- begin runfiles.bash initialization v2 --- 3 | # Copy-pasted from the Bazel Bash runfiles library v2. 4 | set -uo pipefail; f=bazel_tools/tools/bash/runfiles/runfiles.bash 5 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 6 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 7 | source "$0.runfiles/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 10 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 11 | # --- end runfiles.bash initialization v2 --- 12 | set -euo pipefail 13 | 14 | assert_eq() { 15 | if [[ "$1" != "$2" ]]; then 16 | echo -e "Wrong output found in $3:\nExpected: '$1'\nGot: '$2'" 17 | return 1 18 | fi 19 | } 20 | 21 | EXPECTED_OUTPUT="Hello World" 22 | OUTPUT="$(cat "$(rlocation rules_sh_tests/sh_binaries/genrule_output_world)")" 23 | assert_eq "$EXPECTED_OUTPUT" "$OUTPUT" "genrule_output_world" 24 | 25 | EXPECTED_OUTPUT="Hello File" 26 | OUTPUT="$(cat "$(rlocation rules_sh_tests/sh_binaries/genrule_output_data)")" 27 | assert_eq "$EXPECTED_OUTPUT" "$OUTPUT" "genrule_output_data" 28 | 29 | EXPECTED_OUTPUT="Hello World"$'\n'"Hello File" 30 | OUTPUT="$(cat "$(rlocation rules_sh_tests/sh_binaries/genrule_output_by_path)")" 31 | assert_eq "$EXPECTED_OUTPUT" "$OUTPUT" "genrule_output_by_path" 32 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "rules_sh") 2 | 3 | load("//sh:repositories.bzl", "rules_sh_dependencies") 4 | 5 | rules_sh_dependencies() 6 | 7 | load("//sh:posix.bzl", "sh_posix_configure") 8 | 9 | sh_posix_configure() 10 | 11 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 12 | 13 | bazel_skylib_workspace() 14 | 15 | # documentation dependencies 16 | 17 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 18 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 19 | 20 | maybe( 21 | http_archive, 22 | "io_bazel_stardoc", 23 | sha256 = "62bd2e60216b7a6fec3ac79341aa201e0956477e7c8f6ccc286f279ad1d96432", 24 | urls = [ 25 | # NOTE: stardoc >= 0.7 is not compatible with Bazel 6 26 | "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", 27 | "https://github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", 28 | ], 29 | ) 30 | 31 | 32 | load("@io_bazel_stardoc//:setup.bzl", "stardoc_repositories") 33 | 34 | stardoc_repositories() 35 | 36 | load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") 37 | 38 | rules_jvm_external_deps() 39 | 40 | load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") 41 | 42 | rules_jvm_external_setup() 43 | 44 | load("@io_bazel_stardoc//:deps.bzl", "stardoc_external_deps") 45 | 46 | stardoc_external_deps() 47 | 48 | load("@stardoc_maven//:defs.bzl", stardoc_pinned_maven_install = "pinned_maven_install") 49 | 50 | stardoc_pinned_maven_install() 51 | -------------------------------------------------------------------------------- /sh/private/defs.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//lib:dicts.bzl", "dicts") 2 | 3 | ConstantInfo = provider(fields = ["value"]) 4 | 5 | def _constant_impl(ctx): 6 | return [ConstantInfo(value = ctx.attr.value)] 7 | 8 | bool_constant = rule( 9 | _constant_impl, 10 | attrs = { 11 | "value": attr.bool(), 12 | }, 13 | ) 14 | 15 | def to_var_name(label_name): 16 | """Turn a label name into a template variable info. 17 | 18 | Uses all upper case variable names with `_` as separator. 19 | """ 20 | return label_name.upper().replace("-", "_") 21 | 22 | def mk_template_variable_info(name, sh_binaries_info): 23 | var_prefix = to_var_name(name) 24 | return platform_common.TemplateVariableInfo(dicts.add( 25 | { 26 | "{}_{}".format(var_prefix, to_var_name(name)): file.path 27 | for name, file in sh_binaries_info.executables.items() 28 | }, 29 | { 30 | "_{}_PATH".format(var_prefix): ":".join(sh_binaries_info.paths.to_list()), 31 | }, 32 | )) 33 | 34 | def mk_default_info_with_files_to_run(ctx, name, files, runfiles): 35 | # Create a dummy executable to trigger the generation of a FilesToRun 36 | # provider which can be used in custom rules depending on this bundle to 37 | # input the needed runfiles into build actions. 38 | # This is a workaround for https://github.com/bazelbuild/bazel/issues/15486 39 | executable = ctx.actions.declare_file(name) 40 | ctx.actions.write(executable, "", is_executable = True) 41 | return DefaultInfo( 42 | executable = executable, 43 | files = files, 44 | runfiles = runfiles, 45 | ) 46 | -------------------------------------------------------------------------------- /tests/WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "rules_sh_tests") 2 | 3 | local_repository( 4 | name = "rules_sh", 5 | path = "../", 6 | ) 7 | 8 | load("@rules_sh//sh:repositories.bzl", "rules_sh_dependencies") 9 | 10 | rules_sh_dependencies() 11 | 12 | load("@rules_sh//sh:posix.bzl", "sh_posix_configure") 13 | 14 | sh_posix_configure() 15 | 16 | load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") 17 | 18 | bazel_skylib_workspace() 19 | 20 | load("//import:import_test.bzl", "import_test_repositories") 21 | 22 | import_test_repositories() 23 | 24 | # documentation dependencies 25 | 26 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 27 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 28 | 29 | maybe( 30 | http_archive, 31 | "io_bazel_stardoc", 32 | sha256 = "62bd2e60216b7a6fec3ac79341aa201e0956477e7c8f6ccc286f279ad1d96432", 33 | urls = [ 34 | # NOTE: stardoc >= 0.7 is not compatible with Bazel 6 35 | "https://mirror.bazel.build/github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", 36 | "https://github.com/bazelbuild/stardoc/releases/download/0.6.2/stardoc-0.6.2.tar.gz", 37 | ], 38 | ) 39 | 40 | 41 | load("@io_bazel_stardoc//:setup.bzl", "stardoc_repositories") 42 | 43 | stardoc_repositories() 44 | 45 | load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") 46 | 47 | rules_jvm_external_deps() 48 | 49 | load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") 50 | 51 | rules_jvm_external_setup() 52 | 53 | load("@io_bazel_stardoc//:deps.bzl", "stardoc_external_deps") 54 | 55 | stardoc_external_deps() 56 | 57 | load("@stardoc_maven//:defs.bzl", stardoc_pinned_maven_install = "pinned_maven_install") 58 | 59 | stardoc_pinned_maven_install() 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | # Create a draft release when triggered via Github's UI or Github CLI 2 | name: Prepare Release 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | version: 8 | description: 'Version to release (e.g. 0.11.0)' 9 | required: true 10 | type: string 11 | 12 | permissions: 13 | contents: write 14 | 15 | jobs: 16 | release: 17 | name: Prepare Release 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Check version 21 | run: | 22 | if ! [[ '${{ inputs.version }}' =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]]; then 23 | echo '${{ inputs.version }} does not match expected format `major.minor.patch`' >&2 24 | exit 1 25 | fi 26 | - name: Checkout 27 | uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 28 | with: 29 | ref: master # only create releases from main branch 30 | - name: Read section from CHANGELOG.md 31 | id: extract-changelog 32 | uses: sean0x42/markdown-extract@7b185cbe85263116bbf741e739e7198ba86465dc # v2.1.0 33 | with: 34 | file: CHANGELOG.md 35 | pattern: ${{ inputs.version }} 36 | - name: Prepare release notes and artifacts 37 | id: prepare 38 | run: | 39 | .github/workflows/release_prep.sh v${{ inputs.version }} > release_notes.txt 40 | printf '${{ steps.extract-changelog.outputs.markdown }}' >> release_notes.txt 41 | - name: Create draft release 42 | env: 43 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | run: | 45 | gh release create \ 46 | --draft \ 47 | --notes-file release_notes.txt \ 48 | --title v${{ inputs.version }} \ 49 | v${{ inputs.version }} \ 50 | ${{ steps.prepare.outputs.archive }} 51 | -------------------------------------------------------------------------------- /tests/import/create_shim_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # --- begin runfiles.bash initialization v3 --- 4 | # Copy-pasted from the Bazel Bash runfiles library v3. 5 | set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash 6 | # shellcheck disable=SC1090 7 | source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ 8 | source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ 9 | source "$0.runfiles/$f" 2>/dev/null || \ 10 | source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 11 | source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ 12 | { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e 13 | # --- end runfiles.bash initialization v3 --- 14 | 15 | set -euo pipefail 16 | 17 | SHIM_EXE="$(rlocation "$1")" 18 | EMPTY_EXE="$(rlocation "$2")" 19 | 20 | SHIMMED_EXE="$(rlocation "$3")" 21 | SHIMMED_SHIM="$(rlocation "$4")" 22 | 23 | ANOTHER_EXE="$(rlocation "$5")" 24 | ANOTHER_SHIM="$(rlocation "$6")" 25 | 26 | assert_file_eq() { 27 | comm "$1" "$2" || { 28 | echo "Expected files to be equal, but they are different: '$1' and '$2'". >&2 29 | return 1 30 | } 31 | } 32 | 33 | # Test that the shims use the correct shim.exe. 34 | 35 | assert_file_eq "$SHIM_EXE" "$SHIMMED_EXE" 36 | assert_file_eq "$SHIM_EXE" "$ANOTHER_EXE" 37 | 38 | parse_shim() { 39 | local line 40 | read -r line <"$1" || true 41 | if [[ $line =~ ^path\ \=\ (.*)$ ]]; then 42 | echo "${BASH_REMATCH[1]}" 43 | else 44 | echo "Malformed shim file: '$1'" >&2 45 | return 1 46 | fi 47 | } 48 | 49 | # Test that the target of the shim matches the correct target file. 50 | 51 | SHIMMED_TARGET="$(parse_shim "$SHIMMED_SHIM")" 52 | assert_file_eq "$EMPTY_EXE" "$SHIMMED_TARGET" 53 | 54 | ANOTHER_TARGET="$(parse_shim "$ANOTHER_SHIM")" 55 | assert_file_eq "$EMPTY_EXE" "$ANOTHER_TARGET" 56 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintenance instructions 2 | 3 | ## Cutting a new release 4 | 5 | * Create a dedicated branch: `release-` (e.g. `release-0.2.0`) 6 | * Check the changes since the last release by 7 | [comparing the heads](https://github.com/tweag/rules_sh/compare/v0.2.0...HEAD), 8 | add anything relevant to `CHANGELOG.md`, 9 | and update the version heading and unreleased heading in `CHANGELOG.md`. 10 | * Open a PR for the new release, like 11 | [#10](https://github.com/tweag/rules_sh/pull/14) 12 | * When merged, create a tag of the form `v0.2.0` on the merge commit and push it: 13 | `git push origin v0.2.0` 14 | * Create the release on GitHub, selecting the tag created above. 15 | * Add the relevant changelog section to the release notes. 16 | * Obtain the sha256 for the release archive, by downloading the blob, 17 | and calling `sha256sum` on it. 18 | * Update the release notes with a workspace setup section for the new version, 19 | such as [this example](https://github.com/tweag/rules_sh/releases/tag/v0.2.0) 20 | * Add the new version to the Bazel Central Registry as described below 21 | 22 | ## Add a new version to the Bazel Central Registry 23 | 24 | * Follow the instructions given in the [README][bcr-add] to add a new version 25 | of this module. Use the following input file to `add_module.py` for 26 | reference - don't forget to update the version and dependencies: 27 | ``` 28 | { 29 | "build_file": null, 30 | "build_targets": [], 31 | "compatibility_level": "0", 32 | "deps": [["bazel_skylib", "1.0.3"], ["platforms", "0.0.4"]], 33 | "module_dot_bazel": "path/to/rules_sh/MODULE.bazel", 34 | "name": "rules_sh", 35 | "patch_strip": 1, 36 | "patches": [], 37 | "presubmit_yml": "path/to/rules_sh/.bazelci/presubmit.yml", 38 | "strip_prefix": "rules_sh-0.2.0", 39 | "test_targets": [], 40 | "url": "https://github.com/tweag/rules_sh/archive/refs/tags/v0.2.0.tar.gz", 41 | "version": "0.2.0" 42 | } 43 | ``` 44 | 45 | [bcr-add]: https://github.com/bazelbuild/bazel-central-registry#module-contributor 46 | -------------------------------------------------------------------------------- /sh/private/get_cpu_value.bzl: -------------------------------------------------------------------------------- 1 | 2 | # taken from https://github.com/bazelbuild/rules_cc/blob/8395ec0172270f3bf92cd7b06c9b5b3f1f679e88/cc/private/toolchain/lib_cc_configure.bzl#L225 3 | # TODO(cb): remove when using rules_cc > 0.0.17 4 | def get_cpu_value(repository_ctx): 5 | """Compute the cpu_value based on the OS name. Doesn't %-escape the result! 6 | 7 | Args: 8 | repository_ctx: The repository context. 9 | Returns: 10 | One of (darwin, freebsd, x64_windows, ppc, s390x, arm, aarch64, k8, piii) 11 | """ 12 | os_name = repository_ctx.os.name 13 | arch = repository_ctx.os.arch 14 | if os_name.startswith("mac os"): 15 | # Check if we are on x86_64 or arm64 and return the corresponding cpu value. 16 | return "darwin_" + ("arm64" if arch == "aarch64" else "x86_64") 17 | if os_name.find("freebsd") != -1: 18 | return "freebsd" 19 | if os_name.find("openbsd") != -1: 20 | return "openbsd" 21 | if os_name.find("windows") != -1: 22 | if arch == "aarch64": 23 | return "arm64_windows" 24 | else: 25 | return "x64_windows" 26 | 27 | if arch in ["power", "ppc64le", "ppc", "ppc64"]: 28 | return "ppc" 29 | if arch in ["s390x"]: 30 | return "s390x" 31 | if arch in ["mips64"]: 32 | return "mips64" 33 | if arch in ["riscv64"]: 34 | return "riscv64" 35 | if arch in ["arm", "armv7l"]: 36 | return "arm" 37 | if arch in ["aarch64"]: 38 | return "aarch64" 39 | return "k8" if arch in ["amd64", "x86_64", "x64"] else "piii" 40 | 41 | def fail_on_err(return_value, prefix = None): 42 | """Fail if the given return value indicates an error. 43 | 44 | Args: 45 | return_value: Pair; If the second element is not `None` this indicates an error. 46 | prefix: optional, String; A prefix for the error message contained in `return_value`. 47 | 48 | Returns: 49 | The first element of `return_value` if no error was indicated. 50 | """ 51 | result, err = return_value 52 | 53 | if err: 54 | if prefix: 55 | msg = prefix + err 56 | else: 57 | msg = err 58 | fail(msg) 59 | 60 | return result 61 | -------------------------------------------------------------------------------- /sh/docs/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc") 2 | load("@bazel_skylib//rules:diff_test.bzl", "diff_test") 3 | load("@bazel_skylib//rules:write_file.bzl", "write_file") 4 | 5 | # The following rules are tagged with 'stardoc_generation' so we can skip them 6 | # for Github pipelines that have bzlmod enabled. This is necessary because 7 | # Stardoc does not currently work with modules. For more information see: 8 | # https://github.com/bazelbuild/stardoc/issues/117 9 | # https://github.com/bazelbuild/bazel/issues/14140 10 | 11 | stardoc( 12 | name = "gen-setup-md", 13 | input = "//sh:repositories.bzl", 14 | out = "setup.md", 15 | deps = ["//sh:repositories"], 16 | symbol_names = ["rules_sh_dependencies"], 17 | func_template = "//sh/docs:func.vm", 18 | header_template = "//sh/docs:header.vm", 19 | tags = ["stardoc_generation"], 20 | ) 21 | 22 | stardoc( 23 | name = "gen-usage-md", 24 | input = "//sh:posix.bzl", 25 | out = "usage.md", 26 | deps = ["//sh:posix"], 27 | symbol_names = ["sh_posix_configure"], 28 | func_template = "//sh/docs:func.vm", 29 | header_template = "//sh/docs:empty.vm", 30 | tags = ["stardoc_generation"], 31 | ) 32 | 33 | genrule( 34 | name = "gen-readme-md", 35 | outs = ["readme.md"], 36 | srcs = [":setup.md", ":usage.md"], 37 | cmd = "$(POSIX_CAT) $(execpath :setup.md) $(execpath :usage.md) >$(OUTS)", 38 | toolchains = ["@rules_sh//sh/posix:make_variables"], 39 | tags = ["stardoc_generation"], 40 | ) 41 | 42 | write_file( 43 | name = "write-copy-readme-sh", 44 | out = "copy-readme.sh", 45 | content = [""" 46 | "$POSIX_CP" -v --no-preserve=all "$1" "$BUILD_WORKSPACE_DIRECTORY/README.md" 47 | """], 48 | tags = ["stardoc_generation"], 49 | ) 50 | 51 | sh_binary( 52 | name = "update-readme", 53 | srcs = [":copy-readme.sh"], 54 | args = ["$(location :readme.md)"], 55 | data = [":readme.md"], 56 | env = {"POSIX_CP": "$(POSIX_CP)"}, 57 | toolchains = ["@rules_sh//sh/posix:make_variables"], 58 | tags = ["stardoc_generation"], 59 | ) 60 | 61 | diff_test( 62 | name = "check-readme", 63 | failure_message = "Please run: bazel run //sh/docs:update-readme", 64 | file1 = "//:README.md", 65 | file2 = ":readme.md", 66 | tags = ["stardoc_generation"], 67 | ) 68 | -------------------------------------------------------------------------------- /sh/repositories.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") 2 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") 3 | 4 | def rules_sh_dependencies(): 5 | """## Setup 6 | 7 | See the **WORKSPACE setup** section of the [current release][releases]. 8 | 9 | [releases]: https://github.com/tweag/rules_sh/releases 10 | 11 | Or use the following template in your `WORKSPACE` file to install a development 12 | version of `rules_sh`: 13 | 14 | ``` python 15 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 16 | http_archive( 17 | name = "rules_sh", 18 | # Replace git revision and sha256. 19 | sha256 = "0000000000000000000000000000000000000000000000000000000000000000", 20 | strip_prefix = "rules_sh-0000000000000000000000000000000000000000", 21 | urls = ["https://github.com/tweag/rules_sh/archive/0000000000000000000000000000000000000000.tar.gz"], 22 | ) 23 | load("@rules_sh//sh:repositories.bzl", "rules_sh_dependencies") 24 | rules_sh_dependencies() 25 | ``` 26 | """ 27 | maybe( 28 | http_archive, 29 | name = "bazel_skylib", 30 | sha256 = "6e78f0e57de26801f6f564fa7c4a48dc8b36873e416257a92bbb0937eeac8446", 31 | urls = [ 32 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.8.2/bazel-skylib-1.8.2.tar.gz", 33 | "https://github.com/bazelbuild/bazel-skylib/releases/download/1.8.2/bazel-skylib-1.8.2.tar.gz", 34 | ], 35 | ) 36 | maybe( 37 | http_archive, 38 | name = "platforms", 39 | sha256 = "3384eb1c30762704fbe38e440204e114154086c8fc8a8c2e3e28441028c019a8", 40 | urls = [ 41 | "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/1.0.0/platforms-1.0.0.tar.gz", 42 | "https://github.com/bazelbuild/platforms/releases/download/1.0.0/platforms-1.0.0.tar.gz", 43 | ], 44 | ) 45 | maybe( 46 | http_archive, 47 | name = "rules_sh_shim_exe", 48 | sha256 = "c8452b3c4b8c219edef150cc423b0c844cb2d46381266011f6f076301e7e65d9", 49 | urls = ["https://github.com/ScoopInstaller/Shim/releases/download/v1.1.0/shim-1.1.0.zip"], 50 | build_file_content = """exports_files(["shim.exe"])""", 51 | ) 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Bazel 2 | 3 | ## Contributor License Agreement 4 | 5 | Contributions to this project must be accompanied by a Contributor License 6 | Agreement. You (or your employer) retain the copyright to your contribution, 7 | this simply gives us permission to use and redistribute your contributions as 8 | part of the project. Head over to to see 9 | your current agreements on file or to sign a new one. 10 | 11 | You generally only need to submit a CLA once, so if you've already submitted one 12 | (even if it was for a different project), you probably don't need to do it 13 | again. 14 | 15 | ## Contribution process 16 | 17 | 1. Explain your idea and discuss your plan with members of the team. 18 | The best way to do this is to create an [issue][issue-tracker] or 19 | comment on an existing issue. 20 | 1. Prepare a git commit with your change. Don't forget to 21 | add [tests][tests]. Run the existing tests with `bazel test //...`. 22 | Update [README.md](./README.md) if appropriate. 23 | 1. [Create a pull request](https://help.github.com/articles/creating-a-pull-request/). 24 | This will start the code review process. **All submissions, 25 | including submissions by project members, require review.** 26 | 1. You may be asked to make some changes. You'll also need to sign the 27 | CLA at this point, if you haven't done so already. Our continuous 28 | integration bots will test your change automatically on supported 29 | platforms. Once everything looks good, your change will be merged. 30 | 31 | [issue-tracker]: https://github.com/tweag/rules_sh/issues 32 | [tests]: https://github.com/tweag/rules_sh/tree/master/tests 33 | 34 | ## Setting up your development environment 35 | 36 | Read how to [set up your development environment](https://bazel.build/contributing.html) 37 | 38 | ## Bazel Remote Cache 39 | 40 | The remote cache configuration for this repository is stored in [.bazelrc](/.bazelrc) and grouped 41 | under the name, `remote-cache`. It is configured to allow read-only access for all clients and 42 | read-write for CI. 43 | 44 | To enable the remote cache, 45 | 46 | 1. Add `build --remote_header=x-buildbuddy-api-key=${buildbuddy_api_key}` to `.bazelrc.auth` 47 | at the root of the workspace, replacing `${buildbuddy_api_key}` with the actual API key value. 48 | 1. Add `build --config=remote-cache` to `.bazelrc.local` at the root of the workspace. 49 | -------------------------------------------------------------------------------- /sh/import.bzl: -------------------------------------------------------------------------------- 1 | def _copy_file(repository_ctx, *, source, destination, executable): 2 | """Create a file copy of the source at the destination. 3 | 4 | Args: 5 | repository_ctx: The repository rule context. 6 | source: string; Label; or path, The file to copy. 7 | destination: string; Label; or path, The copy to create, relative to the repository directory. 8 | executable: bool, Whether the copy should be executable. 9 | """ 10 | 11 | # Note, Bazel provides no "copy file" primitive in repository rules. This 12 | # reads the source file and writes a new file as a workaround. 13 | # See https://github.com/bazelbuild/bazel/issues/11858 14 | repository_ctx.file( 15 | destination, 16 | repository_ctx.read(source), 17 | executable = executable, 18 | legacy_utf8 = False, 19 | ) 20 | 21 | def create_shim(repository_ctx, *, name, target, shim_exe = Label("@rules_sh_shim_exe//:shim.exe")): 22 | """Create a binary shim for the given target. 23 | 24 | Creates a copy of the [shim binary][shim-binary] named `.exe` with a 25 | neighboring `.shim` file that points to the given target. 26 | 27 | [shim-binary]: https://github.com/ScoopInstaller/Shim 28 | 29 | #### Example 30 | 31 | This can be used inside a repository rule run on Windows to generate a shim 32 | of an external executable and import it into an `sh_binaries` target. 33 | 34 | ```bzl 35 | powershell = repository_ctx.which("powershell") 36 | if powershell != None: 37 | create_shim( 38 | repository_ctx, 39 | name = "powershell", 40 | target = powershell, 41 | ) 42 | 43 | repository_ctx.file( 44 | "BUILD.bazel", 45 | "\\n".join([ 46 | "sh_binaries(", 47 | " name = 'tools',", 48 | " srcs = ['powershell.exe'],", 49 | " data = ['powershell.shim'],", 50 | ")", 51 | ]), 52 | ) 53 | ``` 54 | 55 | Args: 56 | name: string or path, The name of the newly created shim, without the .exe suffix, relative to the repository directory. 57 | target: string or path, Absolute path to the target executable. 58 | shim_exe: string; Label; or path, The original shim binary. 59 | """ 60 | _copy_file( 61 | repository_ctx, 62 | source = shim_exe, 63 | destination = "{}.exe".format(name), 64 | executable = True, 65 | ) 66 | repository_ctx.file( 67 | "{}.shim".format(name), 68 | "path = {}".format(repository_ctx.path(target)), 69 | executable = False, 70 | ) 71 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | on: 3 | push: 4 | branches: master 5 | pull_request: 6 | branches: master 7 | workflow_dispatch: # allows manual triggering 8 | schedule: 9 | - cron: '1 11 * * *' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} 14 | 15 | jobs: 16 | build-and-test: 17 | name: Build & Test 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: [ubuntu-24.04, macos-14, windows-2022] 22 | bazel_mode: [workspace, module] 23 | version: ["6.5.0", "7.4.1", "8.0.0"] 24 | defaults: 25 | run: 26 | shell: bash 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 30 | - uses: tweag/configure-bazel-remote-cache-auth@144b0b915f13a418f5eafe2f68d19564ec136c62 # v0.1.1 31 | with: 32 | buildbuddy_api_key: ${{ secrets.BUILDBUDDY_API_KEY }} 33 | bazelrc_path: .bazelrc.auth 34 | - uses: extractions/netrc@f6f1722d05ce2890aa86fd9654565b1214ac53a4 # v2.0.1 35 | with: 36 | machine: api.github.com 37 | password: ${{ secrets.GITHUB_TOKEN }} 38 | - name: Configure for CI 39 | uses: tweag/write-bazelrc@b1f117f25f2042ecdc708026eadd23bf825bc98a # v0.1.1 40 | with: 41 | content: build --config=ci 42 | - name: Disable stardoc 43 | # Windows: Stardoc complains about docstring quote indentation on Windows. 44 | # bzlmod: Stardoc does not work with bzlmod. 45 | if: ${{ runner.os == 'Windows' || matrix.bazel_mode == 'module' }} 46 | uses: tweag/write-bazelrc@b1f117f25f2042ecdc708026eadd23bf825bc98a # v0.1.1 47 | with: 48 | content: build --config=no-stardoc 49 | - name: Enable bzlmod 50 | uses: tweag/write-bazelrc@b1f117f25f2042ecdc708026eadd23bf825bc98a # v0.1.1 51 | if: ${{ matrix.bazel_mode == 'module' }} 52 | with: 53 | content: build --enable_bzlmod 54 | - name: Configure the Bazel version 55 | run: | 56 | echo "USE_BAZEL_VERSION=${{ matrix.version }}" > .bazeliskrc 57 | echo "USE_BAZEL_VERSION=${{ matrix.version }}" > tests/.bazeliskrc 58 | - name: Run Bazel test at the root 59 | if: ${{ matrix.bazel_mode == 'workspace' && runner.os != 'Windows' }} 60 | uses: ./.github/actions/run_bazel_test 61 | - name: Run Bazel test under the tests directory 62 | uses: ./.github/actions/run_bazel_test 63 | with: 64 | working-directory: tests 65 | 66 | all_ci_tests: 67 | runs-on: ubuntu-latest 68 | needs: 69 | - build-and-test 70 | if: ${{ always() }} 71 | steps: 72 | - uses: cgrindel/gha_join_jobs@8a9736c29a7e5ebd9f3af2870f59cb6d563b6659 # v1.4.0 73 | env: 74 | USE_BAZEL_VERSION: '7.x' 75 | with: 76 | github_token: ${{ secrets.GITHUB_TOKEN }} 77 | -------------------------------------------------------------------------------- /sh/private/posix.bzl: -------------------------------------------------------------------------------- 1 | # List of Unix commands as specified by IEEE Std 1003.1-2008. 2 | # Extracted from https://en.wikipedia.org/wiki/List_of_Unix_commands. 3 | commands = [ 4 | "admin", 5 | "alias", 6 | "ar", 7 | "asa", 8 | "at", 9 | "awk", 10 | "basename", 11 | "batch", 12 | "bc", 13 | "bg", 14 | "cc", 15 | "c99", 16 | "cal", 17 | "cat", 18 | "cd", 19 | "cflow", 20 | "chgrp", 21 | "chmod", 22 | "chown", 23 | "cksum", 24 | "cmp", 25 | "comm", 26 | "command", 27 | "compress", 28 | "cp", 29 | "crontab", 30 | "csplit", 31 | "ctags", 32 | "cut", 33 | "cxref", 34 | "date", 35 | "dd", 36 | "delta", 37 | "df", 38 | "diff", 39 | "dirname", 40 | "du", 41 | "echo", 42 | "ed", 43 | "env", 44 | "ex", 45 | "expand", 46 | "expr", 47 | "false", 48 | "fc", 49 | "fg", 50 | "file", 51 | "find", 52 | "fold", 53 | "fort77", 54 | "fuser", 55 | "gencat", 56 | "get", 57 | "getconf", 58 | "getopts", 59 | "grep", 60 | "hash", 61 | "head", 62 | "iconv", 63 | "id", 64 | "ipcrm", 65 | "ipcs", 66 | "jobs", 67 | "join", 68 | "kill", 69 | "lex", 70 | "link", 71 | "ln", 72 | "locale", 73 | "localedef", 74 | "logger", 75 | "logname", 76 | "lp", 77 | "ls", 78 | "m4", 79 | "mailx", 80 | "make", 81 | "man", 82 | "mesg", 83 | "mkdir", 84 | "mkfifo", 85 | "more", 86 | "mv", 87 | "newgrp", 88 | "nice", 89 | "nl", 90 | "nm", 91 | "nohup", 92 | "od", 93 | "paste", 94 | "patch", 95 | "pathchk", 96 | "pax", 97 | "pr", 98 | "printf", 99 | "prs", 100 | "ps", 101 | "pwd", 102 | "qalter", 103 | "qdel", 104 | "qhold", 105 | "qmove", 106 | "qmsg", 107 | "qrerun", 108 | "qrls", 109 | "qselect", 110 | "qsig", 111 | "qstat", 112 | "qsub", 113 | "read", 114 | "renice", 115 | "rm", 116 | "rmdel", 117 | "rmdir", 118 | "sact", 119 | "sccs", 120 | "sed", 121 | "sh", 122 | "sleep", 123 | "sort", 124 | "split", 125 | "strings", 126 | "strip", 127 | "stty", 128 | "tabs", 129 | "tail", 130 | "talk", 131 | "tee", 132 | "test", 133 | "time", 134 | "touch", 135 | "tput", 136 | "tr", 137 | "true", 138 | "tsort", 139 | "tty", 140 | "type", 141 | "ulimit", 142 | "umask", 143 | "unalias", 144 | "uname", 145 | "uncompress", 146 | "unexpand", 147 | "unget", 148 | "uniq", 149 | "unlink", 150 | "uucp", 151 | "uudecode", 152 | "uuencode", 153 | "uustat", 154 | "uux", 155 | "val", 156 | "vi", 157 | "wait", 158 | "wc", 159 | "what", 160 | "who", 161 | "write", 162 | "xargs", 163 | "yacc", 164 | "zcat", 165 | ] 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shell rules for Bazel 2 | 3 | [![Continuous Integration](https://github.com/tweag/rules_sh/actions/workflows/workflow.yaml/badge.svg?event=schedule)](https://github.com/tweag/rules_sh/actions/workflows/workflow.yaml) 4 | 5 | This project extends Bazel with a toolchain for common shell commands. 6 | 7 | ## Setup 8 | 9 | See the **WORKSPACE setup** section of the [current release][releases]. 10 | 11 | [releases]: https://github.com/tweag/rules_sh/releases 12 | 13 | Or use the following template in your `WORKSPACE` file to install a development 14 | version of `rules_sh`: 15 | 16 | ``` python 17 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 18 | http_archive( 19 | name = "rules_sh", 20 | # Replace git revision and sha256. 21 | sha256 = "0000000000000000000000000000000000000000000000000000000000000000", 22 | strip_prefix = "rules_sh-0000000000000000000000000000000000000000", 23 | urls = ["https://github.com/tweag/rules_sh/archive/0000000000000000000000000000000000000000.tar.gz"], 24 | ) 25 | load("@rules_sh//sh:repositories.bzl", "rules_sh_dependencies") 26 | rules_sh_dependencies() 27 | ``` 28 | 29 | 30 | 31 | ## Usage 32 | 33 | ### Configure the toolchain 34 | 35 | Add the following to your `WORKSPACE` file to configure a local POSIX toolchain. 36 | 37 | ``` python 38 | load("@rules_sh//sh:posix.bzl", "sh_posix_configure") 39 | sh_posix_configure() 40 | ``` 41 | 42 | Bazel will query `PATH` for common Unix shell commands. You can override the 43 | path to individual commands with environment variables of the form 44 | `POSIX_`. E.g. `POSIX_MAKE=/usr/bin/gmake`. 45 | 46 | Note, this introduces an inhermeticity to the build as the contents of `PATH` 47 | may be specific to your machine's setup. 48 | 49 | Refer to [`rules_nixpkgs`'s][rules_nixpkgs] `nixpkgs_posix_configure` for a 50 | hermetic alternative. 51 | 52 | [rules_nixpkgs]: https://github.com/tweag/rules_nixpkgs.git 53 | 54 | ### Use Unix tools in `genrule`s 55 | 56 | The POSIX toolchain exposes custom make variables of the form 57 | `POSIX_` for discovered commands. Use these as follows: 58 | 59 | ``` python 60 | genrule( 61 | name = "example", 62 | srcs = [":some-input-file"], 63 | outs = ["some-output-file"], 64 | cmd = "$(POSIX_GREP) some-pattern $(execpath :some-input-file.bzl) > $(OUTS)", 65 | toolchains = ["@rules_sh//sh/posix:make_variables"], 66 | ) 67 | ``` 68 | 69 | See `posix.commands` defined in `@rules_sh//sh/posix.bzl` for the list of known 70 | POSIX commands. 71 | 72 | ### Use Unix tools in custom rules 73 | 74 | The POSIX toolchain provides two attributes: 75 | - `commands`: A `dict` mapping names of commands to their paths. 76 | - `paths`: A deduplicated list of bindir paths suitable for generating `$PATH`. 77 | 78 | ``` python 79 | def _my_rule_impl(ctx): 80 | posix_info = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"] 81 | ctx.actions.run( 82 | executable = posix_info.commands["grep"], 83 | ... 84 | ) 85 | ctx.actions.run_shell( 86 | command = "grep ...", 87 | env = {"PATH": ":".join(posix_info.paths)}, 88 | ... 89 | ) 90 | 91 | my_rule = rule( 92 | _my_rule_impl, 93 | toolchains = ["@rules_sh//sh/posix:toolchain_type"], 94 | ... 95 | ) 96 | ``` 97 | 98 | 99 | -------------------------------------------------------------------------------- /tests/posix_tests.bzl: -------------------------------------------------------------------------------- 1 | """Unit tests for posix.bzl.""" 2 | 3 | load("@bazel_skylib//lib:new_sets.bzl", "sets") 4 | load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") 5 | load("@rules_sh//sh:posix.bzl", "posix") 6 | 7 | _PosixInfo = provider() 8 | 9 | def _get_posix_toolchain_impl(ctx): 10 | return [_PosixInfo( 11 | toolchain = ctx.toolchains[posix.TOOLCHAIN_TYPE], 12 | variables = ctx.var, 13 | )] 14 | 15 | get_posix_toolchain = rule( 16 | implementation = _get_posix_toolchain_impl, 17 | toolchains = [posix.TOOLCHAIN_TYPE], 18 | ) 19 | 20 | def _command_items_test(ctx): 21 | """The POSIX toolchain should have an item for each command.""" 22 | env = analysistest.begin(ctx) 23 | toolchain = analysistest.target_under_test(env)[_PosixInfo].toolchain 24 | 25 | expected = sets.make(posix.commands) 26 | actual = sets.make(toolchain.commands.keys()) 27 | msg = "Mismatch between POSIX toolchain items and known commands" 28 | asserts.new_set_equals(env, expected, actual, msg) 29 | 30 | return analysistest.end(env) 31 | 32 | command_items_test = analysistest.make(_command_items_test) 33 | 34 | def _make_variables_test(ctx): 35 | """The POSIX toolchain should define a make variable for each found command.""" 36 | env = analysistest.begin(ctx) 37 | toolchain = analysistest.target_under_test(env)[_PosixInfo].toolchain 38 | variables = analysistest.target_under_test(env)[_PosixInfo].variables 39 | 40 | for (cmd, cmd_path) in toolchain.commands.items(): 41 | varname = "POSIX_%s" % cmd.upper() 42 | if cmd_path != None: 43 | asserts.equals(env, cmd_path, variables[varname]) 44 | else: 45 | asserts.false(env, varname in variables) 46 | 47 | return analysistest.end(env) 48 | 49 | make_variables_test = analysistest.make(_make_variables_test) 50 | 51 | def _grep_genrule_test(ctx): 52 | env = analysistest.begin(ctx) 53 | actions = analysistest.target_actions(env) 54 | toolchain = ctx.attr.posix_toolchain[_PosixInfo].toolchain 55 | 56 | asserts.equals(env, 1, len(actions)) 57 | genrule_command = " ".join(actions[0].argv) 58 | grep_path = toolchain.commands["grep"] 59 | msg = "genrule command should contain path to grep" 60 | asserts.true(env, genrule_command.find(grep_path) >= 0, msg) 61 | 62 | return analysistest.end(env) 63 | 64 | grep_genrule_test = analysistest.make( 65 | _grep_genrule_test, 66 | attrs = {"posix_toolchain": attr.label()}, 67 | ) 68 | 69 | def posix_test_suite(): 70 | """Creates the test targets and test suite for posix.bzl tests.""" 71 | get_posix_toolchain( 72 | name = "posix_toolchain", 73 | toolchains = [posix.MAKE_VARIABLES], 74 | ) 75 | command_items_test( 76 | name = "command_items_test", 77 | target_under_test = ":posix_toolchain", 78 | ) 79 | make_variables_test( 80 | name = "make_variables_test", 81 | target_under_test = ":posix_toolchain", 82 | ) 83 | native.genrule( 84 | name = "posix_grep_genrule", 85 | srcs = [":posix_tests.bzl"], 86 | outs = ["posix_grep_genrule.txt"], 87 | cmd = "$(POSIX_GREP) posix_grep_genrule $(execpath :posix_tests.bzl) > $(OUTS)", 88 | toolchains = [posix.MAKE_VARIABLES], 89 | ) 90 | grep_genrule_test( 91 | name = "grep_genrule_test", 92 | target_under_test = ":posix_grep_genrule", 93 | posix_toolchain = ":posix_toolchain", 94 | ) 95 | -------------------------------------------------------------------------------- /bzlmod/extensions.bzl: -------------------------------------------------------------------------------- 1 | load("//sh:posix.bzl", "sh_posix_configure") 2 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 3 | 4 | def _get_local_posix_config_enabled(module_ctx): 5 | # Check that every `local_posix_config` tag on this extension agrees about 6 | # whether we should create the local posix toolchain: 7 | # 8 | # (if no tags are set, default to true) 9 | enable_attrs = [ 10 | tag.enable 11 | for mod in module_ctx.modules 12 | for tag in mod.tags.local_posix_config 13 | ] or [True] 14 | 15 | if all(enable_attrs) or not any(enable_attrs): 16 | return enable_attrs[0] 17 | else: 18 | # if the tags are not in agreement: 19 | msg = "Mismatched values for `sh_configure.local_posix_config.enable`:" 20 | 21 | for mod in module_ctx.modules: 22 | msg += "\n - module `{}` (version: {}) specifies:".format( 23 | mod.name, mod.version, 24 | ) 25 | for tag in mod.tags.local_posix_config: 26 | msg += "\n * {}".format(tag.enable) 27 | msg += "\n" 28 | 29 | # fallback to using the first occurrence: 30 | (fallback, mod_name, mod_version) = [ 31 | (tag.enable, mod.name, mod.version) 32 | for mod in module_ctx.modules 33 | for tag in mod.tags.local_posix_config 34 | ][0] 35 | 36 | msg += "\nUsing `{}` from module `{}` (version: {}) ".format( 37 | fallback, mod_name, mod_version, 38 | ) 39 | msg += "— this is the first occurrence of this tag reached via BFS from " 40 | msg += "the root module." 41 | 42 | print(msg) 43 | return fallback 44 | 45 | # Note: inlining this makes Bazel crash with: 46 | # ``` 47 | # net.starlark.java.eval.Starlark$UncheckedEvalException: 48 | # IllegalStateException thrown during Starlark evaluation 49 | # ``` 50 | _stub = repository_rule( 51 | implementation = lambda rctx: rctx.file("BUILD.bazel"), 52 | ) 53 | 54 | def _sh_configure_impl(module_ctx): 55 | enable_local_posix_toolchain = _get_local_posix_config_enabled(module_ctx) 56 | 57 | if enable_local_posix_toolchain: 58 | sh_posix_configure(register = False) 59 | else: 60 | # stub repository so `use_repo(..., "local_posix_config")` and 61 | # `register_toolchains("@local_posix_config//...")` do not raise errors 62 | _stub(name = "local_posix_config") 63 | 64 | http_archive( 65 | name = "rules_sh_shim_exe", 66 | sha256 = "c8452b3c4b8c219edef150cc423b0c844cb2d46381266011f6f076301e7e65d9", 67 | urls = ["https://github.com/ScoopInstaller/Shim/releases/download/v1.1.0/shim-1.1.0.zip"], 68 | build_file_content = """exports_files(["shim.exe"])""", 69 | ) 70 | 71 | sh_configure = module_extension( 72 | implementation = _sh_configure_impl, 73 | tag_classes = { 74 | "local_posix_config": tag_class( 75 | attrs = { 76 | "enable": attr.bool( 77 | default = True, 78 | doc = """ 79 | Whether to create and register the `@local_posix_config//:local_posix_toolchain` 80 | toolchain. 81 | 82 | In the event that there are multiple instances of this tag that contradict, the 83 | first tag's value (where modules are ordered by BFS from the root module) will 84 | be used. 85 | """, 86 | ), 87 | }, 88 | doc = """ 89 | Tag class with options for the `@local_posix_config` repo that this extension 90 | creates. 91 | """, 92 | ), 93 | }, 94 | doc = """ 95 | Module extension that sets up `rules_sh` toolchains. 96 | 97 | This is essentially a wrapper over `@rules_sh//sh:posix.bzl#sh_posix_configure` 98 | for use with bzlmod. 99 | """, 100 | ) 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/). 6 | 7 | ## [0.5.0] 8 | 9 | [0.5.0]: https://github.com/tweag/rules_sh/compare/v0.4.0...v0.5.0 10 | 11 | ### Changed 12 | 13 | - Add support for Bazel 8. 14 | See [PR #91][#91] 15 | 16 | [#91]: https://github.com/tweag/rules_sh/pull/91 17 | 18 | ## [0.4.0] 19 | 20 | [0.4.0]: https://github.com/tweag/rules_sh/compare/v0.3.0...v0.4.0 21 | 22 | ### Added 23 | 24 | - The new experimental `sh_posix_hermetic_toolchain` rule provides a hermetic 25 | alternative to `sh_posix_toolchain` based on binary bundles defined by 26 | `sh_binaries`. 27 | See [PR #34][#34]. 28 | 29 | - Add support for Bazel 6 and 7 30 | See [PR #35][#35] & [PR #43][#43]. 31 | 32 | - Allow disabling the local posix toolchain under bzlmod 33 | See [PR #50][#50] 34 | 35 | ### Changed 36 | 37 | - Mark stardoc as a dev_dependency 38 | See [PR #52][#52] 39 | 40 | [#34]: https://github.com/tweag/rules_sh/issues/34 41 | [#35]: https://github.com/tweag/rules_sh/issues/35 42 | [#43]: https://github.com/tweag/rules_sh/issues/43 43 | [#50]: https://github.com/tweag/rules_sh/issues/50 44 | [#52]: https://github.com/tweag/rules_sh/issues/52 45 | 46 | ## [0.3.0] - 2022-07-19 47 | 48 | [0.3.0]: https://github.com/tweag/rules_sh/compare/v0.2.0...v0.3.0 49 | 50 | ### Added 51 | 52 | - The new `sh_binaries` rule can bundle multiple executable files tracked by 53 | Bazel into a single target that can be used in a genrule or a custom rule to 54 | access those tools, either through make variables in a genrule, or through 55 | the `ShBinariesInfo` provider. 56 | See [issue #19][#19]. 57 | 58 | ### Changed 59 | 60 | - Modularized rules_sh in preparation for [bzlmod](https://docs.bazel.build/versions/5.2.0/bzlmod.html) 61 | See [#18] 62 | 63 | [#18]: https://github.com/tweag/rules_sh/issues/18 64 | [#19]: https://github.com/tweag/rules_sh/issues/19 65 | 66 | ## [0.2.0] - 2020-03-26 67 | 68 | [0.2.0]: https://github.com/tweag/rules_sh/compare/v0.1.1...v0.2.0 69 | 70 | ### Added 71 | 72 | - Define appropriate `bzl_library` rules, so that rules that 73 | depend `rules_sh` can generate `Stardoc` documentation. 74 | See PR [#11][#11] and [Skydoc's deprecation][skydoc_deprecation] 75 | for the motivation. 76 | 77 | ### Changed 78 | 79 | - `sh_posix_toolchain` now has a single attribute `cmds`, which 80 | is a string to string `dict`; instead of having one attribute 81 | per member of `posix.commands`. It is a breaking change if you were 82 | calling `sh_posix_toolchain` directly. 83 | 84 | If you were calling this rule as follows: 85 | 86 | ``` 87 | sh_posix_toolchain(cat = "/bin/cat", wc = "/usr/bin/wc") 88 | ``` 89 | 90 | you should now do: 91 | 92 | ``` 93 | sh_posix_toolchain(cmds = { "cat": "/bin/cat", "wc": "/usr/bin/wc" }) 94 | ``` 95 | 96 | See PR [#14][#14] and issue [#13][#13] for the motivation. 97 | 98 | [#14]: https://github.com/tweag/rules_sh/pull/14 99 | [#13]: https://github.com/tweag/rules_sh/issues/13 100 | [#11]: https://github.com/tweag/rules_sh/pull/11 101 | [skydoc_deprecation]: https://github.com/bazelbuild/stardoc/blob/master/docs/skydoc_deprecation.md#starlark-dependencies 102 | 103 | ## [0.1.1] - 2019-11-13 104 | 105 | [0.1.1]: https://github.com/tweag/rules_sh/compare/v0.1.0...v0.1.1 106 | 107 | ### Changed 108 | 109 | - Avoid finding non-POSIX compliant tools in `sh_posix_configure` on Windows. 110 | See [#7][#7]. 111 | 112 | [#7]: https://github.com/tweag/rules_sh/pull/7 113 | 114 | ## [0.1.0] - 2019-11-13 115 | 116 | [0.1.0]: https://github.com/tweag/rules_sh/releases/tag/v0.1.0 117 | 118 | ### Added 119 | 120 | - Initial release, see `README.md` for an overview. 121 | See [#1][#1] for the discussion on naming. 122 | 123 | [#1]: https://github.com/tweag/rules_sh/issues/1 124 | -------------------------------------------------------------------------------- /tests/import/import_test.bzl: -------------------------------------------------------------------------------- 1 | load("@rules_sh//sh:import.bzl", "create_shim") 2 | load(":get_cpu_value.bzl", "get_cpu_value") 3 | 4 | # create shim ######################################################## 5 | 6 | def _create_shim_test(): 7 | native.sh_test( 8 | name = "create_shim_test", 9 | srcs = ["create_shim_test.sh"], 10 | deps = ["@bazel_tools//tools/bash/runfiles"], 11 | data = [ 12 | "@rules_sh_shim_exe//:shim.exe", 13 | "@rules_sh_import_test_create_shim_test_source//:empty.exe", 14 | "@rules_sh_import_test_create_shim_test_shim//:shimmed.exe", 15 | "@rules_sh_import_test_create_shim_test_shim//:shimmed.shim", 16 | "@rules_sh_import_test_create_shim_test_shim//prefix:another.exe", 17 | "@rules_sh_import_test_create_shim_test_shim//prefix:another.shim", 18 | ], 19 | args = [ 20 | "$(rlocationpath @rules_sh_shim_exe//:shim.exe)", 21 | "$(rlocationpath @rules_sh_import_test_create_shim_test_source//:empty.exe)", 22 | "$(rlocationpath @rules_sh_import_test_create_shim_test_shim//:shimmed.exe)", 23 | "$(rlocationpath @rules_sh_import_test_create_shim_test_shim//:shimmed.shim)", 24 | "$(rlocationpath @rules_sh_import_test_create_shim_test_shim//prefix:another.exe)", 25 | "$(rlocationpath @rules_sh_import_test_create_shim_test_shim//prefix:another.shim)", 26 | ], 27 | ) 28 | 29 | def _create_shim_test_source_repository_impl(repository_ctx): 30 | repository_ctx.file( 31 | "empty.exe", 32 | "", 33 | executable = True, 34 | ) 35 | repository_ctx.file( 36 | "BUILD.bazel", 37 | 'exports_files(["empty.exe"])', 38 | executable = False, 39 | ) 40 | 41 | _create_shim_test_source_repository = repository_rule( 42 | _create_shim_test_source_repository_impl, 43 | ) 44 | 45 | def _create_shim_test_shim_repository_impl(repository_ctx): 46 | create_shim( 47 | repository_ctx, 48 | name = "shimmed", 49 | target = repository_ctx.attr.empty_exe, 50 | ) 51 | create_shim( 52 | repository_ctx, 53 | name = "prefix/another", 54 | target = repository_ctx.attr.empty_exe, 55 | ) 56 | repository_ctx.file( 57 | "BUILD.bazel", 58 | 'exports_files(["shimmed.exe", "shimmed.shim"])', 59 | executable = False, 60 | ) 61 | repository_ctx.file( 62 | "prefix/BUILD.bazel", 63 | 'exports_files(["another.exe", "another.shim"])', 64 | executable = False, 65 | ) 66 | 67 | _create_shim_test_shim_repository = repository_rule( 68 | _create_shim_test_shim_repository_impl, 69 | attrs = { 70 | "empty_exe": attr.label(mandatory = True), 71 | }, 72 | ) 73 | 74 | def _create_shim_repositories(): 75 | _create_shim_test_source_repository( 76 | name = "rules_sh_import_test_create_shim_test_source", 77 | ) 78 | _create_shim_test_shim_repository( 79 | name = "rules_sh_import_test_create_shim_test_shim", 80 | empty_exe = "@rules_sh_import_test_create_shim_test_source//:empty.exe", 81 | ) 82 | 83 | # invoke shim ######################################################## 84 | 85 | def _invoke_shim_test(): 86 | native.genrule( 87 | name = "invoke_shim", 88 | outs = ["invoke_shim.out"], 89 | cmd = """\ 90 | PATH="$(_TOOLS_PATH):$$PATH" 91 | powershell -Command 'echo "Hello World" | Out-File -FilePath $(OUTS)' 92 | """, 93 | toolchains = ["@rules_sh_import_test_invoke_shim_test_powershell//:tools"], 94 | tags = ["manual"], 95 | target_compatible_with = ["@platforms//os:windows"], 96 | ) 97 | native.sh_test( 98 | name = "invoke_shim_test", 99 | srcs = ["invoke_shim_test.sh"], 100 | deps = ["@bazel_tools//tools/bash/runfiles"], 101 | data = [":invoke_shim"], 102 | ) 103 | 104 | def _invoke_shim_test_powershell_impl(repository_ctx): 105 | cpu_value = get_cpu_value(repository_ctx) 106 | if cpu_value.find("windows") != -1: 107 | powershell = repository_ctx.which("powershell") 108 | if powershell == None: 109 | fail("Could not find powershell") 110 | create_shim( 111 | repository_ctx, 112 | name = "powershell", 113 | target = powershell, 114 | ) 115 | repository_ctx.file( 116 | "BUILD.bazel", 117 | """\ 118 | load("@rules_sh//sh:sh.bzl", "sh_binaries") 119 | sh_binaries( 120 | name = "tools", 121 | srcs = select({ 122 | "@platforms//os:windows": ["powershell.exe"], 123 | "//conditions:default": [], 124 | }), 125 | data = select({ 126 | "@platforms//os:windows": ["powershell.shim"], 127 | "//conditions:default": [], 128 | }), 129 | visibility = ["//visibility:public"], 130 | ) 131 | """, 132 | executable = True, 133 | ) 134 | 135 | _invoke_shim_test_powershell = repository_rule( 136 | _invoke_shim_test_powershell_impl, 137 | ) 138 | 139 | def _invoke_shim_repositories(): 140 | _invoke_shim_test_powershell( 141 | name = "rules_sh_import_test_invoke_shim_test_powershell", 142 | ) 143 | 144 | # test suite ######################################################### 145 | 146 | def import_test_suite(name): 147 | _create_shim_test() 148 | _invoke_shim_test() 149 | 150 | native.test_suite( 151 | name = name, 152 | tests = [ 153 | ":create_shim_test", 154 | ":invoke_shim_test", 155 | ], 156 | ) 157 | 158 | # external workspaces ################################################ 159 | 160 | def import_test_repositories(): 161 | _create_shim_repositories() 162 | _invoke_shim_repositories() 163 | -------------------------------------------------------------------------------- /sh/experimental/posix_hermetic.bzl: -------------------------------------------------------------------------------- 1 | """Unix shell commands toolchain support. 2 | 3 | Defines a hermetic version of the POSIX toolchain defined in 4 | @rules_sh//sh:posix.bzl. Long-term this hermetic version will replace the old, 5 | less hermetic version of the POSIX toolchain. 6 | """ 7 | 8 | load("@bazel_skylib//lib:paths.bzl", "paths") 9 | load("//sh/private:posix.bzl", _commands = "commands") 10 | load("//sh:posix.bzl", "MAKE_VARIABLES", "TOOLCHAIN_TYPE") 11 | load("//sh:sh.bzl", "ShBinariesInfo") 12 | 13 | def _sh_posix_hermetic_toolchain_impl(ctx): 14 | if not ShBinariesInfo in ctx.attr.cmds: 15 | fail("The cmds attribute must be given a sh_binaries target.") 16 | 17 | sh_binaries_info = ctx.attr.cmds[ShBinariesInfo] 18 | 19 | unrecognizeds = [ 20 | cmd 21 | for cmd in sh_binaries_info.executables.keys() 22 | if cmd not in _commands 23 | ] 24 | if unrecognizeds: 25 | fail("Unrecognized commands in keys of sh_posix_hermetic_toolchain's \"cmds\" attribute: {}. See posix_hermetic.commands in @rules_sh//sh/experimental:posix_hermetic.bzl for the list of recognized commands.".format(", ".join(unrecognizeds))) 26 | 27 | return [platform_common.ToolchainInfo( 28 | sh_binaries_info = sh_binaries_info, 29 | # exposed for use-cases requiring runfiles access. 30 | tool = ctx.attr.cmds, 31 | # exposed for backwards compatibility. 32 | commands = { 33 | cmd: sh_binaries_info.executables[cmd].path if cmd in sh_binaries_info.executables else None 34 | for cmd in _commands 35 | }, 36 | paths = sh_binaries_info.paths.to_list(), 37 | )] 38 | 39 | sh_posix_hermetic_toolchain = rule( 40 | attrs = { 41 | "cmds": attr.label( 42 | doc = "sh_binaries target that captures the tools to include in this toolchain.", 43 | mandatory = True, 44 | ), 45 | }, 46 | implementation = _sh_posix_hermetic_toolchain_impl, 47 | # TODO[AH] Add a reference to repository rules that generate this toolchain. 48 | doc = """\ 49 | Defines a POSIX toolchain based on an sh_binaries bundle. 50 | 51 | Provides: 52 | sh_binaries_info: ShBinariesInfo, for use in custom rules, see `sh_binaries`. 53 | tool: Target, the sh_binaries target for use in custom rules that requires runfiles, see `sh_binaries`. 54 | commands: Dict of String, an item per command holding the path to the executable or None. Provided for backwards compatibility with `sh_posix_toolchain`. 55 | paths: List of String, deduplicated bindir paths. Suitable for generating `$PATH`. Provided for backwards compatibility with `sh_posix_toolchain`. 56 | 57 | #### Example 58 | 59 | *Using the toolchain in a genrule* 60 | 61 | You need to add a dependency on the `@rules_sh//sh/posix:make_variables` target 62 | to the `toolchains` attribute to import the toolchain into a `genrule`. You can 63 | then use specific tools through make-variables of the form `POSIX_`, or 64 | by extending the `PATH` variable with `$(_POSIX_PATH)`. For example: 65 | 66 | ```bzl 67 | genrule( 68 | name = "a-genrule", 69 | toolchains = ["@rules_sh//sh/posix:make_variables"], 70 | cmd = "\\n".join([ 71 | "FILE=$(execpath :some-input-file.bzl)", 72 | "$(POSIX_GREP) some-pattern $$FILE > $(OUTS)", 73 | "PATH=$(_POSIX_PATH)", 74 | "grep some-pattern $$FILE >> $(OUTS)", 75 | ]), 76 | srcs = [":some-input-file"], 77 | outs = ["some-output-file"], 78 | ) 79 | ``` 80 | 81 | *Using the toolchain in a custom rule* 82 | 83 | You need to add a dependency on the `@rules_sh//sh/posix:toolchain_type` to 84 | your custom rule. For example: 85 | 86 | ```bzl 87 | custom_rule = rule( 88 | _custom_rule_impl, 89 | toolchains = ["//sh/posix:toolchain_type"], 90 | ... 91 | ) 92 | ``` 93 | 94 | You can then access the `ShBinariesInfo` provider through the 95 | `sh_binaries_info` attribute of the toolchain object. See `sh_binaries` for 96 | details on the use of that provider. The example below illustrates how to 97 | access the `grep` tool directly, or how to expose it through the `PATH` 98 | environment variable. 99 | 100 | ```bzl 101 | load("@rules_sh//sh:sh.bzl", "ShBinariesInfo") 102 | 103 | def _custom_rule_impl(ctx): 104 | toolchain = ctx.toolchains["//sh/posix:toolchain_type"] 105 | sh_binaries_info = toolchain.sh_binaries_info 106 | 107 | ctx.actions.run( 108 | executable = sh_binaries_info.executables["grep"], 109 | ... 110 | ) 111 | 112 | ctx.actions.run_shell( 113 | command = "grep ...", 114 | tools = [tools.executables["grep"]], 115 | env = {"PATH": ":".join(sh_binaries_info.paths.to_list())}, 116 | ... 117 | ) 118 | ``` 119 | 120 | Note that the `grep` tool still needs to be declared as an explicit dependency 121 | of the action in the `PATH` use-case using the `tools` parameter. 122 | 123 | The toolchain also exposes the attributes `commands` and `paths` exposed by the 124 | non-hermetic POSIX toolchain for backwards compatibility. Note, however, that 125 | actions still need to explicitly declare the relevant tool files as inputs 126 | explicitly. 127 | 128 | *Caveat: Using Binaries that require Runfiles* 129 | 130 | If the tools bundled by the hermetic POSIX toolchains require Bazel runfiles 131 | access then the same caveats as for `sh_binaries` apply. 132 | 133 | For example, use in a `genrule` looks like this: 134 | 135 | ```bzl 136 | genrule( 137 | name = "runfiles-genrule", 138 | toolchains = ["@rules_sh//sh/posix:make_variables"], 139 | cmd = "\\n".join([ 140 | # The explicit RUNFILES_DIR/RUNFILES_MANIFEST_FILE is a workaround for 141 | # https://github.com/bazelbuild/bazel/issues/15486 142 | "RUNFILES_DIR=$(execpath @rules_sh//sh/posix:make_variables).runfiles", 143 | "RUNFILES_MANIFEST_FILE=$(execpath @rules_sh//sh/posix:make_variables).runfiles_manifest", 144 | "FILE=$(execpath :some-input-file.bzl)", 145 | "$(POSIX_GREP) some-pattern $$FILE > $(OUTS)", 146 | "PATH=$(_POSIX_PATH)", 147 | "grep some-pattern $$FILE >> $(OUTS)", 148 | ]), 149 | srcs = [":some-input-file"], 150 | outs = ["some-output-file"], 151 | ) 152 | ``` 153 | 154 | And use in a custom rule looks like this: 155 | 156 | ```bzl 157 | def _custom_rule_impl(ctx): 158 | toolchain = ctx.toolchains["//sh/posix:toolchain_type"] 159 | sh_binaries_info = toolchain.sh_binaries_info 160 | 161 | # The explicit RUNFILES_DIR/RUNFILES_MANIFEST_FILE is a workaround for 162 | # https://github.com/bazelbuild/bazel/issues/15486 163 | tools_env = { 164 | "RUNFILES_DIR": toolchain.tool[DefaultInfo].files_to_run.runfiles_manifest.dirname, 165 | "RUNFILES_MANIFEST_FILE": toolchain.tool[DefaultInfo].files_to_run.runfiles_manifest.path, 166 | } 167 | 168 | ctx.actions.run( 169 | executable = sh_binaries_info.files_to_run["grep"], 170 | env = tools_env, # Pass the environment into the action. 171 | ... 172 | ) 173 | ``` 174 | 175 | *Defining a toolchain* 176 | 177 | A hermetic POSIX toolchain can be defined by capturing an `sh_binaries` bundle 178 | that provides the required tools. For example: 179 | 180 | ```bzl 181 | sh_binaries( 182 | name = "commands", 183 | srcs = [ 184 | ... 185 | ], 186 | ) 187 | 188 | sh_posix_hermetic_toolchain( 189 | name = "posix", 190 | cmds = ":commands", 191 | ) 192 | 193 | toolchain( 194 | name = "posix_toolchain", 195 | toolchain = ":posix", 196 | toolchain_type = "@rules_sh//sh/posix:toolchain_type", 197 | exec_compatible_with = [ 198 | ... 199 | ], 200 | target_compatible_with = [ 201 | ... 202 | ], 203 | ) 204 | ``` 205 | """, 206 | ) 207 | 208 | posix_hermetic = struct( 209 | commands = _commands, 210 | TOOLCHAIN_TYPE = TOOLCHAIN_TYPE, 211 | MAKE_VARIABLES = MAKE_VARIABLES, 212 | ) 213 | -------------------------------------------------------------------------------- /sh/posix.bzl: -------------------------------------------------------------------------------- 1 | """Unix shell commands toolchain support. 2 | 3 | Defines a toolchain capturing common Unix shell commands as defined by IEEE 4 | 1003.1-2008 (POSIX), see `sh_posix_toolchain`, and `sh_posix_configure` to scan 5 | the local environment for shell commands. The list of known commands is 6 | available in `posix.commands`. 7 | 8 | """ 9 | 10 | load("@bazel_skylib//lib:paths.bzl", "paths") 11 | load("//sh/private:get_cpu_value.bzl", "get_cpu_value") 12 | load( 13 | "//sh/private:defs.bzl", 14 | "mk_default_info_with_files_to_run", 15 | "mk_template_variable_info", 16 | ) 17 | load("//sh/private:posix.bzl", _commands = "commands") 18 | 19 | TOOLCHAIN_TYPE = "@rules_sh//sh/posix:toolchain_type" 20 | MAKE_VARIABLES = "@rules_sh//sh/posix:make_variables" 21 | 22 | def _sh_posix_toolchain_impl(ctx): 23 | commands = {} 24 | cmds = ctx.attr.cmds 25 | for cmd in _commands: 26 | cmd_path = cmds.get(cmd, None) 27 | if not cmd_path: 28 | cmd_path = None 29 | commands[cmd] = cmd_path 30 | unrecognizeds = [cmd for cmd in cmds.keys() if cmd not in _commands] 31 | if unrecognizeds: 32 | fail("Unrecognized commands in keys of sh_posix_toolchain's \"cmds\" attributes: {}. See posix.commands in @rules_sh//sh:posix.bzl for the list of recognized commands.".format(", ".join(unrecognizeds))) 33 | cmd_paths = { 34 | paths.dirname(cmd_path): None 35 | for cmd_path in commands.values() 36 | if cmd_path 37 | }.keys() 38 | return [platform_common.ToolchainInfo( 39 | commands = commands, 40 | paths = cmd_paths, 41 | )] 42 | 43 | sh_posix_toolchain = rule( 44 | attrs = { 45 | "cmds": attr.string_dict( 46 | doc = "dict where keys are command names and values are paths", 47 | mandatory = True, 48 | ), 49 | }, 50 | doc = """ 51 | A toolchain capturing standard Unix shell commands. 52 | 53 | Provides: 54 | commands: Dict of String, an item per command holding the path to the executable or None. 55 | paths: List of String, deduplicated bindir paths. Suitable for generating `$PATH`. 56 | 57 | Use `sh_posix_configure` to create an instance of this toolchain. 58 | See `sh_posix_make_variables` on how to use this toolchain in genrules. 59 | """, 60 | implementation = _sh_posix_toolchain_impl, 61 | ) 62 | 63 | def _sh_posix_make_variables_impl(ctx): 64 | toolchain = ctx.toolchains[TOOLCHAIN_TYPE] 65 | 66 | if not hasattr(toolchain, "sh_binaries_info"): 67 | cmd_vars = { 68 | "POSIX_%s" % cmd.upper(): cmd_path 69 | for cmd in _commands 70 | for cmd_path in [toolchain.commands[cmd]] 71 | if cmd_path 72 | } 73 | return [platform_common.TemplateVariableInfo(cmd_vars)] 74 | 75 | template_variable_info = mk_template_variable_info( 76 | "posix", 77 | toolchain.sh_binaries_info, 78 | ) 79 | 80 | default_info = mk_default_info_with_files_to_run( 81 | ctx, 82 | ctx.label.name, 83 | toolchain.tool[DefaultInfo].files, 84 | toolchain.tool[DefaultInfo].default_runfiles, 85 | ) 86 | 87 | return [template_variable_info, default_info] 88 | 89 | sh_posix_make_variables = rule( 90 | doc = """ 91 | Provides POSIX toolchain commands as custom make variables. 92 | 93 | Make variables: 94 | Provides a make variable of the form `POSIX_` for each available 95 | command, where `` is the name of the command in upper case. 96 | 97 | Use `posix.MAKE_VARIABLES` instead of instantiating this rule yourself. 98 | 99 | Example: 100 | >>> genrule( 101 | name = "use-grep", 102 | srcs = [":some-file"], 103 | outs = ["grep-out"], 104 | cmd = "$(POSIX_GREP) search $(execpath :some-file) > $(OUTS)", 105 | toolchains = [posix.MAKE_VARIABLES], 106 | ) 107 | """, 108 | implementation = _sh_posix_make_variables_impl, 109 | toolchains = [TOOLCHAIN_TYPE], 110 | ) 111 | 112 | def _windows_detect_sh_dir(repository_ctx): 113 | # Taken and adapted from @bazel_tools//tools/sh/sh_configure.bzl. 114 | sh_path = repository_ctx.os.environ.get("BAZEL_SH") 115 | if not sh_path: 116 | sh_path = repository_ctx.which("bash.exe") 117 | if sh_path: 118 | # repository_ctx.which returns a path object, convert that to 119 | # string so we can call string.startswith on it. 120 | sh_path = str(sh_path) 121 | 122 | # When the Windows Subsystem for Linux is installed there's a 123 | # bash.exe under %WINDIR%\system32\bash.exe that launches Ubuntu 124 | # Bash which cannot run native Windows programs so it's not what 125 | # we want. 126 | windir = repository_ctx.os.environ.get("WINDIR") 127 | if windir and sh_path.startswith(windir): 128 | sh_path = None 129 | 130 | if sh_path != None: 131 | sh_dir = str(repository_ctx.path(sh_path).dirname) 132 | 133 | return sh_dir 134 | 135 | def _sh_posix_config_impl(repository_ctx): 136 | cpu = get_cpu_value(repository_ctx) 137 | env = repository_ctx.os.environ 138 | 139 | windows_sh_dir = None 140 | if cpu == "x64_windows": 141 | windows_sh_dir = _windows_detect_sh_dir(repository_ctx) 142 | 143 | commands = {} 144 | for cmd in _commands: 145 | cmd_path = env.get("POSIX_%s" % cmd.upper(), None) 146 | if cmd_path == None and cpu != "x64_windows": 147 | cmd_path = repository_ctx.which(cmd) 148 | elif cmd_path == None and cpu == "x64_windows": 149 | # Autodetection using `repository_ctx.which` is not safe on 150 | # Windows, as it may turn up false friends. E.g. Windows has a 151 | # `find.exe` which is unrelated to POSIX `find`. Instead we use 152 | # tools next to `bash.exe`. 153 | cmd_path = repository_ctx.path(windows_sh_dir + "/" + cmd + ".exe") 154 | if not cmd_path.exists: 155 | cmd_path = None 156 | commands[cmd] = cmd_path 157 | repository_ctx.file("BUILD.bazel", executable = False, content = """ 158 | load("@rules_sh//sh:posix.bzl", "sh_posix_toolchain") 159 | sh_posix_toolchain( 160 | name = "local_posix", 161 | visibility = ["//visibility:public"], 162 | cmds = {{ 163 | {commands} 164 | }} 165 | ) 166 | toolchain( 167 | name = "local_posix_toolchain", 168 | toolchain = ":local_posix", 169 | toolchain_type = "{toolchain_type}", 170 | exec_compatible_with = [ 171 | "@platforms//cpu:{arch}", 172 | "@platforms//os:{os}", 173 | ], 174 | ) 175 | """.format( 176 | commands = ",\n ".join([ 177 | '"{cmd}": "{path}"'.format(cmd = cmd, path = cmd_path) 178 | for (cmd, cmd_path) in commands.items() 179 | if cmd_path 180 | ]), 181 | arch = { 182 | "aarch64": "arm64", 183 | "arm64_windows": "arm64", 184 | "darwin_arm64": "arm64", 185 | }.get(cpu, "x86_64"), 186 | os = { 187 | "arm64_windows": "windows", 188 | "darwin": "osx", 189 | "darwin_arm64": "osx", 190 | "darwin_x86_64": "osx", 191 | "x64_windows": "windows", 192 | }.get(cpu, "linux"), 193 | toolchain_type = TOOLCHAIN_TYPE, 194 | )) 195 | 196 | _sh_posix_config = repository_rule( 197 | configure = True, 198 | environ = ["PATH"] + [ 199 | "POSIX_%s" % cmd.upper() 200 | for cmd in _commands 201 | ], 202 | local = True, 203 | implementation = _sh_posix_config_impl, 204 | ) 205 | 206 | def sh_posix_configure(name = "local_posix_config", register = True): 207 | """## Usage 208 | 209 | ### Configure the toolchain 210 | 211 | Add the following to your `WORKSPACE` file to configure a local POSIX toolchain. 212 | 213 | ``` python 214 | load("@rules_sh//sh:posix.bzl", "sh_posix_configure") 215 | sh_posix_configure() 216 | ``` 217 | 218 | Bazel will query `PATH` for common Unix shell commands. You can override the 219 | path to individual commands with environment variables of the form 220 | `POSIX_`. E.g. `POSIX_MAKE=/usr/bin/gmake`. 221 | 222 | Note, this introduces an inhermeticity to the build as the contents of `PATH` 223 | may be specific to your machine's setup. 224 | 225 | Refer to [`rules_nixpkgs`'s][rules_nixpkgs] `nixpkgs_posix_configure` for a 226 | hermetic alternative. 227 | 228 | [rules_nixpkgs]: https://github.com/tweag/rules_nixpkgs.git 229 | 230 | ### Use Unix tools in `genrule`s 231 | 232 | The POSIX toolchain exposes custom make variables of the form 233 | `POSIX_` for discovered commands. Use these as follows: 234 | 235 | ``` python 236 | genrule( 237 | name = "example", 238 | srcs = [":some-input-file"], 239 | outs = ["some-output-file"], 240 | cmd = "$(POSIX_GREP) some-pattern $(execpath :some-input-file.bzl) > $(OUTS)", 241 | toolchains = ["@rules_sh//sh/posix:make_variables"], 242 | ) 243 | ``` 244 | 245 | See `posix.commands` defined in `@rules_sh//sh/posix.bzl` for the list of known 246 | POSIX commands. 247 | 248 | ### Use Unix tools in custom rules 249 | 250 | The POSIX toolchain provides two attributes: 251 | - `commands`: A `dict` mapping names of commands to their paths. 252 | - `paths`: A deduplicated list of bindir paths suitable for generating `$PATH`. 253 | 254 | ``` python 255 | def _my_rule_impl(ctx): 256 | posix_info = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"] 257 | ctx.actions.run( 258 | executable = posix_info.commands["grep"], 259 | ... 260 | ) 261 | ctx.actions.run_shell( 262 | command = "grep ...", 263 | env = {"PATH": ":".join(posix_info.paths)}, 264 | ... 265 | ) 266 | 267 | my_rule = rule( 268 | _my_rule_impl, 269 | toolchains = ["@rules_sh//sh/posix:toolchain_type"], 270 | ... 271 | ) 272 | ``` 273 | """ 274 | _sh_posix_config(name = name) 275 | if register: 276 | native.register_toolchains("@{}//:local_posix_toolchain".format(name)) 277 | 278 | posix = struct( 279 | commands = _commands, 280 | TOOLCHAIN_TYPE = TOOLCHAIN_TYPE, 281 | MAKE_VARIABLES = MAKE_VARIABLES, 282 | ) 283 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /sh/sh.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//lib:dicts.bzl", "dicts") 2 | load("@bazel_skylib//lib:paths.bzl", "paths") 3 | load( 4 | "@rules_sh//sh/private:defs.bzl", 5 | "ConstantInfo", 6 | "mk_default_info_with_files_to_run", 7 | "mk_template_variable_info", 8 | ) 9 | 10 | ShBinariesInfo = provider( 11 | doc = "The description of a sh_binaries target.", 12 | fields = { 13 | "executables": "dict of File, The executables included in the bundle by name.", 14 | "files_to_run": "dict of FilesToRunProvider, to be passed to ctx.action.run / ctx.action.run_shell.", 15 | "paths": "depset of string, The directories under which the binaries can be found.", 16 | }, 17 | ) 18 | 19 | _WINDOWS_EXE_EXTENSIONS = [".exe", ".cmd", ".bat", ".ps1"] 20 | 21 | def _sh_binaries_from_srcs(ctx, srcs, is_windows): 22 | executable_files = [] 23 | runfiles = ctx.runfiles() 24 | executables_dict = dict() 25 | files_to_run_dict = dict() 26 | executable_paths = [] 27 | 28 | for src in srcs: 29 | if src[DefaultInfo].files_to_run == None or src[DefaultInfo].files_to_run.executable == None: 30 | fail("srcs must be executable, but '{}' is not.".format(src.label)) 31 | 32 | files_to_run = src[DefaultInfo].files_to_run 33 | executable = files_to_run.executable 34 | name = executable.basename 35 | if is_windows: 36 | (noext, ext) = paths.split_extension(executable.basename) 37 | if ext in _WINDOWS_EXE_EXTENSIONS: 38 | name = noext 39 | 40 | if name in executables_dict: 41 | fail("name collision on '{}' between '{}' and '{}' in srcs.".format( 42 | name, 43 | executables_dict[name].owner, 44 | src.label, 45 | )) 46 | 47 | executable_files.append(executable) 48 | runfiles = runfiles.merge(src[DefaultInfo].default_runfiles) 49 | executables_dict[name] = executable 50 | files_to_run_dict[name] = files_to_run 51 | executable_paths.append(executable.dirname) 52 | 53 | return struct( 54 | executable_files = executable_files, 55 | runfiles = runfiles, 56 | executables_dict = executables_dict, 57 | executable_paths = executable_paths, 58 | files_to_run_dict = files_to_run_dict, 59 | ) 60 | 61 | def _sh_binaries_from_deps(ctx, deps): 62 | executable_files = [] 63 | runfiles = ctx.runfiles() 64 | files_to_run_dict = dict() 65 | executables_dict = dict() 66 | executable_paths = [] 67 | 68 | for dep in deps: 69 | if not ShBinariesInfo in dep: 70 | fail("deps must be sh_binaries targets, but '{}' is not.".format(dep.label)) 71 | 72 | executable_files.append(dep[DefaultInfo].files) 73 | runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) 74 | executables_dict.update(dep[ShBinariesInfo].executables) 75 | files_to_run_dict.update(dep[ShBinariesInfo].files_to_run) 76 | executable_paths.append(dep[ShBinariesInfo].paths) 77 | 78 | return struct( 79 | executable_files = executable_files, 80 | runfiles = runfiles, 81 | executables_dict = executables_dict, 82 | executable_paths = executable_paths, 83 | files_to_run_dict = files_to_run_dict, 84 | ) 85 | 86 | def _runfiles_from_data(ctx, data): 87 | runfiles = ctx.runfiles() 88 | 89 | for data in data: 90 | runfiles = runfiles.merge(ctx.runfiles(transitive_files = data[DefaultInfo].files)) 91 | runfiles = runfiles.merge(data[DefaultInfo].default_runfiles) 92 | 93 | return runfiles 94 | 95 | def _mk_sh_binaries_info(direct, transitive): 96 | return ShBinariesInfo( 97 | executables = dicts.add( 98 | # The order is important so that srcs take precedence over deps on collision. 99 | transitive.executables_dict, 100 | direct.executables_dict, 101 | ), 102 | files_to_run = dicts.add( 103 | # The order is important so that srcs take precedence over deps on collision. 104 | transitive.files_to_run_dict, 105 | direct.files_to_run_dict, 106 | ), 107 | paths = depset( 108 | direct = direct.executable_paths, 109 | # Reverse the order so that later deps take precedence. 110 | transitive = transitive.executable_paths[::-1], 111 | order = "preorder", 112 | ), 113 | ) 114 | 115 | def _sh_binaries_impl(ctx): 116 | is_windows = ctx.attr._is_windows[ConstantInfo].value 117 | direct = _sh_binaries_from_srcs(ctx, ctx.attr.srcs, is_windows) 118 | transitive = _sh_binaries_from_deps(ctx, ctx.attr.deps) 119 | data_runfiles = _runfiles_from_data(ctx, ctx.attr.data) 120 | 121 | sh_binaries_info = _mk_sh_binaries_info(direct, transitive) 122 | template_variable_info = mk_template_variable_info(ctx.label.name, sh_binaries_info) 123 | default_info = mk_default_info_with_files_to_run( 124 | ctx, 125 | ctx.label.name, 126 | depset(direct = direct.executable_files, transitive = transitive.executable_files), 127 | direct.runfiles.merge(transitive.runfiles).merge(data_runfiles), 128 | ) 129 | 130 | return [sh_binaries_info, template_variable_info, default_info] 131 | 132 | sh_binaries = rule( 133 | _sh_binaries_impl, 134 | attrs = { 135 | "srcs": attr.label_list( 136 | allow_files = True, 137 | doc = "The executables to include in the bundle.", 138 | ), 139 | "deps": attr.label_list( 140 | doc = "Existing binary bundles to merge into this bundle. In case of collision, later deps take precedence over earlier ones, and srcs take precedence over deps.", 141 | ), 142 | "data": attr.label_list( 143 | allow_files = True, 144 | doc = "Additional runtime dependencies needed by any of the bundled binaries.", 145 | ), 146 | "_is_windows": attr.label( 147 | default = "@rules_sh//sh/private:is_windows", 148 | ), 149 | }, 150 | executable = True, 151 | doc = """\ 152 | Defines a bundle of binaries usable as build tools in a genrule or a custom rule. 153 | 154 | Exposes the bundled tools as make variables of the form 155 | `$(BUNDLE_NAME_BINARY_NAME)`, and exposes a make variable of the form 156 | `$(_BUNDLE_NAME_PATH)` to extend `PATH` with all bundled binaries. 157 | 158 | Also exposes the bundled tools and a list of `PATH` items through the Starlark 159 | provider `ShBinariesInfo`. 160 | 161 | #### Example 162 | 163 | *Defining a Bundle* 164 | 165 | Create a binary bundle target as follows: 166 | 167 | ```bzl 168 | sh_binaries( 169 | name = "a-bundle", 170 | srcs = [ 171 | "binary-a", 172 | "binary-b", 173 | ], 174 | ) 175 | ``` 176 | 177 | *Using a Bundle in a Genrule* 178 | 179 | Use the binaries in the bundle in a `genrule` by adding it to the `toolchains` 180 | attribute and use the generated make variables to access the bundled binaries 181 | as follows: 182 | 183 | ```bzl 184 | genrule( 185 | name = "a-genrule", 186 | toolchains = [":a-bundle"], 187 | cmd = "\\n".join([ 188 | "$(A_BUNDLE_BINARY_A) -foo bar", # Invoke binary-a 189 | "$(A_BUNDLE_BINARY_B) -baz qux", # Invoke binary-b 190 | "PATH=$(_A_BUNDLE_PATH):$$PATH", # Extend PATH to include both binaries 191 | "binary-a; binary-b", # Invoke binary-a and binary-b 192 | ]), 193 | outs = [...], 194 | ) 195 | ``` 196 | 197 | *Merging Bundles* 198 | 199 | Merge existing bundles into other bundles using the `deps` attribute as follows: 200 | 201 | ```bzl 202 | sh_binaries( 203 | name = "another-bundle", 204 | srcs = ["binary-c"], 205 | deps = [":a-bundle"], 206 | ) 207 | ``` 208 | 209 | The make variable prefix will be determined by the `sh_binaries` target that 210 | you depend on. E.g. with the above `another-bundle` target the make variable 211 | prefix will be `ANOTHER_BUNDLE_`: 212 | 213 | ```bzl 214 | genrule( 215 | name = "another-genrule", 216 | toolchains = [":another-bundle"], 217 | cmd = "\\n".join([ 218 | "$(ANOTHER_BUNDLE_BINARY_A) -foo bar", # Invoke binary-a 219 | "$(ANOTHER_BUNDLE_BINARY_C) -baz qux", # Invoke binary-c 220 | "PATH=$(_ANOTHER_BUNDLE_PATH):$$PATH", # Extend PATH to include both binaries 221 | "binary-a; binary-c", # Invoke binary-a and binary-c 222 | ]), 223 | outs = [...], 224 | ) 225 | ``` 226 | 227 | *Using a Bundle in a Custom Rule* 228 | 229 | Use the binaries in a bundle in a custom rule by adding an attribute to depend 230 | on the bundle as follows, note that it should use the `cfg = "exec"` parameter, 231 | because these tools will be used as build tools: 232 | 233 | ```bzl 234 | # defs.bzl 235 | custom_rule = rule( 236 | _custom_rule_impl, 237 | attrs = { 238 | "tools": attr.label( 239 | cfg = "exec", 240 | ), 241 | }, 242 | ) 243 | 244 | # BUILD.bazel 245 | custom_rule( 246 | name = "custom", 247 | tools = ":a-bundle", 248 | ) 249 | ``` 250 | 251 | Use the `ShBinariesInfo` provider to use the tools in build actions. The 252 | individual tools are exposed in the `executables` field, and the `PATH` list is 253 | exposed in `paths` as follows: 254 | 255 | ```bzl 256 | load("@rules_sh//sh:sh.bzl", "ShBinariesInfo") 257 | 258 | def _custom_rule_impl(ctx): 259 | tools = ctx.attr.tools[ShBinariesInfo] 260 | 261 | # Use binary-a in a `run` action. 262 | ctx.actions.run( 263 | executable = tools.files_to_run["binary-a"], # Invoke binary-a 264 | ... 265 | ) 266 | 267 | # Use binary-a and binary-b in a `run_shell` action. 268 | ctx.actions.run_shell( 269 | command = "{binary_a}; {binary_b}".format( 270 | binary_a = tools.executables["binary-a"].path, # Path to binary-a 271 | binary_b = tools.executables["binary-b"].path, # Path to binary-b 272 | ]), 273 | tools = [ 274 | tools.files_to_run["binary-a"], 275 | tools.files_to_run["binary-b"], 276 | ], 277 | ... 278 | ) 279 | 280 | # Use binary-a and binary-b in a `run_shell` action with `PATH`. 281 | ctx.actions.run_shell( 282 | command = "PATH={path}:$PATH; binary-a; binary-b".format( 283 | path = ":".join(tools.paths.to_list()), 284 | ), 285 | tools = [ 286 | tools.files_to_run["binary-a"], 287 | tools.files_to_run["binary-b"], 288 | ], 289 | ... 290 | ) 291 | ``` 292 | 293 | *Caveat: Using Binaries that require Runfiles* 294 | 295 | Note, support for binaries that require runfiles is limited due to limitations 296 | imposed by Bazel's Starlark API, see [#15486][issue-15486]. In order for these 297 | to work you will need to define the `RUNFILES_DIR` or `RUNFILES_MANIFEST_FILE` 298 | environment variables for the action using tools from the bundle. 299 | 300 | (Use `RUNFILES_MANIFEST_FILE` if your operating system and configuration does 301 | not support a runfiles tree and instead only provides a runfiles manifest file, 302 | as is commonly the case on Windows.) 303 | 304 | You can achieve this in a `genrule` as follows: 305 | 306 | ```bzl 307 | genrule( 308 | name = "runfiles-genrule", 309 | toolchains = [":a-bundle"], 310 | cmd = "\\n".join([ 311 | # The explicit RUNFILES_DIR/RUNFILES_MANIFEST_FILE is a workaround for 312 | # https://github.com/bazelbuild/bazel/issues/15486 313 | "RUNFILES_DIR=$(execpath :a-bundle).runfiles", 314 | "RUNFILES_MANIFEST_FILE=$(execpath :a-bundle).runfiles_manifest", 315 | "$(A_BUNDLE_BINARY_A)", 316 | "$(A_BUNDLE_BINARY_B)", 317 | ]), 318 | outs = [...], 319 | ) 320 | ``` 321 | 322 | And in a custom rule as follows: 323 | 324 | ```bzl 325 | def _custom_rule_impl(ctx): 326 | tools = ctx.attr.tools[ShBinariesInfo] 327 | # The explicit RUNFILES_DIR/RUNFILES_MANIFEST_FILE is a workaround for 328 | # https://github.com/bazelbuild/bazel/issues/15486 329 | tools_env = { 330 | "RUNFILES_DIR": ctx.attr.tools[DefaultInfo].files_to_run.runfiles_manifest.dirname, 331 | "RUNFILES_MANIFEST_FILE": ctx.attr.tools[DefaultInfo].files_to_run.runfiles_manifest.path, 332 | } 333 | 334 | ctx.actions.run( 335 | executable = tools.files_to_run["binary-a"], 336 | env = tools_env, # Pass the environment into the action. 337 | ... 338 | ) 339 | ``` 340 | 341 | [issue-15486]: https://github.com/bazelbuild/bazel/issues/15486 342 | """, 343 | ) 344 | -------------------------------------------------------------------------------- /tests/posix_hermetic/posix_hermetic_test.bzl: -------------------------------------------------------------------------------- 1 | load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") 2 | load( 3 | "@rules_sh//sh:sh.bzl", 4 | "ShBinariesInfo", 5 | "sh_binaries", 6 | ) 7 | load( 8 | "@rules_sh//sh/experimental:posix_hermetic.bzl", 9 | "posix_hermetic", 10 | "sh_posix_hermetic_toolchain", 11 | ) 12 | 13 | # empty toolchain #################################################### 14 | 15 | def _empty_toolchain(): 16 | sh_binaries( 17 | name = "binaries-empty", 18 | srcs = [], 19 | ) 20 | sh_posix_hermetic_toolchain( 21 | name = "posix-toolchain-empty", 22 | cmds = ":binaries-empty", 23 | ) 24 | 25 | def _empty_toolchain_test_impl(ctx): 26 | env = analysistest.begin(ctx) 27 | 28 | toolchain = analysistest.target_under_test(env)[platform_common.ToolchainInfo] 29 | 30 | # -------------------------------------------- 31 | # sh_binaries attribute 32 | 33 | asserts.true( 34 | env, 35 | hasattr(toolchain, "sh_binaries_info"), 36 | "Expected 'sh_binaries_info' attribute", 37 | ) 38 | asserts.equals( 39 | env, 40 | toolchain.sh_binaries_info.executables, 41 | {}, 42 | ) 43 | asserts.equals( 44 | env, 45 | toolchain.sh_binaries_info.paths.to_list(), 46 | [], 47 | ) 48 | 49 | # -------------------------------------------- 50 | # Backwards compatible attributes 51 | 52 | asserts.true( 53 | env, 54 | hasattr(toolchain, "commands"), 55 | "Expected 'commands' attribute for backwards compatibility", 56 | ) 57 | asserts.equals( 58 | env, 59 | {cmd: None for cmd in posix_hermetic.commands}, 60 | getattr(toolchain, "commands", None), 61 | "Malformed backwards-compatible 'commands' attribute", 62 | ) 63 | asserts.true( 64 | env, 65 | hasattr(toolchain, "paths"), 66 | "Expected 'paths' attribute for backwards compatibility", 67 | ) 68 | asserts.equals( 69 | env, 70 | getattr(toolchain, "paths", None), 71 | [], 72 | "Malformed backwards-compatible 'paths' attribute", 73 | ) 74 | 75 | return analysistest.end(env) 76 | 77 | empty_toolchain_test = analysistest.make( 78 | _empty_toolchain_test_impl, 79 | ) 80 | 81 | def _test_empty_toolchain(): 82 | _empty_toolchain() 83 | empty_toolchain_test( 84 | name = "empty_toolchain_test", 85 | target_under_test = ":posix-toolchain-empty", 86 | ) 87 | 88 | # unrecognized tool ################################################## 89 | 90 | def _unrecognized_tool_toolchain(): 91 | native.sh_binary( 92 | name = "unrecognized", 93 | srcs = ["unrecognized.sh"], 94 | ) 95 | sh_binaries( 96 | name = "binaries-unrecognized", 97 | srcs = [":unrecognized"], 98 | ) 99 | sh_posix_hermetic_toolchain( 100 | name = "posix-toolchain-unrecognized", 101 | cmds = ":binaries-unrecognized", 102 | tags = ["manual"], 103 | ) 104 | 105 | def _unrecognized_tool_toolchain_test_impl(ctx): 106 | env = analysistest.begin(ctx) 107 | 108 | asserts.expect_failure( 109 | env, 110 | """Unrecognized commands in keys of sh_posix_hermetic_toolchain's "cmds" attribute: unrecognized""", 111 | ) 112 | 113 | return analysistest.end(env) 114 | 115 | unrecognized_tool_toolchain_test = analysistest.make( 116 | _unrecognized_tool_toolchain_test_impl, 117 | expect_failure = True, 118 | ) 119 | 120 | def _test_unrecognized_tool_toolchain(): 121 | _unrecognized_tool_toolchain() 122 | unrecognized_tool_toolchain_test( 123 | name = "unrecognized_tool_toolchain_test", 124 | target_under_test = ":posix-toolchain-unrecognized", 125 | ) 126 | 127 | # local binaries toolchian ########################################### 128 | 129 | # A toolchain that depends on local files, but has no further runtime 130 | # dependencies, like runfiles resolution. 131 | 132 | def _local_binaries_toolchain(): 133 | native.cc_binary( 134 | name = "false", 135 | srcs = ["false.cc"], 136 | ) 137 | native.cc_binary( 138 | name = "true", 139 | srcs = ["true.cc"], 140 | ) 141 | sh_binaries( 142 | name = "binaries-local-binaries", 143 | srcs = [ 144 | "false", 145 | "true", 146 | ], 147 | ) 148 | sh_posix_hermetic_toolchain( 149 | name = "posix-toolchain-local-binaries", 150 | cmds = ":binaries-local-binaries", 151 | ) 152 | native.toolchain( 153 | name = "toolchain-local-binaries", 154 | toolchain = ":posix-toolchain-local-binaries", 155 | toolchain_type = "@rules_sh//sh/posix:toolchain_type", 156 | ) 157 | 158 | def _local_binaries_toolchain_transition_impl(settings, attr): 159 | return { 160 | "//command_line_option:extra_toolchains": str(Label("//posix_hermetic:toolchain-local-binaries")), 161 | } 162 | 163 | _local_binaries_toolchain_transition = transition( 164 | implementation = _local_binaries_toolchain_transition_impl, 165 | inputs = [], 166 | outputs = ["//command_line_option:extra_toolchains"], 167 | ) 168 | 169 | def _use_local_binaries_toolchain_impl(ctx): 170 | src = ctx.attr.src[DefaultInfo] 171 | return [ 172 | DefaultInfo( 173 | files = src.files, 174 | runfiles = ctx 175 | .runfiles(files = src.files.to_list()) 176 | .merge(src.default_runfiles), 177 | ), 178 | ] 179 | 180 | _use_local_binaries_toolchain = rule( 181 | _use_local_binaries_toolchain_impl, 182 | cfg = _local_binaries_toolchain_transition, 183 | attrs = { 184 | "src": attr.label(), 185 | "_allowlist_function_transition": attr.label( 186 | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 187 | ), 188 | }, 189 | ) 190 | 191 | def _local_binaries_toolchain_test_impl(ctx): 192 | env = analysistest.begin(ctx) 193 | 194 | toolchain = analysistest.target_under_test(env)[platform_common.ToolchainInfo] 195 | 196 | # -------------------------------------------- 197 | # sh_binaries attribute 198 | 199 | asserts.true( 200 | env, 201 | hasattr(toolchain, "sh_binaries_info"), 202 | "Expected 'sh_binaries_info' attribute", 203 | ) 204 | asserts.equals( 205 | env, 206 | len(toolchain.sh_binaries_info.executables), 207 | 2, 208 | ) 209 | asserts.true( 210 | env, 211 | "false" in toolchain.sh_binaries_info.executables, 212 | "Expected 'false' to be a member of the executables dict", 213 | ) 214 | asserts.true( 215 | env, 216 | ctx.executable.false_binary in toolchain.sh_binaries_info.executables.values(), 217 | "Expected 'false' binary to be a member of the executables", 218 | ) 219 | asserts.true( 220 | env, 221 | "true" in toolchain.sh_binaries_info.executables, 222 | "Expected 'true' to be a member of the executables dict", 223 | ) 224 | asserts.true( 225 | env, 226 | ctx.executable.true_binary in toolchain.sh_binaries_info.executables.values(), 227 | "Expected 'true' binary to be a member of the executables", 228 | ) 229 | asserts.equals( 230 | env, 231 | len(toolchain.sh_binaries_info.paths.to_list()), 232 | 1, 233 | ) 234 | asserts.equals( 235 | env, 236 | toolchain.sh_binaries_info.paths.to_list()[0], 237 | ctx.executable.false_binary.dirname, 238 | ) 239 | 240 | # -------------------------------------------- 241 | # Backwards compatible attributes 242 | 243 | asserts.true( 244 | env, 245 | hasattr(toolchain, "commands"), 246 | "Expected 'commands' attribute for backwards compatibility", 247 | ) 248 | asserts.equals( 249 | env, 250 | len(getattr(toolchain, "commands", None)), 251 | len(posix_hermetic.commands), 252 | "Malformed backwards-compatible 'commands' attribute", 253 | ) 254 | asserts.equals( 255 | env, 256 | toolchain.commands["false"], 257 | ctx.executable.false_binary.path, 258 | ) 259 | asserts.equals( 260 | env, 261 | toolchain.commands["true"], 262 | ctx.executable.true_binary.path, 263 | ) 264 | asserts.true( 265 | env, 266 | all([ 267 | toolchain.commands[cmd] == None 268 | for cmd in posix_hermetic.commands 269 | if not cmd in ["false", "true"] 270 | ]), 271 | ) 272 | asserts.true( 273 | env, 274 | hasattr(toolchain, "paths"), 275 | "Expected 'paths' attribute for backwards compatibility", 276 | ) 277 | asserts.equals( 278 | env, 279 | len(getattr(toolchain, "paths", None)), 280 | 1, 281 | "Malformed backwards-compatible 'paths' attribute", 282 | ) 283 | asserts.equals( 284 | env, 285 | toolchain.paths[0], 286 | ctx.executable.false_binary.dirname, 287 | ) 288 | 289 | return analysistest.end(env) 290 | 291 | local_binaries_toolchain_test = analysistest.make( 292 | _local_binaries_toolchain_test_impl, 293 | attrs = { 294 | "false_binary": attr.label( 295 | executable = True, 296 | # TODO[AH] Both the target_under_test and the reference should be 297 | # provided in the exec configuration. 298 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 299 | # cfg = "exec", 300 | cfg = "target", 301 | ), 302 | "true_binary": attr.label( 303 | executable = True, 304 | # TODO[AH] Both the target_under_test and the reference should be 305 | # provided in the exec configuration. 306 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 307 | # cfg = "exec", 308 | cfg = "target", 309 | ), 310 | }, 311 | ) 312 | 313 | def _test_local_binaries_toolchain(): 314 | _local_binaries_toolchain() 315 | local_binaries_toolchain_test( 316 | name = "local_binaries_toolchain_test", 317 | target_under_test = ":posix-toolchain-local-binaries", 318 | false_binary = ":false", 319 | true_binary = ":true", 320 | ) 321 | 322 | # custom rule usage ################################################## 323 | 324 | CustomRuleInfo = provider() 325 | 326 | def _custom_rule_impl(ctx): 327 | toolchain = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"] 328 | 329 | explicit_output = ctx.actions.declare_file("{}_explicit.txt".format(ctx.label.name)) 330 | ctx.actions.run_shell( 331 | outputs = [explicit_output], 332 | tools = [ 333 | toolchain.sh_binaries_info.executables["false"], 334 | toolchain.sh_binaries_info.executables["true"], 335 | ], 336 | arguments = [ 337 | toolchain.sh_binaries_info.executables["false"].path, 338 | toolchain.sh_binaries_info.executables["true"].path, 339 | explicit_output.path, 340 | ], 341 | command = """\ 342 | FALSE="$1" 343 | TRUE="$2" 344 | OUT="$3" 345 | { $FALSE && echo 1 || echo 0; } >>$OUT 346 | { $TRUE && echo 1 || echo 0; } >>$OUT 347 | """, 348 | ) 349 | 350 | path_output = ctx.actions.declare_file("{}_path.txt".format(ctx.label.name)) 351 | ctx.actions.run_shell( 352 | outputs = [path_output], 353 | tools = [ 354 | toolchain.sh_binaries_info.executables["false"], 355 | toolchain.sh_binaries_info.executables["true"], 356 | ], 357 | arguments = [ 358 | ":".join(toolchain.sh_binaries_info.paths.to_list()), 359 | path_output.path, 360 | ], 361 | command = """\ 362 | # Disable the shell builtins and standard PATH to ensure 363 | # that we use the commands provided by the toolchain. 364 | enable -n false true 365 | export PATH="$1" 366 | OUT="$2" 367 | { false && echo 1 || echo 0; } >>$OUT 368 | { true && echo 1 || echo 0; } >>$OUT 369 | """, 370 | ) 371 | 372 | return [ 373 | DefaultInfo( 374 | files = depset(direct = [explicit_output, path_output]), 375 | runfiles = ctx.runfiles(files = [explicit_output, path_output]), 376 | ), 377 | CustomRuleInfo( 378 | false_binary = toolchain.sh_binaries_info.executables["false"], 379 | ), 380 | ] 381 | 382 | _custom_rule = rule( 383 | _custom_rule_impl, 384 | cfg = _local_binaries_toolchain_transition, 385 | toolchains = ["@rules_sh//sh/posix:toolchain_type"], 386 | attrs = { 387 | "_allowlist_function_transition": attr.label( 388 | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 389 | ), 390 | }, 391 | ) 392 | 393 | def _custom_rule_test_impl(ctx): 394 | env = analysistest.begin(ctx) 395 | 396 | rule_info = analysistest.target_under_test(env)[CustomRuleInfo] 397 | 398 | asserts.equals( 399 | env, 400 | rule_info.false_binary, 401 | ctx.executable.false_binary, 402 | ) 403 | 404 | return analysistest.end(env) 405 | 406 | custom_rule_test = analysistest.make( 407 | _custom_rule_test_impl, 408 | attrs = { 409 | "false_binary": attr.label( 410 | executable = True, 411 | # The _local_binaries_toolchain_transition places the toolchain 412 | # provided binary under a different configuration than the 413 | # reference binary would be under the regular `host` configuration. 414 | # We apply the same transition to the reference to align the 415 | # configurations. 416 | cfg = _local_binaries_toolchain_transition, 417 | ), 418 | "_allowlist_function_transition": attr.label( 419 | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 420 | ), 421 | }, 422 | ) 423 | 424 | def _test_custom_rule(): 425 | _custom_rule( 426 | name = "custom_rule", 427 | tags = ["manual"], 428 | ) 429 | custom_rule_test( 430 | name = "custom_rule_analysis_test", 431 | target_under_test = ":custom_rule", 432 | false_binary = ":false", 433 | ) 434 | native.sh_test( 435 | name = "custom_rule_output_test", 436 | srcs = ["custom_rule_output_test.sh"], 437 | deps = ["@bazel_tools//tools/bash/runfiles"], 438 | data = [":custom_rule"], 439 | ) 440 | native.test_suite( 441 | name = "custom_rule_test", 442 | tests = [ 443 | ":custom_rule_analysis_test", 444 | ":custom_rule_output_test", 445 | ], 446 | ) 447 | 448 | # genrule usage ###################################################### 449 | 450 | def _test_genrule(): 451 | native.genrule( 452 | name = "genrule_explicit", 453 | tags = ["manual"], 454 | outs = ["genrule_explicit.txt"], 455 | cmd = """\ 456 | { $(POSIX_FALSE) && echo 1 || echo 0; } >> $(OUTS) 457 | { $(POSIX_TRUE) && echo 1 || echo 0; } >> $(OUTS) 458 | """, 459 | toolchains = [posix_hermetic.MAKE_VARIABLES], 460 | ) 461 | native.genrule( 462 | name = "genrule_path", 463 | tags = ["manual"], 464 | outs = ["genrule_path.txt"], 465 | cmd = """\ 466 | # Disable the shell builtins and standard PATH to ensure 467 | # that we use the commands provided by the toolchain. 468 | enable -n false true 469 | export PATH="$(_POSIX_PATH)" 470 | { false && echo 1 || echo 0; } >> $(OUTS) 471 | { true && echo 1 || echo 0; } >> $(OUTS) 472 | """, 473 | toolchains = [posix_hermetic.MAKE_VARIABLES], 474 | ) 475 | _use_local_binaries_toolchain( 476 | name = "genrule_explicit_transitioned", 477 | src = ":genrule_explicit", 478 | ) 479 | _use_local_binaries_toolchain( 480 | name = "genrule_path_transitioned", 481 | src = ":genrule_path", 482 | ) 483 | native.sh_test( 484 | name = "genrule_explicit_output_test", 485 | srcs = ["genrule_explicit_output_test.sh"], 486 | deps = ["@bazel_tools//tools/bash/runfiles"], 487 | data = [":genrule_explicit_transitioned"], 488 | ) 489 | native.sh_test( 490 | name = "genrule_path_output_test", 491 | srcs = ["genrule_path_output_test.sh"], 492 | deps = ["@bazel_tools//tools/bash/runfiles"], 493 | data = [":genrule_path_transitioned"], 494 | ) 495 | native.test_suite( 496 | name = "genrule_test", 497 | tests = [ 498 | ":genrule_explicit_output_test", 499 | ":genrule_path_output_test", 500 | ], 501 | ) 502 | 503 | # runfiles toolchain ################################################# 504 | 505 | # A toolchain that depends on runfiles resolution. 506 | 507 | def _runfiles_toolchain(): 508 | native.sh_binary( 509 | name = "echo", 510 | srcs = ["echo.sh"], 511 | deps = ["@bazel_tools//tools/bash/runfiles"], 512 | data = [":echo_data.txt"], 513 | ) 514 | sh_binaries( 515 | name = "binaries-runfiles", 516 | srcs = [":echo"], 517 | ) 518 | sh_posix_hermetic_toolchain( 519 | name = "posix-toolchain-runfiles", 520 | cmds = ":binaries-runfiles", 521 | ) 522 | native.toolchain( 523 | name = "toolchain-runfiles", 524 | toolchain = ":posix-toolchain-runfiles", 525 | toolchain_type = "@rules_sh//sh/posix:toolchain_type", 526 | ) 527 | 528 | def _runfiles_toolchain_transition_impl(settings, attr): 529 | return { 530 | "//command_line_option:extra_toolchains": str(Label("//posix_hermetic:toolchain-runfiles")), 531 | } 532 | 533 | _runfiles_toolchain_transition = transition( 534 | implementation = _runfiles_toolchain_transition_impl, 535 | inputs = [], 536 | outputs = ["//command_line_option:extra_toolchains"], 537 | ) 538 | 539 | def _use_runfiles_toolchain_impl(ctx): 540 | src = ctx.attr.src[DefaultInfo] 541 | return [ 542 | DefaultInfo( 543 | files = src.files, 544 | runfiles = ctx 545 | .runfiles(files = src.files.to_list()) 546 | .merge(src.default_runfiles), 547 | ), 548 | ] 549 | 550 | _use_runfiles_toolchain = rule( 551 | _use_runfiles_toolchain_impl, 552 | cfg = _runfiles_toolchain_transition, 553 | attrs = { 554 | "src": attr.label(), 555 | "_allowlist_function_transition": attr.label( 556 | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 557 | ), 558 | }, 559 | ) 560 | 561 | def _runfiles_toolchain_test_impl(ctx): 562 | env = analysistest.begin(ctx) 563 | 564 | toolchain = analysistest.target_under_test(env)[platform_common.ToolchainInfo] 565 | 566 | # -------------------------------------------- 567 | # runfiles forwarding 568 | 569 | toolchain_runfiles = toolchain.tool[DefaultInfo].default_runfiles.files.to_list() 570 | echo_runfiles = ctx.attr.echo_binary[DefaultInfo].default_runfiles.files.to_list() 571 | asserts.true( 572 | env, 573 | all([ 574 | runfile in toolchain_runfiles 575 | for runfile in echo_runfiles 576 | ]), 577 | "Expected that all of echo's runfiles are forwared in the toolchain runfiles.", 578 | ) 579 | 580 | return analysistest.end(env) 581 | 582 | runfiles_toolchain_test = analysistest.make( 583 | _runfiles_toolchain_test_impl, 584 | attrs = { 585 | "echo_binary": attr.label( 586 | executable = True, 587 | # TODO[AH] Both the target_under_test and the reference should be 588 | # provided in the exec configuration. 589 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 590 | # cfg = "exec", 591 | cfg = "target", 592 | ), 593 | "echo_data": attr.label(allow_single_file = True), 594 | }, 595 | ) 596 | 597 | def _test_runfiles_toolchain(): 598 | _runfiles_toolchain() 599 | runfiles_toolchain_test( 600 | name = "runfiles_toolchain_test", 601 | target_under_test = ":posix-toolchain-runfiles", 602 | echo_binary = ":echo", 603 | echo_data = ":echo_data.txt", 604 | ) 605 | 606 | # runfiles in custom rule ############################################ 607 | 608 | def _runfiles_custom_rule_impl(ctx): 609 | toolchain = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"] 610 | 611 | # Override the argv[0] relative runfiles tree or manifest by the bundle's. 612 | # This is a workaround for https://github.com/bazelbuild/bazel/issues/15486 613 | tools_env = { 614 | "RUNFILES_DIR": toolchain.tool[DefaultInfo].files_to_run.runfiles_manifest.dirname, 615 | "RUNFILES_MANIFEST_FILE": toolchain.tool[DefaultInfo].files_to_run.runfiles_manifest.path, 616 | } 617 | 618 | output = ctx.actions.declare_file("{}.txt".format(ctx.label.name)) 619 | ctx.actions.run_shell( 620 | outputs = [output], 621 | env = tools_env, 622 | tools = [ 623 | toolchain.sh_binaries_info.files_to_run["echo"], 624 | ], 625 | arguments = [ 626 | toolchain.sh_binaries_info.executables["echo"].path, 627 | output.path, 628 | ], 629 | command = """\ 630 | ECHO="$1" 631 | OUT="$2" 632 | $ECHO message >>$OUT 633 | """, 634 | ) 635 | 636 | return [ 637 | DefaultInfo( 638 | files = depset(direct = [output]), 639 | runfiles = ctx.runfiles(files = [output]), 640 | ), 641 | ] 642 | 643 | _runfiles_custom_rule = rule( 644 | _runfiles_custom_rule_impl, 645 | cfg = _runfiles_toolchain_transition, 646 | toolchains = ["@rules_sh//sh/posix:toolchain_type"], 647 | attrs = { 648 | "_allowlist_function_transition": attr.label( 649 | default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 650 | ), 651 | }, 652 | ) 653 | 654 | def _test_runfiles_custom_rule(): 655 | _runfiles_custom_rule( 656 | name = "runfiles_custom_rule", 657 | tags = ["manual"], 658 | ) 659 | native.sh_test( 660 | name = "runfiles_custom_rule_test", 661 | srcs = ["runfiles_custom_rule_test.sh"], 662 | deps = ["@bazel_tools//tools/bash/runfiles"], 663 | data = ["runfiles_custom_rule"], 664 | ) 665 | 666 | # runfiles in genrule ################################################ 667 | 668 | def _test_runfiles_genrule(): 669 | native.genrule( 670 | name = "runfiles_genrule", 671 | tags = ["manual"], 672 | outs = ["runfiles_genrule.txt"], 673 | cmd = """\ 674 | IS_WINDOWS= 675 | case "$$OSTYPE" in 676 | cygwin|msys|win32) IS_WINDOWS=1;; 677 | esac 678 | 679 | with_runfiles() { 680 | # The explicit RUNFILES_DIR|RUNFILES_MANIFEST_FILE is a workaround for 681 | # https://github.com/bazelbuild/bazel/issues/15486 682 | if [[ -n $$IS_WINDOWS ]]; then 683 | RUNFILES_MANIFEST_FILE=$(execpath @rules_sh//sh/posix:make_variables).runfiles_manifest \\ 684 | eval "$$@" 685 | else 686 | RUNFILES_DIR=$(execpath @rules_sh//sh/posix:make_variables).runfiles \\ 687 | eval "$$@" 688 | fi 689 | } 690 | 691 | with_runfiles $(POSIX_ECHO) message >>$(OUTS) 692 | """, 693 | toolchains = [posix_hermetic.MAKE_VARIABLES], 694 | ) 695 | _use_runfiles_toolchain( 696 | name = "runfiles_genrule_transitioned", 697 | src = ":runfiles_genrule", 698 | ) 699 | native.sh_test( 700 | name = "runfiles_genrule_test", 701 | srcs = ["runfiles_genrule_test.sh"], 702 | deps = ["@bazel_tools//tools/bash/runfiles"], 703 | data = [":runfiles_genrule_transitioned"], 704 | ) 705 | 706 | # test suite ######################################################### 707 | 708 | def posix_hermetic_test_suite(name): 709 | _test_empty_toolchain() 710 | _test_unrecognized_tool_toolchain() 711 | _test_local_binaries_toolchain() 712 | _test_custom_rule() 713 | _test_genrule() 714 | _test_runfiles_toolchain() 715 | _test_runfiles_custom_rule() 716 | _test_runfiles_genrule() 717 | 718 | native.test_suite( 719 | name = name, 720 | tests = [ 721 | ":empty_toolchain_test", 722 | ":unrecognized_tool_toolchain_test", 723 | ":local_binaries_toolchain_test", 724 | ":custom_rule_test", 725 | ":genrule_test", 726 | ":runfiles_toolchain_test", 727 | ":runfiles_custom_rule_test", 728 | ":runfiles_genrule_test", 729 | ], 730 | ) 731 | -------------------------------------------------------------------------------- /tests/sh_binaries/sh_binaries_test.bzl: -------------------------------------------------------------------------------- 1 | load("@rules_sh//sh:sh.bzl", "ShBinariesInfo", "sh_binaries") 2 | load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") 3 | 4 | # bundle single binary ############################################### 5 | 6 | def _hello_world_binary(): 7 | native.sh_binary( 8 | name = "hello_world", 9 | srcs = ["hello_world.sh"], 10 | ) 11 | 12 | def _bundle_single_binary_test_impl(ctx): 13 | env = analysistest.begin(ctx) 14 | 15 | bundle_under_test = analysistest.target_under_test(env) 16 | bundle_default_info = bundle_under_test[DefaultInfo] 17 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 18 | 19 | asserts.equals( 20 | env, 21 | 1, 22 | len(bundle_default_info.files.to_list()), 23 | "Expected a singleton binary bundle", 24 | ) 25 | 26 | asserts.true( 27 | env, 28 | ctx.executable.reference in bundle_default_info.files.to_list(), 29 | '"hello_world" should be in the DefaultInfo.files', 30 | ) 31 | 32 | asserts.true( 33 | env, 34 | "hello_world" in bundle_binaries_info.executables, 35 | '"hello_world" should be in ShBinariesInfo.files', 36 | ) 37 | 38 | asserts.equals( 39 | env, 40 | ctx.executable.reference, 41 | bundle_binaries_info.executables["hello_world"], 42 | ) 43 | 44 | asserts.equals( 45 | env, 46 | 1, 47 | len(bundle_binaries_info.paths.to_list()), 48 | "Expected a singleton PATH list.", 49 | ) 50 | 51 | asserts.true( 52 | env, 53 | ctx.executable.reference.dirname in bundle_binaries_info.paths.to_list(), 54 | 'Expected "hello_world" in bundle PATH list.', 55 | ) 56 | 57 | return analysistest.end(env) 58 | 59 | bundle_single_binary_test = analysistest.make( 60 | _bundle_single_binary_test_impl, 61 | attrs = { 62 | "reference": attr.label( 63 | executable = True, 64 | # TODO[AH] Both the target_under_test and the reference should be 65 | # provided in the exec configuration. 66 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 67 | # cfg = "exec", 68 | cfg = "target", 69 | doc = "The single binary contained in the bundle. Used for reference.", 70 | ), 71 | }, 72 | ) 73 | 74 | def _test_single_binary(): 75 | sh_binaries( 76 | name = "bundle_single_binary", 77 | srcs = [":hello_world"], 78 | tags = ["manual"], 79 | ) 80 | bundle_single_binary_test( 81 | name = "bundle_single_binary_test", 82 | target_under_test = ":bundle_single_binary", 83 | reference = ":hello_world", 84 | ) 85 | 86 | # bundle binary with data ############################################ 87 | 88 | def _hello_data_binary(): 89 | native.sh_binary( 90 | name = "hello_data", 91 | srcs = ["hello_data.sh"], 92 | deps = ["@bazel_tools//tools/bash/runfiles"], 93 | data = ["hello_data.txt"], 94 | ) 95 | 96 | def _bundle_binary_with_data_test_impl(ctx): 97 | env = analysistest.begin(ctx) 98 | 99 | bundle_under_test = analysistest.target_under_test(env) 100 | bundle_default_info = bundle_under_test[DefaultInfo] 101 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 102 | 103 | asserts.equals( 104 | env, 105 | # On Windows the runfiles will contain both hello_data and hello_data.exe. 106 | 6 if ctx.attr.is_windows else 5, 107 | len(bundle_default_info.default_runfiles.files.to_list()), 108 | "Expected a runfiles set with five items", 109 | ) 110 | 111 | for file in ctx.attr.reference[DefaultInfo].default_runfiles.files.to_list(): 112 | asserts.true( 113 | env, 114 | file in bundle_default_info.default_runfiles.files.to_list(), 115 | "The file {} should be in the runfiles set.".format(file.basename), 116 | ) 117 | 118 | # The dummy binary is a workaround for https://github.com/bazelbuild/bazel/issues/15486 119 | asserts.true( 120 | env, 121 | bundle_default_info.files_to_run.executable in bundle_default_info.default_runfiles.files.to_list(), 122 | "The bundle dummy binary should be in the runfiles set.", 123 | ) 124 | 125 | return analysistest.end(env) 126 | 127 | bundle_binary_with_data_test = analysistest.make( 128 | _bundle_binary_with_data_test_impl, 129 | attrs = { 130 | "reference": attr.label( 131 | executable = True, 132 | # TODO[AH] Both the target_under_test and the reference should be 133 | # provided in the exec configuration. 134 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 135 | # cfg = "exec", 136 | cfg = "target", 137 | doc = "The binary with data contained in the bundle. Used for reference.", 138 | ), 139 | "is_windows": attr.bool(), 140 | }, 141 | ) 142 | 143 | def _test_binary_with_data(): 144 | sh_binaries( 145 | name = "bundle_binary_with_data", 146 | srcs = [":hello_data"], 147 | tags = ["manual"], 148 | ) 149 | bundle_binary_with_data_test( 150 | name = "bundle_binary_with_data_test", 151 | target_under_test = ":bundle_binary_with_data", 152 | reference = ":hello_data", 153 | is_windows = select({ 154 | "@platforms//os:windows": True, 155 | "//conditions:default": False, 156 | }), 157 | ) 158 | 159 | # empty bundle with data ############################################# 160 | 161 | def _empty_bundle_with_data_test_impl(ctx): 162 | env = analysistest.begin(ctx) 163 | 164 | bundle_under_test = analysistest.target_under_test(env) 165 | bundle_default_info = bundle_under_test[DefaultInfo] 166 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 167 | 168 | asserts.equals( 169 | env, 170 | 2, 171 | len(bundle_default_info.default_runfiles.files.to_list()), 172 | "Expected a runfiles set with two items.", 173 | ) 174 | 175 | asserts.true( 176 | env, 177 | ctx.file.reference in bundle_default_info.default_runfiles.files.to_list(), 178 | '"hello_data.txt" should be in the runfiles set.', 179 | ) 180 | 181 | # The dummy binary is a workaround for https://github.com/bazelbuild/bazel/issues/15486 182 | asserts.true( 183 | env, 184 | bundle_default_info.files_to_run.executable in bundle_default_info.default_runfiles.files.to_list(), 185 | "The bundle dummy binary should be in the runfiles set.", 186 | ) 187 | 188 | return analysistest.end(env) 189 | 190 | empty_bundle_with_data_test = analysistest.make( 191 | _empty_bundle_with_data_test_impl, 192 | attrs = { 193 | "reference": attr.label( 194 | allow_single_file = True, 195 | doc = "The data contained in the bundle. Used for reference.", 196 | ), 197 | }, 198 | ) 199 | 200 | def _test_empty_bundle_with_data(): 201 | sh_binaries( 202 | name = "empty_bundle_with_data", 203 | srcs = [], 204 | data = ["hello_data.txt"], 205 | tags = ["manual"], 206 | ) 207 | empty_bundle_with_data_test( 208 | name = "empty_bundle_with_data_test", 209 | target_under_test = ":empty_bundle_with_data", 210 | reference = "hello_data.txt", 211 | ) 212 | 213 | # bundle two binaries ################################################ 214 | 215 | def _bundle_two_binaries_test_impl(ctx): 216 | env = analysistest.begin(ctx) 217 | 218 | bundle_under_test = analysistest.target_under_test(env) 219 | bundle_default_info = bundle_under_test[DefaultInfo] 220 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 221 | 222 | asserts.equals( 223 | env, 224 | 2, 225 | len(bundle_default_info.files.to_list()), 226 | "Expected two binaries in the bundle", 227 | ) 228 | 229 | asserts.true( 230 | env, 231 | ctx.executable.reference_hello_world in bundle_default_info.files.to_list(), 232 | '"hello_world" should be in the DefaultInfo.files', 233 | ) 234 | 235 | asserts.true( 236 | env, 237 | ctx.executable.reference_hello_data in bundle_default_info.files.to_list(), 238 | '"hello_data" should be in the DefaultInfo.files', 239 | ) 240 | 241 | asserts.true( 242 | env, 243 | ctx.file.reference_data_file in bundle_default_info.default_runfiles.files.to_list(), 244 | "Expected the data file in the runfiles", 245 | ) 246 | 247 | asserts.true( 248 | env, 249 | "hello_world" in bundle_binaries_info.executables, 250 | '"hello_world" should be in ShBinariesInfo.files', 251 | ) 252 | 253 | asserts.equals( 254 | env, 255 | ctx.executable.reference_hello_world, 256 | bundle_binaries_info.executables["hello_world"], 257 | ) 258 | 259 | asserts.equals( 260 | env, 261 | bundle_binaries_info.executables["hello_world"], 262 | bundle_binaries_info.files_to_run["hello_world"].executable, 263 | ) 264 | 265 | asserts.true( 266 | env, 267 | "hello_data" in bundle_binaries_info.executables, 268 | '"hello_data" should be in ShBinariesInfo.files', 269 | ) 270 | 271 | asserts.equals( 272 | env, 273 | ctx.executable.reference_hello_data, 274 | bundle_binaries_info.executables["hello_data"], 275 | ) 276 | 277 | asserts.equals( 278 | env, 279 | bundle_binaries_info.executables["hello_data"], 280 | bundle_binaries_info.files_to_run["hello_data"].executable, 281 | ) 282 | 283 | asserts.equals( 284 | env, 285 | 1, 286 | len(bundle_binaries_info.paths.to_list()), 287 | "Expected a singleton PATH list.", 288 | ) 289 | 290 | asserts.true( 291 | env, 292 | ctx.executable.reference_hello_world.dirname in bundle_binaries_info.paths.to_list(), 293 | 'Expected "hello_world" in bundle PATH list.', 294 | ) 295 | 296 | asserts.true( 297 | env, 298 | ctx.executable.reference_hello_data.dirname in bundle_binaries_info.paths.to_list(), 299 | 'Expected "hello_data" in bundle PATH list.', 300 | ) 301 | 302 | return analysistest.end(env) 303 | 304 | bundle_two_binaries_test = analysistest.make( 305 | _bundle_two_binaries_test_impl, 306 | attrs = { 307 | "reference_hello_world": attr.label( 308 | executable = True, 309 | # TODO[AH] Both the target_under_test and the reference should be 310 | # provided in the exec configuration. 311 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 312 | # cfg = "exec", 313 | cfg = "target", 314 | doc = "The first binary. Used for reference.", 315 | ), 316 | "reference_hello_data": attr.label( 317 | executable = True, 318 | # TODO[AH] Both the target_under_test and the reference should be 319 | # provided in the exec configuration. 320 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 321 | # cfg = "exec", 322 | cfg = "target", 323 | doc = "The second binary. Used for reference.", 324 | ), 325 | "reference_data_file": attr.label( 326 | allow_single_file = True, 327 | doc = "The data file contained in the second dependency. Used for reference.", 328 | ), 329 | }, 330 | ) 331 | 332 | def _test_bundle_two_binaries(): 333 | sh_binaries( 334 | name = "bundle_two_binaries", 335 | srcs = [ 336 | ":hello_world", 337 | ":hello_data", 338 | ], 339 | tags = ["manual"], 340 | ) 341 | bundle_two_binaries_test( 342 | name = "bundle_two_binaries_test", 343 | target_under_test = ":bundle_two_binaries", 344 | reference_hello_world = ":hello_world", 345 | reference_hello_data = ":hello_data", 346 | reference_data_file = "hello_data.txt", 347 | ) 348 | 349 | # merge bundles ###################################################### 350 | 351 | def _merge_bundles_test_impl(ctx): 352 | env = analysistest.begin(ctx) 353 | 354 | bundle_under_test = analysistest.target_under_test(env) 355 | bundle_default_info = bundle_under_test[DefaultInfo] 356 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 357 | 358 | asserts.equals( 359 | env, 360 | 2, 361 | len(bundle_default_info.files.to_list()), 362 | "Expected two binaries in the bundle", 363 | ) 364 | 365 | asserts.true( 366 | env, 367 | ctx.executable.reference_hello_world in bundle_default_info.files.to_list(), 368 | '"hello_world" should be in the DefaultInfo.files', 369 | ) 370 | 371 | asserts.true( 372 | env, 373 | ctx.executable.reference_hello_data in bundle_default_info.files.to_list(), 374 | '"hello_data" should be in the DefaultInfo.files', 375 | ) 376 | 377 | asserts.true( 378 | env, 379 | ctx.file.reference_data_file in bundle_default_info.default_runfiles.files.to_list(), 380 | "Expected the data file in the runfiles", 381 | ) 382 | 383 | asserts.true( 384 | env, 385 | "hello_world" in bundle_binaries_info.executables, 386 | '"hello_world" should be in ShBinariesInfo.files', 387 | ) 388 | 389 | asserts.equals( 390 | env, 391 | ctx.executable.reference_hello_world, 392 | bundle_binaries_info.executables["hello_world"], 393 | ) 394 | 395 | asserts.equals( 396 | env, 397 | bundle_binaries_info.executables["hello_world"], 398 | bundle_binaries_info.files_to_run["hello_world"].executable, 399 | ) 400 | 401 | asserts.true( 402 | env, 403 | "hello_data" in bundle_binaries_info.executables, 404 | '"hello_data" should be in ShBinariesInfo.files', 405 | ) 406 | 407 | asserts.equals( 408 | env, 409 | ctx.executable.reference_hello_data, 410 | bundle_binaries_info.executables["hello_data"], 411 | ) 412 | 413 | asserts.equals( 414 | env, 415 | bundle_binaries_info.executables["hello_data"], 416 | bundle_binaries_info.files_to_run["hello_data"].executable, 417 | ) 418 | 419 | asserts.equals( 420 | env, 421 | 1, 422 | len(bundle_binaries_info.paths.to_list()), 423 | "Expected a singleton PATH list.", 424 | ) 425 | 426 | asserts.true( 427 | env, 428 | ctx.executable.reference_hello_world.dirname in bundle_binaries_info.paths.to_list(), 429 | 'Expected "hello_world" in bundle PATH list.', 430 | ) 431 | 432 | asserts.true( 433 | env, 434 | ctx.executable.reference_hello_data.dirname in bundle_binaries_info.paths.to_list(), 435 | 'Expected "hello_data" in bundle PATH list.', 436 | ) 437 | 438 | return analysistest.end(env) 439 | 440 | merge_bundles_test = analysistest.make( 441 | _merge_bundles_test_impl, 442 | attrs = { 443 | "reference_hello_world": attr.label( 444 | executable = True, 445 | # TODO[AH] Both the target_under_test and the reference should be 446 | # provided in the exec configuration. 447 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 448 | # cfg = "exec", 449 | cfg = "target", 450 | doc = "The binary contained in the first dependency. Used for reference.", 451 | ), 452 | "reference_hello_data": attr.label( 453 | executable = True, 454 | # TODO[AH] Both the target_under_test and the reference should be 455 | # provided in the exec configuration. 456 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 457 | # cfg = "exec", 458 | cfg = "target", 459 | doc = "The binary contained in the second dependency. Used for reference.", 460 | ), 461 | "reference_data_file": attr.label( 462 | allow_single_file = True, 463 | doc = "The data file contained in the second dependency. Used for reference.", 464 | ), 465 | }, 466 | ) 467 | 468 | def _test_merge_bundles(): 469 | sh_binaries( 470 | name = "merge_bundles_part_hello_world", 471 | srcs = [":hello_world"], 472 | tags = ["manual"], 473 | ) 474 | sh_binaries( 475 | name = "merge_bundles_part_hello_data", 476 | srcs = [":hello_data"], 477 | tags = ["manual"], 478 | ) 479 | sh_binaries( 480 | name = "merge_bundles", 481 | deps = [ 482 | ":merge_bundles_part_hello_world", 483 | ":merge_bundles_part_hello_data", 484 | ], 485 | tags = ["manual"], 486 | ) 487 | merge_bundles_test( 488 | name = "merge_bundles_test", 489 | target_under_test = ":merge_bundles", 490 | reference_hello_world = ":hello_world", 491 | reference_hello_data = ":hello_data", 492 | reference_data_file = "hello_data.txt", 493 | ) 494 | 495 | # bundle used in custom rule ######################################### 496 | 497 | def _custom_rule_impl(ctx): 498 | tools = ctx.attr.tools[ShBinariesInfo] 499 | 500 | # Override the argv[0] relative runfiles tree or manifest by the bundle's. 501 | # This is a workaround for https://github.com/bazelbuild/bazel/issues/15486 502 | tools_env = { 503 | "RUNFILES_DIR": ctx.attr.tools[DefaultInfo].files_to_run.runfiles_manifest.dirname, 504 | "RUNFILES_MANIFEST_FILE": ctx.attr.tools[DefaultInfo].files_to_run.runfiles_manifest.path, 505 | } 506 | 507 | output_run = ctx.actions.declare_file("custom_rule_run_output") 508 | ctx.actions.run( 509 | outputs = [output_run], 510 | executable = tools.files_to_run["hello_data"], 511 | arguments = [output_run.path], 512 | mnemonic = "RunExecutableWithBundle", 513 | progress_message = "Running hello_data", 514 | env = tools_env, 515 | ) 516 | 517 | output_run_shell = ctx.actions.declare_file("custom_rule_run_shell_output") 518 | ctx.actions.run_shell( 519 | outputs = [output_run_shell], 520 | tools = [ 521 | tools.files_to_run["hello_world"], 522 | tools.files_to_run["hello_data"], 523 | ], 524 | arguments = [ 525 | tools.executables["hello_world"].path, 526 | tools.executables["hello_data"].path, 527 | output_run_shell.path, 528 | ], 529 | mnemonic = "RunCommandWithBundle", 530 | command = "$1 > $3 && $2 >> $3", 531 | progress_message = "Running hello_world and hello_data", 532 | env = tools_env, 533 | ) 534 | 535 | default_info = DefaultInfo( 536 | files = depset(direct = [output_run, output_run_shell]), 537 | runfiles = ctx.runfiles(files = [output_run, output_run_shell]), 538 | ) 539 | 540 | return [default_info] 541 | 542 | custom_rule = rule( 543 | _custom_rule_impl, 544 | attrs = { 545 | "tools": attr.label( 546 | cfg = "exec", 547 | mandatory = True, 548 | ), 549 | }, 550 | ) 551 | 552 | def _test_custom_rule(): 553 | sh_binaries( 554 | name = "custom_rule_bundle", 555 | srcs = [ 556 | ":hello_world", 557 | ":hello_data", 558 | ], 559 | tags = ["manual"], 560 | ) 561 | custom_rule( 562 | name = "custom_rule", 563 | tools = ":custom_rule_bundle", 564 | tags = ["manual"], 565 | ) 566 | native.sh_test( 567 | name = "custom_rule_test", 568 | srcs = ["custom_rule_test.sh"], 569 | deps = ["@bazel_tools//tools/bash/runfiles"], 570 | data = [":custom_rule"], 571 | ) 572 | 573 | # bundle used in genrule ############################################# 574 | 575 | def _test_genrule(): 576 | sh_binaries( 577 | name = "genrule_bundle", 578 | srcs = [ 579 | ":hello_world", 580 | ":hello_data", 581 | ], 582 | tags = ["manual"], 583 | ) 584 | native.genrule( 585 | name = "genrule", 586 | outs = [ 587 | "genrule_output_world", 588 | "genrule_output_data", 589 | "genrule_output_by_path", 590 | ], 591 | cmd = """\ 592 | $(GENRULE_BUNDLE_HELLO_WORLD) >$(execpath genrule_output_world) 593 | 594 | IS_WINDOWS= 595 | case "$$OSTYPE" in 596 | cygwin|msys|win32) IS_WINDOWS=1;; 597 | esac 598 | 599 | with_runfiles() { 600 | # The explicit RUNFILES_DIR|RUNFILES_MANIFEST_FILE is a workaround for 601 | # https://github.com/bazelbuild/bazel/issues/15486 602 | if [[ -n $$IS_WINDOWS ]]; then 603 | RUNFILES_MANIFEST_FILE=$(execpath :genrule_bundle).runfiles_manifest \\ 604 | eval "$$@" 605 | else 606 | RUNFILES_DIR=$(execpath :genrule_bundle).runfiles \\ 607 | eval "$$@" 608 | fi 609 | } 610 | 611 | with_runfiles $(GENRULE_BUNDLE_HELLO_DATA) >$(execpath genrule_output_data) 612 | 613 | PATH="$(_GENRULE_BUNDLE_PATH):$$PATH" 614 | hello_world >$(execpath genrule_output_by_path) 615 | with_runfiles hello_data >>$(execpath genrule_output_by_path) 616 | """, 617 | toolchains = [":genrule_bundle"], 618 | ) 619 | native.sh_test( 620 | name = "genrule_test", 621 | srcs = ["genrule_test.sh"], 622 | deps = ["@bazel_tools//tools/bash/runfiles"], 623 | data = [":genrule"], 624 | ) 625 | 626 | # non executable ##################################################### 627 | 628 | def _non_executable_impl(ctx): 629 | output = ctx.actions.declare_file( 630 | ctx.attr.path, 631 | ) 632 | ctx.actions.write( 633 | output, 634 | content = "", 635 | is_executable = False, 636 | ) 637 | return [DefaultInfo( 638 | files = depset(direct = [output]), 639 | )] 640 | 641 | non_executable = rule( 642 | _non_executable_impl, 643 | attrs = { 644 | "path": attr.string(), 645 | }, 646 | ) 647 | 648 | def _non_executable_test_impl(ctx): 649 | env = analysistest.begin(ctx) 650 | 651 | workspace = str(ctx.label).split("//")[0] 652 | 653 | asserts.expect_failure( 654 | env, 655 | " ".join([ 656 | "srcs must be executable,", 657 | "but '{}//sh_binaries:non_executable_target' is not.".format(workspace), 658 | ]), 659 | ) 660 | 661 | return analysistest.end(env) 662 | 663 | non_executable_test = analysistest.make( 664 | _non_executable_test_impl, 665 | expect_failure = True, 666 | ) 667 | 668 | def _test_non_executable(): 669 | non_executable( 670 | name = "non_executable_target", 671 | path = "non_executable_file", 672 | tags = ["manual"], 673 | ) 674 | sh_binaries( 675 | name = "non_executable", 676 | srcs = [ 677 | ":non_executable_target", 678 | ], 679 | tags = ["manual"], 680 | ) 681 | non_executable_test( 682 | name = "non_executable_test", 683 | target_under_test = ":non_executable", 684 | ) 685 | 686 | # non bundle dependency ############################################## 687 | 688 | def _non_bundle_dependency_test_impl(ctx): 689 | env = analysistest.begin(ctx) 690 | 691 | workspace = str(ctx.label).split("//")[0] 692 | 693 | asserts.expect_failure( 694 | env, 695 | " ".join([ 696 | "deps must be sh_binaries targets,", 697 | "but '{}//sh_binaries:hello_world' is not.".format(workspace), 698 | ]), 699 | ) 700 | 701 | return analysistest.end(env) 702 | 703 | non_bundle_dependency_test = analysistest.make( 704 | _non_bundle_dependency_test_impl, 705 | expect_failure = True, 706 | ) 707 | 708 | def _test_non_bundle_dependency(): 709 | sh_binaries( 710 | name = "non_bundle_dependency", 711 | deps = [ 712 | ":hello_world", 713 | ], 714 | tags = ["manual"], 715 | ) 716 | non_bundle_dependency_test( 717 | name = "non_bundle_dependency_test", 718 | target_under_test = ":non_bundle_dependency", 719 | ) 720 | 721 | # name collision ##################################################### 722 | 723 | def _dummy_binary_impl(ctx): 724 | output = ctx.actions.declare_file( 725 | ctx.attr.path, 726 | ) 727 | ctx.actions.write( 728 | output, 729 | content = "", 730 | is_executable = True, 731 | ) 732 | return [DefaultInfo( 733 | executable = output, 734 | )] 735 | 736 | dummy_binary = rule( 737 | _dummy_binary_impl, 738 | attrs = { 739 | "path": attr.string(), 740 | }, 741 | executable = True, 742 | ) 743 | 744 | def _name_collision_test_impl(ctx): 745 | env = analysistest.begin(ctx) 746 | 747 | workspace = str(ctx.label).split("//")[0] 748 | 749 | asserts.expect_failure( 750 | env, 751 | " ".join([ 752 | "name collision on 'dummy' between", 753 | "'{}//sh_binaries:name_collision_1' and".format(workspace), 754 | "'{}//sh_binaries:name_collision_2' in srcs.".format(workspace), 755 | ]), 756 | ) 757 | 758 | return analysistest.end(env) 759 | 760 | name_collision_test = analysistest.make( 761 | _name_collision_test_impl, 762 | expect_failure = True, 763 | ) 764 | 765 | def _test_name_collision(): 766 | dummy_binary( 767 | name = "name_collision_1", 768 | path = "name_collision_1/dummy", 769 | tags = ["manual"], 770 | ) 771 | dummy_binary( 772 | name = "name_collision_2", 773 | path = "name_collision_2/dummy", 774 | tags = ["manual"], 775 | ) 776 | sh_binaries( 777 | name = "name_collision", 778 | srcs = [ 779 | ":name_collision_1", 780 | ":name_collision_2", 781 | ], 782 | tags = ["manual"], 783 | ) 784 | name_collision_test( 785 | name = "name_collision_test", 786 | target_under_test = ":name_collision", 787 | ) 788 | 789 | # override srcs ###################################################### 790 | 791 | def _override_srcs_test_impl(ctx): 792 | env = analysistest.begin(ctx) 793 | 794 | bundle_under_test = analysistest.target_under_test(env) 795 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 796 | 797 | asserts.equals( 798 | env, 799 | ctx.executable.reference, 800 | bundle_binaries_info.executables["dummy"], 801 | ) 802 | 803 | asserts.equals( 804 | env, 805 | ctx.executable.reference.dirname, 806 | bundle_binaries_info.paths.to_list()[0], 807 | ) 808 | 809 | return analysistest.end(env) 810 | 811 | override_srcs_test = analysistest.make( 812 | _override_srcs_test_impl, 813 | attrs = { 814 | "reference": attr.label( 815 | executable = True, 816 | # TODO[AH] Both the target_under_test and the reference should be 817 | # provided in the exec configuration. 818 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 819 | # cfg = "exec", 820 | cfg = "target", 821 | doc = "The binary that should take precedence. Used for reference.", 822 | ), 823 | }, 824 | ) 825 | 826 | def _test_override_srcs(): 827 | dummy_binary( 828 | name = "override_srcs_bin_1", 829 | path = "override_srcs_bin_1/dummy", 830 | tags = ["manual"], 831 | ) 832 | dummy_binary( 833 | name = "override_srcs_bin_2", 834 | path = "override_srcs_bin_2/dummy", 835 | tags = ["manual"], 836 | ) 837 | sh_binaries( 838 | name = "override_srcs_1", 839 | srcs = [":override_srcs_bin_1"], 840 | tags = ["manual"], 841 | ) 842 | sh_binaries( 843 | name = "override_srcs", 844 | srcs = [":override_srcs_bin_2"], 845 | deps = [":override_srcs_1"], 846 | tags = ["manual"], 847 | ) 848 | override_srcs_test( 849 | name = "override_srcs_test", 850 | target_under_test = ":override_srcs", 851 | reference = ":override_srcs_bin_2", 852 | ) 853 | 854 | # override deps ###################################################### 855 | 856 | def _override_deps_test_impl(ctx): 857 | env = analysistest.begin(ctx) 858 | 859 | bundle_under_test = analysistest.target_under_test(env) 860 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 861 | 862 | asserts.equals( 863 | env, 864 | ctx.executable.reference, 865 | bundle_binaries_info.executables["dummy"], 866 | ) 867 | 868 | asserts.equals( 869 | env, 870 | ctx.executable.reference.dirname, 871 | bundle_binaries_info.paths.to_list()[0], 872 | ) 873 | 874 | return analysistest.end(env) 875 | 876 | override_deps_test = analysistest.make( 877 | _override_deps_test_impl, 878 | attrs = { 879 | "reference": attr.label( 880 | executable = True, 881 | # TODO[AH] Both the target_under_test and the reference should be 882 | # provided in the exec configuration. 883 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 884 | # cfg = "exec", 885 | cfg = "target", 886 | doc = "The binary that should take precedence. Used for reference.", 887 | ), 888 | }, 889 | ) 890 | 891 | def _test_override_deps(): 892 | dummy_binary( 893 | name = "override_deps_bin_1", 894 | path = "override_deps_bin_1/dummy", 895 | tags = ["manual"], 896 | ) 897 | dummy_binary( 898 | name = "override_deps_bin_2", 899 | path = "override_deps_bin_2/dummy", 900 | tags = ["manual"], 901 | ) 902 | sh_binaries( 903 | name = "override_deps_1", 904 | srcs = [":override_deps_bin_1"], 905 | tags = ["manual"], 906 | ) 907 | sh_binaries( 908 | name = "override_deps_2", 909 | srcs = [":override_deps_bin_2"], 910 | tags = ["manual"], 911 | ) 912 | sh_binaries( 913 | name = "override_deps", 914 | deps = [ 915 | ":override_deps_1", 916 | ":override_deps_2", 917 | ], 918 | tags = ["manual"], 919 | ) 920 | override_deps_test( 921 | name = "override_deps_test", 922 | target_under_test = ":override_deps", 923 | reference = ":override_deps_bin_2", 924 | ) 925 | 926 | # Windows strip exe ################################################## 927 | 928 | def _windows_strip_exe_test_impl(ctx): 929 | env = analysistest.begin(ctx) 930 | 931 | bundle_under_test = analysistest.target_under_test(env) 932 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 933 | 934 | asserts.true( 935 | env, 936 | "empty" in bundle_binaries_info.executables, 937 | ) 938 | 939 | return analysistest.end(env) 940 | 941 | windows_strip_exe_test = analysistest.make( 942 | _windows_strip_exe_test_impl, 943 | config_settings = { 944 | "//command_line_option:platforms": str(Label("//sh_binaries:windows")), 945 | }, 946 | # TODO[AH] The target_under_test should be provided in the exec 947 | # configuration to be sure that the Windows platform check considers the 948 | # correct platform, i.e. the execute platform in tools use-cases. 949 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 950 | ) 951 | 952 | def _test_windows_strip_exe(): 953 | native.platform( 954 | name = "windows", 955 | constraint_values = [ 956 | "@platforms//os:windows", 957 | ], 958 | ) 959 | sh_binaries( 960 | name = "windows_strip_exe", 961 | srcs = ["empty.exe"], 962 | ) 963 | windows_strip_exe_test( 964 | name = "windows_strip_exe_test", 965 | target_under_test = ":windows_strip_exe", 966 | ) 967 | 968 | # Linux keep exe ##################################################### 969 | 970 | def _linux_keep_exe_test_impl(ctx): 971 | env = analysistest.begin(ctx) 972 | 973 | bundle_under_test = analysistest.target_under_test(env) 974 | bundle_binaries_info = bundle_under_test[ShBinariesInfo] 975 | 976 | asserts.true( 977 | env, 978 | "empty.exe" in bundle_binaries_info.executables, 979 | ) 980 | 981 | return analysistest.end(env) 982 | 983 | linux_keep_exe_test = analysistest.make( 984 | _linux_keep_exe_test_impl, 985 | config_settings = { 986 | "//command_line_option:platforms": str(Label("//sh_binaries:linux")), 987 | }, 988 | # TODO[AH] The target_under_test should be provided in the exec 989 | # configuration to be sure that the Windows platform check considers the 990 | # correct platform, i.e. the execute platform in tools use-cases. 991 | # See https://github.com/bazelbuild/bazel-skylib/issues/377 992 | ) 993 | 994 | def _test_linux_keep_exe(): 995 | native.platform( 996 | name = "linux", 997 | constraint_values = [ 998 | "@platforms//os:linux", 999 | ], 1000 | ) 1001 | sh_binaries( 1002 | name = "linux_keep_exe", 1003 | srcs = ["empty.exe"], 1004 | ) 1005 | linux_keep_exe_test( 1006 | name = "linux_keep_exe_test", 1007 | target_under_test = ":linux_keep_exe", 1008 | ) 1009 | 1010 | # test suite ######################################################### 1011 | 1012 | def sh_binaries_test_suite(name): 1013 | _hello_world_binary() 1014 | _hello_data_binary() 1015 | 1016 | _test_single_binary() 1017 | _test_binary_with_data() 1018 | _test_empty_bundle_with_data() 1019 | _test_bundle_two_binaries() 1020 | _test_merge_bundles() 1021 | _test_custom_rule() 1022 | _test_genrule() 1023 | _test_non_executable() 1024 | _test_non_bundle_dependency() 1025 | _test_name_collision() 1026 | _test_override_srcs() 1027 | _test_override_deps() 1028 | _test_windows_strip_exe() 1029 | _test_linux_keep_exe() 1030 | 1031 | native.test_suite( 1032 | name = name, 1033 | tests = [ 1034 | ":bundle_single_binary_test", 1035 | ":bundle_binary_with_data_test", 1036 | ":empty_bundle_with_data_test", 1037 | ":bundle_two_binaries_test", 1038 | ":merge_bundles_test", 1039 | ":custom_rule_test", 1040 | ":genrule_test", 1041 | ":non_executable_test", 1042 | ":non_bundle_dependency_test", 1043 | ":name_collision_test", 1044 | ":override_srcs_test", 1045 | ":override_deps_test", 1046 | ":windows_strip_exe_test", 1047 | ":linux_keep_exe_test", 1048 | ], 1049 | ) 1050 | --------------------------------------------------------------------------------