diff --git a/.bazelrc b/.bazelrc index 23e19094d..b55f7c9c4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -8,6 +8,9 @@ import %workspace%/.aspect/bazelrc/performance.bazelrc ### YOUR PROJECT SPECIFIC OPTIONS GO HERE ### +# symlinks on windows are required by some tests +startup --windows_enable_symlinks + # For testing our --stamp behavior. # Normally users would use a --workspace_status_command with a script that calls `git describe`. common --embed_label=v1.2.3 diff --git a/configure.bat b/configure.bat new file mode 100644 index 000000000..c30fcadcd --- /dev/null +++ b/configure.bat @@ -0,0 +1,4 @@ +:: run this on Windows to set up example for use +@echo Configuring workspace for Windows +:: aspect-cli doesn't support windows, so disable it +del %~dp0.bazeliskrc diff --git a/lib/paths.bzl b/lib/paths.bzl index 50807b405..01a945a87 100644 --- a/lib/paths.bzl +++ b/lib/paths.bzl @@ -24,3 +24,16 @@ source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ { echo>&2 "ERROR: runfiles.bash initializer cannot find $f. An executable rule may have forgotten to expose it in the runfiles, or the binary may require RUNFILES_DIR to be set."; exit 1; }; f=; set -e # --- end runfiles.bash initialization v3 --- """ + +# Convenient helper for macros that generate bash scripts +# Usage example: +# write_file( +# name = "_" + name, +# out = test_sh, +# content = BASH_RLOCATION_PREFIX + [ +# "set -o errexit", +# "file=$(rlocation $1)", +# "grep foo $file", +# ], +# ) +BASH_RLOCATION_PREFIX = ["#!/usr/bin/env bash"] + BASH_RLOCATION_FUNCTION.split("\n") diff --git a/lib/private/diff_test.bzl b/lib/private/diff_test.bzl index 68d0b951c..3ddc4b607 100644 --- a/lib/private/diff_test.bzl +++ b/lib/private/diff_test.bzl @@ -23,6 +23,8 @@ command (fc.exe) on Windows (no Bash is required). load("//lib:utils.bzl", "default_timeout") load(":directory_path.bzl", "DirectoryPathInfo") +load("//lib:windows_utils.bzl", "BATCH_RLOCATION_FUNCTION") +load("//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path") def _runfiles_path(f): if f.root.path: @@ -35,21 +37,21 @@ def _diff_test_impl(ctx): if DirectoryPathInfo in ctx.attr.file1: file1 = ctx.attr.file1[DirectoryPathInfo].directory - file1_path = "/".join([_runfiles_path(file1), ctx.attr.file1[DirectoryPathInfo].path]) + file1_path = "/".join([to_rlocation_path(ctx, file1), ctx.attr.file1[DirectoryPathInfo].path]) else: if len(ctx.files.file1) != 1: fail("file1 must be a single file or a target that provides a DirectoryPathInfo") file1 = ctx.files.file1[0] - file1_path = _runfiles_path(file1) + file1_path = to_rlocation_path(ctx, file1) if DirectoryPathInfo in ctx.attr.file2: file2 = ctx.attr.file2[DirectoryPathInfo].directory - file2_path = "/".join([_runfiles_path(file2), ctx.attr.file2[DirectoryPathInfo].path]) + file2_path = "/".join([to_rlocation_path(ctx, file2), ctx.attr.file2[DirectoryPathInfo].path]) else: if len(ctx.files.file2) != 1: fail("file2 must be a single file or a target that provides a DirectoryPathInfo") file2 = ctx.files.file2[0] - file2_path = _runfiles_path(file2) + file2_path = to_rlocation_path(ctx, file2) if file1 == file2: msg = "diff_test comparing the same file %s" % file1 @@ -58,9 +60,11 @@ def _diff_test_impl(ctx): if is_windows: test_suffix = "-test.bat" template = ctx.file._diff_test_tmpl_bat + rlocation_function = BATCH_RLOCATION_FUNCTION else: test_suffix = "-test.sh" template = ctx.file._diff_test_tmpl_sh + rlocation_function = BASH_RLOCATION_FUNCTION test_bin = ctx.actions.declare_file(ctx.label.name + test_suffix) ctx.actions.expand_template( @@ -71,6 +75,7 @@ def _diff_test_impl(ctx): "{fail_msg}": ctx.attr.failure_message, "{file1}": file1_path, "{file2}": file2_path, + "{rlocation_function}": rlocation_function, "{build_file_path}": ctx.build_file_path, }, is_executable = True, @@ -79,7 +84,7 @@ def _diff_test_impl(ctx): return DefaultInfo( executable = test_bin, files = depset(direct = [test_bin]), - runfiles = ctx.runfiles(files = [test_bin, file1, file2]), + runfiles = ctx.runfiles(files = [test_bin, file1, file2] + ctx.files._bash_runfiles), ) _diff_test = rule( @@ -102,6 +107,9 @@ _diff_test = rule( default = ":diff_test_tmpl.bat", allow_single_file = True, ), + "_bash_runfiles": attr.label( + default = Label("@bazel_tools//tools/bash/runfiles"), + ), }, test = True, implementation = _diff_test_impl, diff --git a/lib/private/diff_test_tmpl.bat b/lib/private/diff_test_tmpl.bat index 6ef94f967..f7c1b7726 100644 --- a/lib/private/diff_test_tmpl.bat +++ b/lib/private/diff_test_tmpl.bat @@ -3,48 +3,12 @@ :: TODO: Add support for XML_OUTPUT_FILE like in diff_test_tmpl.sh SETLOCAL ENABLEEXTENSIONS SETLOCAL ENABLEDELAYEDEXPANSION -set MF=%RUNFILES_MANIFEST_FILE:/=\\% set PATH=%SYSTEMROOT%\\system32 -set F1={file1} -set F2={file2} -if "!F1:~0,9!" equ "external/" (set F1=!F1:~9!) else (set F1=!TEST_WORKSPACE!/!F1!) -if "!F2:~0,9!" equ "external/" (set F2=!F2:~9!) else (set F2=!TEST_WORKSPACE!/!F2!) -for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F1! " "%MF%"`) do ( - set RF1=%%i - set RF1=!RF1:/=\\! -) -if "!RF1!" equ "" ( - if "%RUNFILES_MANIFEST_ONLY%" neq "1" if exist "%RUNFILES_DIR%\\%F1%" ( - set RF1="%RUNFILES_DIR%\\%F1%" - ) else ( - if exist "{file1}" ( - set RF1="{file1}" - ) - ) - if "!RF1!" neq "" ( set RF1=!RF1:/=\\! - ) else ( - echo>&2 ERROR: !F1! not found - exit /b 1 - ) -) -for /F "tokens=2* usebackq" %%i in (`findstr.exe /l /c:"!F2! " "%MF%"`) do ( - set RF2=%%i - set RF2=!RF2:/=\\! -) -if "!RF2!" equ "" ( - if "%RUNFILES_MANIFEST_ONLY%" neq "1" if exist "%RUNFILES_DIR%\\%F2%" ( - set RF2="%RUNFILES_DIR%\\%F2%" - ) else ( - if exist "{file2}" ( - set RF2="{file2}" - ) - ) - if "!RF2!" neq "" ( set RF2=!RF2:/=\\! - ) else ( - echo>&2 ERROR: !F2! not found - exit /b 1 - ) -) + +{rlocation_function} + +call :rlocation {file1} F1 +call :rlocation {file2} F2 set DF1=0 set DF2=0 if exist "!RF1!\\*" ( diff --git a/lib/private/diff_test_tmpl.sh b/lib/private/diff_test_tmpl.sh index 22da03806..a481694bc 100644 --- a/lib/private/diff_test_tmpl.sh +++ b/lib/private/diff_test_tmpl.sh @@ -19,46 +19,11 @@ EOF echo >&2 "FAIL: $1" exit 1 } -resolve_exec_root() { - local RUNFILES_PARENT - RUNFILES_PARENT=$(dirname "$RUNFILES_DIR") - local BIN_DIR - BIN_DIR="${RUNFILES_PARENT%$BUILD_FILE_DIR}" - local EXEC_ROOT - EXEC_ROOT=$(dirname $(dirname $(dirname "${BIN_DIR}"))) - echo -n "$EXEC_ROOT" -} -find_file() { - local F_RAW="$1" - local F="$2" - local RF= - - if [[ -f "$TEST_SRCDIR/$F1" || -d "$TEST_SRCDIR/$F" ]]; then - RF="$TEST_SRCDIR/$F" - elif [[ -d "${RUNFILES_DIR:-/dev/null}" && "${RUNFILES_MANIFEST_ONLY:-}" != 1 ]]; then - EXEC_ROOT=$(resolve_exec_root) - if [[ -e "$EXEC_ROOT/$F_RAW" ]]; then - RF="$EXEC_ROOT/$F_RAW" - else - RF="$RUNFILES_DIR/$F1" - fi - elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then - RF="$(grep -F -m1 "$F " "$RUNFILES_MANIFEST_FILE" | sed 's/^[^ ]* //')" - else - echo >&2 "ERROR: could not find \"${F_RAW}\"" - exit 1 - fi +{rlocation_function} - echo -n "$RF" -} -BUILD_FILE_DIR="$(dirname "{build_file_path}")" -F1="{file1}" -F2="{file2}" -[[ "$F1" =~ ^external/ ]] && F1="${F1#external/}" || F1="$TEST_WORKSPACE/$F1" -[[ "$F2" =~ ^external/ ]] && F2="${F2#external/}" || F2="$TEST_WORKSPACE/$F2" -RF1="$(find_file {file1} "$F1")" -RF2="$(find_file {file2} "$F2")" +RF1="$(rlocation {file1})" +RF2="$(rlocation {file2})" DF1= DF2= [[ ! -d "$RF1" ]] || DF1=1 diff --git a/lib/private/write_source_file.bzl b/lib/private/write_source_file.bzl index f5e85d2ec..f7ff5fceb 100644 --- a/lib/private/write_source_file.bzl +++ b/lib/private/write_source_file.bzl @@ -4,6 +4,8 @@ load(":diff_test.bzl", _diff_test = "diff_test") load(":directory_path.bzl", "DirectoryPathInfo") load(":fail_with_message_test.bzl", "fail_with_message_test") load(":utils.bzl", "utils") +load("//lib:windows_utils.bzl", "BATCH_RLOCATION_FUNCTION") +load("//lib:paths.bzl", "BASH_RLOCATION_PREFIX", "to_rlocation_path") WriteSourceFileInfo = provider( "Provider for write_source_file targets", @@ -191,9 +193,8 @@ def _write_source_file_sh(ctx, paths): for target in ctx.attr.additional_update_targets: additional_update_scripts.append(target[WriteSourceFileInfo].executable) - contents = ["""#!/usr/bin/env bash + contents = BASH_RLOCATION_PREFIX + [""" set -o errexit -o nounset -o pipefail -runfiles_dir=$PWD # BUILD_WORKSPACE_DIRECTORY not set when running as a test, uses the sandbox instead if [[ ! -z "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then cd "$BUILD_WORKSPACE_DIRECTORY" @@ -213,7 +214,7 @@ fi"""] for in_path, out_path in paths: contents.append(""" -in=$runfiles_dir/{in_path} +in=$(rlocation {in_path}) out={out_path} mkdir -p "$(dirname "$out")" @@ -245,11 +246,10 @@ fi )) contents.extend([ - "cd \"$runfiles_dir\"", "# Run the update scripts for all write_source_file deps", ]) for update_script in additional_update_scripts: - contents.append("./\"{update_script}\"".format(update_script = update_script.short_path)) + contents.append("./$(rlocation {update_script})".format(update_script = to_rlocation_path(ctx, update_script))) ctx.actions.write( output = updater, @@ -273,14 +273,17 @@ def _write_source_file_bat(ctx, paths): contents = ["""@rem @generated by @aspect_bazel_lib//:lib/private:write_source_file.bzl @echo off -set runfiles_dir=%cd% +SETLOCAL ENABLEEXTENSIONS +SETLOCAL ENABLEDELAYEDEXPANSION +set RUNFILES_MANIFEST_ONLY=1 if defined BUILD_WORKSPACE_DIRECTORY ( cd %BUILD_WORKSPACE_DIRECTORY% )"""] for in_path, out_path in paths: contents.append(""" -set in=%runfiles_dir%\\{in_path} +call :rlocation {in_path} in +set in=%in:/=\\% set out={out_path} if not defined BUILD_WORKSPACE_DIRECTORY ( @@ -288,25 +291,29 @@ if not defined BUILD_WORKSPACE_DIRECTORY ( @rem file's symlink it will get copied back into the source directory @rem during tests. Work around this in tests by deleting the target file @rem symlink before copying over it. - del %out% + if exist %out% del %out% ) echo Copying %in% to %out% in %cd% - if exist "%in%\\*" ( mkdir "%out%" >NUL 2>NUL robocopy "%in%" "%out%" /E >NUL ) else ( copy %in% %out% >NUL ) -""".format(in_path = in_path.replace("/", "\\"), out_path = out_path.replace("/", "\\"))) +""".format(in_path = in_path, out_path = out_path.replace("/", "\\"))) contents.extend([ - "cd %runfiles_dir%", "@rem Run the update scripts for all write_source_file deps", ]) for update_script in additional_update_scripts: - contents.append("call {update_script}".format(update_script = update_script.short_path)) + contents.extend([ + "set update_script=", + "call :rlocation {update_script} update_script".format(update_script = to_rlocation_path(ctx, update_script)), + "call %update_script%", + ]) + + contents.append(BATCH_RLOCATION_FUNCTION) ctx.actions.write( output = updater, @@ -331,7 +338,7 @@ def _write_source_file_impl(ctx): if ctx.attr.in_file and out_file: if DirectoryPathInfo in ctx.attr.in_file: in_path = "/".join([ - ctx.attr.in_file[DirectoryPathInfo].directory.short_path, + to_rlocation_path(ctx, ctx.attr.in_file[DirectoryPathInfo].directory), ctx.attr.in_file[DirectoryPathInfo].path, ]) runfiles.append(ctx.attr.in_file[DirectoryPathInfo].directory) @@ -339,7 +346,7 @@ def _write_source_file_impl(ctx): msg = "in file {} must provide files".format(ctx.attr.in_file.label) fail(msg) elif len(ctx.files.in_file) == 1: - in_path = ctx.files.in_file[0].short_path + in_path = to_rlocation_path(ctx, ctx.files.in_file[0]) else: msg = "in file {} must be a single file or a target that provides a DirectoryPathInfo".format(ctx.attr.in_file.label) fail(msg) diff --git a/lib/testing.bzl b/lib/testing.bzl index 1b6feb2e9..798e299b1 100644 --- a/lib/testing.bzl +++ b/lib/testing.bzl @@ -6,6 +6,7 @@ load("//lib:diff_test.bzl", "diff_test") load("//lib:jq.bzl", "jq") load("//lib:params_file.bzl", "params_file") load("//lib:utils.bzl", "default_timeout") +load("//lib:paths.bzl", "BASH_RLOCATION_PREFIX") def assert_contains(name, actual, expected, size = None, timeout = None, **kwargs): """Generates a test target which fails if the file doesn't contain the string. @@ -33,20 +34,21 @@ def assert_contains(name, actual, expected, size = None, timeout = None, **kwarg write_file( name = "_" + name, out = test_sh, - content = [ - "#!/usr/bin/env bash", + content = BASH_RLOCATION_PREFIX + [ "set -o errexit", - "grep --fixed-strings -f $1 $2", + "file1=$(rlocation $1)", + "file2=$(rlocation $2)", + "grep --fixed-strings -f $file1 $file2", ], ) native.sh_test( name = name, srcs = [test_sh], - args = ["$(rootpath %s)" % expected_file, "$(rootpath %s)" % actual], + args = ["$(rlocationpath %s)" % expected_file, "$(rlocationpath %s)" % actual], size = size, timeout = default_timeout(size, timeout), - data = [actual, expected_file], + data = [actual, expected_file, "@bazel_tools//tools/bash/runfiles"], **kwargs ) @@ -172,12 +174,11 @@ def assert_archive_contains(name, archive, expected, type = None, **kwargs): write_file( name = script_name, out = "assert_{}.sh".format(name), - content = [ - "#!/usr/bin/env bash", + content = BASH_RLOCATION_PREFIX + [ "actual=$(mktemp)", - "{} $1 > $actual".format(command), + "{} $(rlocation $1) > $actual".format(command), "# Grep exits 1 if no matches, which is success for this test.", - "if {} $2; then".format(grep), + "if {} $(rlocation $2); then".format(grep), " echo", " echo 'ERROR: above line(s) appeared in {} but are not present in the archive' $1".format(expected_name), " exit 1", @@ -188,8 +189,8 @@ def assert_archive_contains(name, archive, expected, type = None, **kwargs): native.sh_test( name = name, srcs = [script_name], - args = ["$(rootpath %s)" % archive, "$(rootpath %s)" % expected_name], - data = [archive, expected_name], + args = ["$(rlocationpath %s)" % archive, "$(rlocationpath %s)" % expected_name], + data = [archive, expected_name, "@bazel_tools//tools/bash/runfiles"], timeout = "short", **kwargs ) @@ -225,14 +226,13 @@ def assert_directory_contains(name, directory, expected, **kwargs): write_file( name = script_name, out = "assert_{}.sh".format(name), - content = [ - "#!/usr/bin/env bash", + content = BASH_RLOCATION_PREFIX + [ "actual=$(mktemp)", - "pushd $1 > /dev/null", + "pushd $(rlocation $1) > /dev/null", "find . -type l,f | cut -b 3- > $actual", "popd > /dev/null", "# Grep exits 1 if no matches, which is success for this test.", - "if {} $2; then".format(grep), + "if {} $(rlocation $2); then".format(grep), " echo", " echo 'ERROR: above line(s) appeared in {} but are not present in the directory' $1".format(expected_name), " exit 1", @@ -243,8 +243,8 @@ def assert_directory_contains(name, directory, expected, **kwargs): native.sh_test( name = name, srcs = [script_name], - args = ["$(rootpath %s)" % directory, "$(rootpath %s)" % expected_name], - data = [directory, expected_name], + args = ["$(rlocationpath %s)" % directory, "$(rlocationpath %s)" % expected_name], + data = [directory, expected_name, "@bazel_tools//tools/bash/runfiles"], timeout = "short", **kwargs ) diff --git a/lib/tests/README.md b/lib/tests/README.md new file mode 100644 index 000000000..5d41bddd9 --- /dev/null +++ b/lib/tests/README.md @@ -0,0 +1,41 @@ +## Running bazel-lib tests + +A test wrapper is available that runs all the permutations of runfiles: + +``` +cd +lib/tests/test.sh //lib/tests/... +``` + +This runs the following: +``` +cd +1. bazel test //lib/tests/... --enable_runfiles +2. bazel test //lib/tests/... --noenable_runfiles +3. lib/tests/test_with_run.sh //lib/tests/... --enable_runfiles +4. lib/tests/test_with_run.sh //lib/tests/... --noenable_runfiles +``` + +These four commands each give a different set of results. `bazel run` behaves differently +to `bazel test` in how it sets up the environment, so commands 3 and 4 give a more accurate +validation of tools used in this way. + +Note: On linux, commands 1 and 2 currently have the same behaviour (the --noenable_runfiles +flag appears to be ignored with bazel test), I have an issue requesting clarification. + +### bash scripts + +#### !This script makes changes to the source tree! +WARNING: test_with_run.sh causes data to be written to the source tree. The outputs don't appear to conflict in this repo. This is discussed here: https://github.com/bazelbuild/bazel/issues/3325. I've tried --run_under and can't get it to work on windows. + +test.sh uses git to undo changes between each test run. + +#### Exit code +- test_with_run.sh returns 1 if there are any failures and 0 if all tests pass +- test.sh does not have an exit code + +### windows wrappers +On windows, there are bat wrappers. %BAZEL_SH% must be set to bash + +- run all the permutations: `lib\tests\test.bat //lib/tests/...` +- or a single run: `lib\tests\test_with_run.bat //lib/tests/... --enable_runfiles` diff --git a/lib/tests/bats/BUILD.bazel b/lib/tests/bats/BUILD.bazel index 32743c133..ec4c13132 100644 --- a/lib/tests/bats/BUILD.bazel +++ b/lib/tests/bats/BUILD.bazel @@ -1,36 +1,44 @@ load("//lib:bats.bzl", "bats_test") +# these tests can take over 60s when running in parallel on windows (I don't know why, something related to bats-core) + bats_test( name = "basic", - size = "small", + size = "medium", srcs = [ "basic.bats", ], + # timeout on windows if run at same time as other bats tests + tags = ["exclusive"], ) bats_test( name = "env", - size = "small", + size = "medium", srcs = [ "env.bats", ], env = { "USE_BAZEL_VERSION": "latest", }, + # timeout on windows if run at same time as other bats tests + tags = ["exclusive"], ) bats_test( name = "args", - size = "small", + size = "medium", srcs = [ "basic.bats", ], args = ["--timing"], + # timeout on windows if run at same time as other bats tests + tags = ["exclusive"], ) bats_test( name = "env_expansion", - size = "small", + size = "medium", srcs = [ "env_expansion.bats", ], @@ -40,11 +48,17 @@ bats_test( env = { "DATA_PATH": "$(location :data.bin)", }, + # bats does not find data if runfiles are disabled + target_compatible_with = select({ + # incompatible with --noenable_runfiles + "@aspect_bazel_lib//lib:enable_runfiles": [], + "//conditions:default": ["@platforms//:incompatible"], + }), ) bats_test( name = "additional_lib", - size = "small", + size = "medium", srcs = [ "additional_lib.bats", ], diff --git a/lib/tests/copy_to_directory/BUILD.bazel b/lib/tests/copy_to_directory/BUILD.bazel index 988517151..bc9a6073a 100644 --- a/lib/tests/copy_to_directory/BUILD.bazel +++ b/lib/tests/copy_to_directory/BUILD.bazel @@ -156,7 +156,10 @@ sh_test( name = "case_6_test", timeout = "short", srcs = ["case6.sh"], - data = ["case_6"], + data = [ + "case_6", + "@bazel_tools//tools/bash/runfiles", + ], ) # Case 7: default settings diff --git a/lib/tests/copy_to_directory/case6.sh b/lib/tests/copy_to_directory/case6.sh index 3288b4a22..ad241ea9f 100755 --- a/lib/tests/copy_to_directory/case6.sh +++ b/lib/tests/copy_to_directory/case6.sh @@ -2,14 +2,23 @@ set -o errexit -o nounset -cd $TEST_SRCDIR -cd $TEST_WORKSPACE -cd $(dirname $TEST_BINARY) -cd case_6 +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +# https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: runfiles.bash initializer cannot find $f. An executable rule may have forgotten to expose it in the runfiles, or the binary may require RUNFILES_DIR to be set."; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- # A path that should be in the directory we have in data[] -path="f/f2/f2" +rpath="_main/lib/tests/copy_to_directory/case_6" +path=$(rlocation $rpath)/f/f2/f2 if [ ! -f "$path" ]; then echo >&2 "Expected $path to exist in runfiles" exit 1 fi + \ No newline at end of file diff --git a/lib/tests/copy_to_directory_bin_action/BUILD.bazel b/lib/tests/copy_to_directory_bin_action/BUILD.bazel index 29e2f0f4a..102a232af 100644 --- a/lib/tests/copy_to_directory_bin_action/BUILD.bazel +++ b/lib/tests/copy_to_directory_bin_action/BUILD.bazel @@ -47,12 +47,7 @@ pkg( out = "pkg", # RBE not happy with the symlinks in this test case tags = ["no-remote"], - target_compatible_with = select({ - # D:/a/bazel-lib/bazel-lib/lib/tests/copy_to_directory_bin_action/BUILD.bazel:36:4: - # declared output 'lib/tests/copy_to_directory_bin_action/pkg_symlink_0' is not a symlink - "@platforms//os:windows": ["@platforms//:incompatible"], - "//conditions:default": [], - }), + # works on all platforms as long as symlinks are enabled (see .bazelrc) use_declare_symlink = select({ "//lib/tests:allow_unresolved_symlinks": True, "//conditions:default": False, diff --git a/lib/tests/test.bat b/lib/tests/test.bat new file mode 100644 index 000000000..56bf50f81 --- /dev/null +++ b/lib/tests/test.bat @@ -0,0 +1,4 @@ +@set dir=%~dp0 +@set dir=%dir:\=/% +@echo %BAZEL_SH% -c "%dir%test.sh %*" +@%BAZEL_SH% -c "%dir%test.sh %*" diff --git a/lib/tests/test.sh b/lib/tests/test.sh new file mode 100644 index 000000000..0d3e2d9fd --- /dev/null +++ b/lib/tests/test.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set +x + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +diffs=$(git diff --name-only lib/tests) +if [ "$diffs" != "" ]; then + echo "ERROR: changes to test source tree prior to running tests:" + echo "${diffs[@]}" + echo commit or run 'git checkout lib/tests/*' to discard + exit 1 +fi + +mkdir test-out +quiet_opts="--noshow_progress --ui_event_filters=,+error,+fail --show_result=0 --logging=0" + +bazel clean >/dev/null 2>&1 +bazel test "$@" --enable_runfiles $quiet_opts > test-out/bazel_test_enable_runfiles.log 2>&1 +result=$(grep -m1 -E "Executed" test-out/bazel_test_enable_runfiles.log) +echo "[test enable runfiles ] $result" + +bazel clean >/dev/null 2>&1 +bazel test "$@" --noenable_runfiles $quiet_opts > test-out/bazel_test_noenable_runfiles.log 2>&1 +result=$(grep -m1 -E "Executed" test-out/bazel_test_noenable_runfiles.log) +echo "[test noenable runfiles] $result" + +bazel clean >/dev/null 2>&1 +$SCRIPT_DIR/test_with_run.sh "$@" --enable_runfiles > test-out/bazel_run_enable_runfiles.log 2>&1 +result=$(grep -m1 -E "Executed" test-out/bazel_run_enable_runfiles.log) +echo "[run enable runfiles ] $result" +git checkout lib/tests/ >/dev/null + +bazel clean >/dev/null 2>&1 +$SCRIPT_DIR/test_with_run.sh "$@" --noenable_runfiles > test-out/bazel_run_noenable_runfiles.log 2>&1 +result=$(grep -m1 -E "Executed" test-out/bazel_run_noenable_runfiles.log) +echo "[run noenable runfiles ] $result" +git checkout lib/tests/ >/dev/null diff --git a/lib/tests/test_with_run.bat b/lib/tests/test_with_run.bat new file mode 100644 index 000000000..647e5c7f4 --- /dev/null +++ b/lib/tests/test_with_run.bat @@ -0,0 +1,4 @@ +@set dir=%~dp0 +@set dir=%dir:\=/% +@echo %BAZEL_SH% -c "%dir%test_with_run.sh %*" +@%BAZEL_SH% -c "%dir%test_with_run.sh %*" diff --git a/lib/tests/test_with_run.sh b/lib/tests/test_with_run.sh new file mode 100644 index 000000000..f02136b6a --- /dev/null +++ b/lib/tests/test_with_run.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +target=${1} + +env=$(printenv | grep -E "^(RUNFILES|BUILD|TEST)_.*=") +if [ "$env" != "" ]; then + echo "WARNING: bazel env set outside this test harness:" + echo "${env[@]}" +fi +diffs=$(git diff --name-only lib/tests) +if [ "$diffs" != "" ]; then + echo "WARNING: changes to test source tree prior to running tests:" + echo "${diffs[@]}" + echo commit or run 'git checkout lib/tests/*' to discard +fi + +tests=$(eval "bazel query 'kind(\".*_test\", ${target} ${@:2})'" | tr -d '\r') + +failures=0 +runs=0 +passes=0 +skips=0 +quiet_opts="--noshow_progress --ui_event_filters=,+error,+fail --show_result=0 --logging=0" +test_opts="--skip_incompatible_explicit_targets" + +echo "running each target with:" +echo "bazel run $quiet_opts $test_opts $run_opts ${@:2}" +for test in ${tests} +do + runs=$((runs + 1)) + #echo "bazel run $test $quiet_opts $test_opts $run_opts ${@:2}" + out=$(bazel run $test $quiet_opts $test_opts $run_opts ${@:2} 2>&1) + status=$? + if [[ $status != 0 ]]; then + if [[ " ${out[@]} " =~ "ERROR: No targets found to run" ]]; then + skips=$((++skips)) + echo $test: skipped + else + failures=$((failures+1)) + echo "${out[@]}" + echo ----------------------------------------------------------------------------- + echo $test: fail $status + fi + else + #echo $test: pass + passes=$((++passes)) + fi +done + +echo "Executed $((runs-skips)) out of $runs tests: $passes tests pass, $failures fail and $skips were skipped." +if [[ $failures == 0 ]]; then + exit 0 +else + exit 1 +fi diff --git a/lib/tests/transitions/BUILD.bazel b/lib/tests/transitions/BUILD.bazel index db1f31b39..6df3688c3 100644 --- a/lib/tests/transitions/BUILD.bazel +++ b/lib/tests/transitions/BUILD.bazel @@ -34,6 +34,15 @@ platform( ], ) +platform( + name = "x86_64_windows", + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + "@io_bazel_rules_go//go/toolchain:cgo_off", # https://github.com/bazelbuild/rules_go/pull/3390 + ], +) + config_setting( name = "is_x86", constraint_values = [ @@ -117,6 +126,12 @@ go_binary( go_test( name = "test_transition_test", srcs = ["simple_test.go"], + target_compatible_with = select({ + # incompatible with --noenable_runfiles + # test harness says: panic: could not change to test directory + "@aspect_bazel_lib//lib:enable_runfiles": [], + "//conditions:default": ["@platforms//:incompatible"], + }), ) platform_transition_binary( @@ -131,64 +146,101 @@ platform_transition_binary( target_platform = "arm64_linux", ) +platform_transition_binary( + name = "transitioned_go_binary_x86_64_windows", + binary = ":test_transition_binary", + target_platform = "x86_64_windows", +) + platform_transition_test( name = "transitioned_go_test_x86_64", binary = ":test_transition_test", # only run this test on x86_64 platforms target_compatible_with = [ "@platforms//cpu:x86_64", - ], + "@platforms//os:linux", + ] + select({ + # incompatible with --noenable_runfiles + # test harness says: panic: could not change to test directory + "@aspect_bazel_lib//lib:enable_runfiles": [], + "//conditions:default": ["@platforms//:incompatible"], + }), target_platform = "x86_64_linux", ) platform_transition_test( name = "transitioned_go_test_arm64", binary = ":test_transition_test", - # only run this test on arm64 platforms + # only run this test on arm64 platformsd target_compatible_with = [ "@platforms//cpu:arm64", + "@platforms//os:linux", ], target_platform = "arm64_linux", ) +platform_transition_test( + name = "transitioned_go_test_x86_64_windows", + size = "small", + binary = ":test_transition_test", + # only run this test on x86_64 windows platforms + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + ], + target_platform = "x86_64_windows", +) + sh_test( name = "test_go_binary_is_x86_64", srcs = ["test_file_type_contains.sh"], args = [ - "$(rootpath :transitioned_go_binary_x86_64)", + "$(rlocationpath :transitioned_go_binary_x86_64)", "x86-64", ], - data = [":transitioned_go_binary_x86_64"], + data = [ + ":transitioned_go_binary_x86_64", + "@bazel_tools//tools/bash/runfiles" + ], ) sh_test( name = "test_go_binary_is_arm64", srcs = ["test_file_type_contains.sh"], args = [ - "$(rootpath :transitioned_go_binary_arm64)", + "$(rlocationpath :transitioned_go_binary_arm64)", "aarch64", ], - data = [":transitioned_go_binary_arm64"], + data = [ + ":transitioned_go_binary_arm64", + "@bazel_tools//tools/bash/runfiles" + ], ) sh_test( name = "test_go_test_is_x86_64", srcs = ["test_file_type_contains.sh"], args = [ - "$(rootpath :transitioned_go_test_x86_64)", + "$(rlocationpath :transitioned_go_test_x86_64)", "x86-64", ], - data = [":transitioned_go_test_x86_64"], + data = [ + ":transitioned_go_test_x86_64", + "@bazel_tools//tools/bash/runfiles" + ], ) sh_test( name = "test_go_test_is_arm64", srcs = ["test_file_type_contains.sh"], args = [ - "$(rootpath :transitioned_go_test_arm64)", + "$(rlocationpath :transitioned_go_test_arm64)", "aarch64", ], - data = [":transitioned_go_test_arm64"], + data = [ + ":transitioned_go_test_arm64", + "@bazel_tools//tools/bash/runfiles" + ], ) go_library( diff --git a/lib/tests/transitions/test_file_type_contains.sh b/lib/tests/transitions/test_file_type_contains.sh index d677a8696..679b9b3b8 100755 --- a/lib/tests/transitions/test_file_type_contains.sh +++ b/lib/tests/transitions/test_file_type_contains.sh @@ -6,4 +6,16 @@ set -o errexit -o nounset -o pipefail -file --dereference "$1" | grep -q "$2" +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +# https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: runfiles.bash initializer cannot find $f. An executable rule may have forgotten to expose it in the runfiles, or the binary may require RUNFILES_DIR to be set."; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- + +file --dereference "$(rlocation $1)" | grep -q "$2" diff --git a/lib/tests/write_source_files/write_source_file_test.bzl b/lib/tests/write_source_files/write_source_file_test.bzl index 0bebe6c13..0eb885566 100644 --- a/lib/tests/write_source_files/write_source_file_test.bzl +++ b/lib/tests/write_source_files/write_source_file_test.bzl @@ -3,8 +3,10 @@ load("//lib/private:directory_path.bzl", "DirectoryPathInfo") load("//lib/private:write_source_file.bzl", _write_source_file = "write_source_file") +load("//lib:windows_utils.bzl", "BATCH_RLOCATION_FUNCTION") +load("//lib:paths.bzl", "BASH_RLOCATION_FUNCTION", "to_rlocation_path") -def _write_source_file_test_impl_sh(ctx, in_file_path, out_file_path): +def _write_source_file_test_impl_sh(ctx, in_file_path, out_file_path, out_short_path): test = ctx.actions.declare_file( ctx.label.name + "_test.sh", ) @@ -14,37 +16,47 @@ def _write_source_file_test_impl_sh(ctx, in_file_path, out_file_path): contents.append(""" #!/usr/bin/env bash set -o errexit -o nounset -o pipefail +""") + + contents.append(BASH_RLOCATION_FUNCTION) + contents.append(""" assert_different() { - local in_file="${1}" - local out_file="${2}" - diff "${in_file}" "${out_file}" > /dev/null && (echo >&2 "Expected files to differ. in: ${in_file}, out: ${out_file}" && return -1) + diff "${1}" "${2}" > /dev/null && (echo >&2 "Expected files to differ. in: ${1}, out: ${2}" && return -1) return 0 } assert_same() { - local in_file="${1}" - local out_file="${2}" - diff "${in_file}" "${out_file}" || (echo >&2 "Expected files to be same. in: ${in_file}, out: ${out_file}" && return -1) + diff "${1}" "${2}" || (echo >&2 "Expected files to be same. in: ${1}, out: ${2}" && return -1) }""") contents.append(""" -# Check that in and out files are different -assert_different {in_file} {out_file} +# use rlocation for in_file and original state of out_file +in_path="$(rlocation {in_file})" +out_file_orig="$(rlocation {out_file_orig})" +# out_path may be in runfiles (test) or workspace (run) +out_path={out_short_path} +# remap out_path to workspace if invoked by bazel run +if [[ ! -z "${{BUILD_WORKSPACE_DIRECTORY:-}}" ]]; then + out_path="${{BUILD_WORKSPACE_DIRECTORY}}/${{out_path}}" +fi """.format( in_file = in_file_path, - out_file = out_file_path, + out_file_orig = out_file_path, + out_short_path = out_short_path, )) + contents.append(""" +# Check that in and original out files are different +assert_different $in_path $out_file_orig +""") + contents.append("""# Write to the source files -{write_source_files} -""".format(write_source_files = ctx.file.write_source_file_target.short_path)) +$(rlocation {write_source_files}) +""".format(write_source_files = to_rlocation_path(ctx, ctx.executable.write_source_file_target))) contents.append("""# Check that in and out files are the same -assert_same {in_file} {out_file}""".format( - in_file = in_file_path, - out_file = out_file_path, - )) +assert_same $in_path $out_path""") ctx.actions.write( output = test, @@ -54,49 +66,58 @@ assert_same {in_file} {out_file}""".format( return test -def _write_source_file_test_impl_bat(ctx, in_file_path, out_file_path): +def _write_source_file_test_impl_bat(ctx, in_file_path, out_file_path, out_short_path): test = ctx.actions.declare_file( ctx.label.name + "_test.bat", ) - # BUG: Windows is having trouble executing the symlink to the write_source_files executable, - # but it is able to execute the actual output script so point to that for now. - # - # What we would use if this bug didn't exist: - # write_source_files = ctx.executable.write_source_file_target.short_path.replace("/", "\\") - # - # Instead back out of the runfiles execution directory: - # write_to_source_files_test_test.bat.runfiles/aspect_bazel_lib - # And point to the output script. - write_source_files = "..\\..\\%s" % ctx.executable.write_source_file_target.basename + write_source_files = to_rlocation_path(ctx, ctx.executable.write_source_file_target) contents = [] contents.append(""" @rem @generated by @aspect_bazel_lib//:lib/tests/write_source_files:write_source_file_test.bzl @echo off +SETLOCAL ENABLEEXTENSIONS +SETLOCAL ENABLEDELAYEDEXPANSION +set RUNFILES_MANIFEST_ONLY=1 +@rem use rlocation for in_file and original state of out_file +call :rlocation {in_file} in_path +call :rlocation {out_file_orig} out_path_orig +@rem out_file may be in runfiles (test) or workspace (run) +set out_path={out_short_path} +@rem remap out_path to workspace if invoked by bazel run +if defined BUILD_WORKSPACE_DIRECTORY ( + set out_path=%BUILD_WORKSPACE_DIRECTORY%\\%out_path% +) @rem Check that in and out files are different -call :assert_different {in_file}, {out_file} +call :assert_different %in_path% %out_path_orig% if %errorlevel% neq 0 exit /b 1 """.format( - in_file = in_file_path.replace("/", "\\"), - out_file = out_file_path.replace("/", "\\"), + in_file = in_file_path, + out_file_orig = out_file_path, + out_short_path = out_short_path.replace("/", "\\"), )) contents.append(""" +@rem if invoked by bazel test --noenable_runfiles, create the outdir +for %%F in ("%out_path%") do set "out_dir=%%~dpF" +if not exist %out_dir% ( + mkdir %out_dir% +) @rem Write to the source files -call {write_source_files} +call :rlocation {write_source_files} write_source_files_path +call %write_source_files_path% if %errorlevel% neq 0 exit /b 1 -""".format(write_source_files = write_source_files)) +""".format( + write_source_files = write_source_files + )) contents.append(""" @rem Check that in and out files are the same -call :assert_same {in_file}, {out_file} +call :assert_same %in_path% %out_path% if %errorlevel% neq 0 exit /b 1 -""".format( - in_file = in_file_path.replace("/", "\\"), - out_file = out_file_path.replace("/", "\\"), - )) +""") contents.append(""" exit /b 0 @@ -114,6 +135,8 @@ echo Error: %~1 and %~2 are not the same exit /b 1 """) + contents.append(BATCH_RLOCATION_FUNCTION) + ctx.actions.write( output = test, is_executable = True, @@ -127,22 +150,24 @@ def _write_source_file_test_impl(ctx): if DirectoryPathInfo in ctx.attr.in_file: in_file = ctx.attr.in_file[DirectoryPathInfo].directory - in_file_path = "/".join([in_file.short_path, ctx.attr.in_file[DirectoryPathInfo].path]) + in_file_path = "/".join([to_rlocation_path(ctx, in_file), ctx.attr.in_file[DirectoryPathInfo].path]) else: if len(ctx.files.in_file) != 1: fail("in_file must be a single file or a target that provides a DirectoryPathInfo") in_file = ctx.files.in_file[0] - in_file_path = in_file.short_path + in_file_path = to_rlocation_path(ctx, in_file) + out_file_path = to_rlocation_path(ctx, ctx.file.out_file) + out_short_path = ctx.file.out_file.short_path if is_windows: - test = _write_source_file_test_impl_bat(ctx, in_file_path, ctx.file.out_file.short_path) + test = _write_source_file_test_impl_bat(ctx, in_file_path, out_file_path, out_short_path) else: - test = _write_source_file_test_impl_sh(ctx, in_file_path, ctx.file.out_file.short_path) + test = _write_source_file_test_impl_sh(ctx, in_file_path, out_file_path, out_short_path) return DefaultInfo( executable = test, runfiles = ctx.runfiles( - files = [ctx.executable.write_source_file_target, in_file, ctx.file.out_file], + files = [ctx.executable.write_source_file_target, in_file, ctx.file.out_file] + ctx.files._bash_runfiles, ), ) @@ -167,7 +192,12 @@ _write_source_file_test = rule( allow_files = True, mandatory = True, ), - "_windows_constraint": attr.label(default = "@platforms//os:windows"), + "_bash_runfiles": attr.label( + default = Label("@bazel_tools//tools/bash/runfiles"), + ), + "_windows_constraint": attr.label( + default = "@platforms//os:windows" + ), }, test = True, ) diff --git a/lib/windows_utils.bzl b/lib/windows_utils.bzl index f08f2d76b..e2e472b73 100644 --- a/lib/windows_utils.bzl +++ b/lib/windows_utils.bzl @@ -14,7 +14,7 @@ "Helpers for rules running on windows" -load("//lib/private:paths.bzl", "paths") +load("@aspect_bazel_lib//lib/private:paths.bzl", "paths") # cmd.exe function for looking up runfiles. # Equivalent of the BASH_RLOCATION_FUNCTION in paths.bzl. @@ -29,42 +29,404 @@ rem path and stores the result in a variable named . rem This function fails if the doesn't exist in mainifest rem file. :: Start of rlocation -goto :rlocation_end +goto :end :rlocation if "%~2" equ "" ( - echo>&2 ERROR: Expected two arguments for rlocation function. + echo ERROR: Expected two arguments for rlocation function. 1>&2 1>&2 exit 1 ) -if "%RUNFILES_MANIFEST_ONLY%" neq "1" ( - set %~2=%~1 - exit /b 0 + +:: if set outside this script, these variables may have unix paths. Update to windows. +if not "%RUNFILES_MANIFEST_FILE%"=="" ( + set RUNFILES_MANIFEST_FILE=!RUNFILES_MANIFEST_FILE:/=\! ) -if exist "%RUNFILES_DIR%" ( - set RUNFILES_MANIFEST_FILE=%RUNFILES_DIR%_manifest +if not "%RUNFILES_DIR%"=="" ( + set RUNFILES_DIR=!RUNFILES_DIR:/=\! ) -if "%RUNFILES_MANIFEST_FILE%" equ "" ( - set RUNFILES_MANIFEST_FILE=%~f0.runfiles\MANIFEST +if not "%RUNFILES_REPO_MAPPING%"=="" ( + set RUNFILES_REPO_MAPPING=!RUNFILES_REPO_MAPPING:/=\! ) -if not exist "%RUNFILES_MANIFEST_FILE%" ( - set RUNFILES_MANIFEST_FILE=%~f0.runfiles_manifest + +set GOT_RF=0 +if not "%RUNFILES_DIR%"=="" if exist "%RUNFILES_DIR%" (set GOT_RF=1) +if not "%RUNFILES_MANIFEST_FILE%"=="" if exist "%RUNFILES_MANIFEST_FILE%" (set GOT_RF=1) +if "%GOT_RF%"=="0" ( + if exist "%~f0.runfiles_manifest" ( + set "RUNFILES_MANIFEST_FILE=%~f0.runfiles_manifest" + ) else if exist "%~f0.runfiles\MANIFEST" ( + set "RUNFILES_MANIFEST_FILE=%~f0.runfiles\MANIFEST" + ) else if exist "%~f0.runfiles" ( + set "RUNFILES_DIR=%~f0.runfiles" + ) ) -set MF=%RUNFILES_MANIFEST_FILE:/=\% -if not exist "%MF%" ( - echo>&2 ERROR: Manifest file %MF% does not exist. - exit 1 + +if not exist "%RUNFILES_REPO_MAPPING%" ( + set RUNFILES_REPO_MAPPING=%~f0.repo_mapping ) -set runfile_path=%~1 -for /F "tokens=2* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /l /c:"!runfile_path! " "%MF%"`) do ( - set abs_path=%%i +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo RUNFILES_LIB_DEBUG=!RUNFILES_LIB_DEBUG! 1>&2 + echo RUNFILES_REPO_MAPPING=%RUNFILES_REPO_MAPPING% 1>&2 + echo RUNFILES_MANIFEST_FILE=%RUNFILES_MANIFEST_FILE% 1>&2 ) -if "!abs_path!" equ "" ( - echo>&2 ERROR: !runfile_path! not found in runfiles manifest - exit 1 + +:: we always set these; unlike the bash script, this always runs on windows +set _RLOCATION_ISABS_PATTERN="^[a-zA-Z]:[/\\]" +:: Windows paths are case insensitive and Bazel and MSYS2 capitalize differently, so we can't +:: assume that all paths are in the same native case. +set _RLOCATION_GREP_CASE_INSENSITIVE_ARGS=-i + +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): start 1>&2 +) + +REM Check if the path is absolute +if "%~f1"=="%1" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): absolute path, return 1>&2 + ) + set "convert=%~1" + set "%~2=!convert:/=\!" + exit /b 0 +) +REM Check if the path is not normalized +if "%1"=="../*" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: rlocation(%1^): path is not normalized 1>&2 + ) + exit /b 1 +) +REM Check if the path is absolute without drive name +if "%1:~0,1%"=="\\" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: rlocation(%1^): absolute path without drive name 1>&2 + ) + exit /b 1 +) + +if exist "%RUNFILES_REPO_MAPPING%" ( + set target_repo_apparent_name= + for /f "tokens=1 delims=/" %%a in ("%1") do ( + set "target_repo_apparent_name=%%a" + ) + rem Use -s to get an empty remainder if the argument does not contain a slash. + rem The repo mapping should not be applied to single segment paths, which may + rem be root symlinks. + set remainder= + for /f "tokens=2-99 delims=/" %%a in ("%1") do ( + set "remainder=%%a" + ) + if not "!remainder!"=="" ( + if "%2"=="" ( + call :runfiles_current_repository source_repo 2 + ) else ( + set "source_repo=%2" + ) + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): looking up canonical name for (!target_repo_apparent_name!^) from (!source_repo!^) in (%RUNFILES_REPO_MAPPING%^) 1>&2 + ) + set target_repo= + for /f "tokens=1-3 delims=," %%a in ('findstr /r /c:"^!source_repo!,!target_repo_apparent_name!," "%RUNFILES_REPO_MAPPING%"') do ( + set "target_repo=%%c" + ) + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): canonical name of target repo is (!target_repo!^) 1>&2 + ) + if not "!target_repo!"=="" ( + set "rlocation_path=!target_repo!/!remainder!" + ) else ( + set "rlocation_path=%1" + ) + ) else ( + set "rlocation_path=%1" + ) +) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): not using repository mapping (%RUNFILES_REPO_MAPPING%^) since it does not exist 1>&2 + ) + set "rlocation_path=%1" +) + +set "rlocation_checked_out=" +call :runfiles_rlocation_checked !rlocation_path! rlocation_checked_out +set "%~2=" +if not "%rlocation_checked_out%"=="" ( + set "%~2=%rlocation_checked_out:/=\%" +) +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%1^): returning (%~2) 1>&2 ) -set %~2=!abs_path! exit /b 0 -:rlocation_end :: End of rlocation + +:: :runfiles_current_repository +:: Returns the canonical name of the Bazel repository containing the script that +:: calls this function. +:: n: return the canonical name of the N-th caller (pass 1 for standard use cases) +:: result: variable name for the result +:: +:: Note: This function only works correctly with Bzlmod enabled. Without Bzlmod, +:: its return value is ignored if passed to rlocation. +:runfiles_current_repository +set "idx=%~1" +if "%idx%"=="" set "idx=1" + +set raw_caller_path= +for /f "tokens=%idx%" %%a in ("%~f0") do ( + set "raw_caller_path=%%a" +) + +if not exist "!raw_caller_path!" ( + set "caller_path=%~dp0\!raw_caller_path!" +) else ( + set "caller_path=!raw_caller_path!" +) + +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): caller's path is (!caller_path!^) 1>&2 +) + +set "rlocation_path=" + +if exist "%RUNFILES_MANIFEST_FILE%" ( + REM Escape caller_path for use in the findstr regex below. Also replace \ with / since the manifest + REM uses / as the path separator even on Windows. + set "normalized_caller_path=!caller_path:\=/!" + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): normalized caller's path is (!normalized_caller_path!^) 1>&2 + ) + for /f "tokens=1 delims= " %%a in ('findstr /r /c:"^[^ ]* !normalized_caller_path!$" "%RUNFILES_MANIFEST_FILE%"') do ( + set "rlocation_path=%%a" + ) + if "%rlocation_path%"=="" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) is not the target of an entry in the runfiles manifest (%RUNFILES_MANIFEST_FILE%^) 1>&2 + ) + REM The binary may also be run directly from bazel-bin or bazel-out. + set repository= + for /f "tokens=5 delims=/" %%a in ('echo %normalized_caller_path% ^| findstr /r /c:"(^|/)(bazel-out/[^/]+/bin|bazel-bin)/external/[^/]+/"') do ( 1>&2 + set "repository=%%a" + ) + if not "!repository!"=="" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) lies in repository (!repository!^) (parsed exec path^) 1>&2 + ) + set "%~2=!repository!" + ) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) lies in the main repository (parsed exec path^) 1>&2 + ) + set %~2="" + ) + exit /b 1 + ) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) is the target of (!rlocation_path!^) in the runfiles manifest 1>&2 + ) + ) +) + +if "!rlocation_path!"=="" if exist "%RUNFILES_DIR%" ( + set "normalized_caller_path=!caller_path:\=/!" + set "normalized_dir=!RUNFILES_DIR:/=\!" + rem if not "!_RLOCATION_GREP_CASE_INSENSITIVE_ARGS!"=="" ( + rem for /f "tokens=*" %%a in ('echo !normalized_caller_path! ^| tr "[:upper:]" "[:lower:]"') do ( 1>&2 + rem set "normalized_caller_path=%%a" + rem ) + rem for /f "tokens=*" %%a in ('echo !normalized_dir! ^| tr "[:upper:]" "[:lower:]"') do ( 1>&2 + rem set "normalized_dir=%%a" + rem ) + rem ) + if "!normalized_caller_path:~0,%normalized_dir:~0,-1%!"=="!normalized_dir!" ( + set "rlocation_path=!normalized_caller_path:~%normalized_dir:~0,-1%!" + set "rlocation_path=!rlocation_path:~1!" + ) + if "!rlocation_path!"=="" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) does not lie under the runfiles directory (!normalized_dir!^) 1>&2 + ) + REM The only shell script that is not executed from the runfiles directory (if it is populated) + REM is the sh_binary entrypoint. Parse its path under the execroot, using the last match to + REM allow for nested execroots (e.g. in Bazel integration tests). The binary may also be run + REM directly from bazel-bin. + rem for /f "tokens=5 delims=/" %%a in ('echo !normalized_caller_path! ^| findstr /r /c:"(^|/)(bazel-out/[^/]+/bin|bazel-bin)/external/[^/]+/"') do ( 1>&2 + rem set "repository=%%a" + rem ) + if not "!repository!"=="" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) lies in repository (!repository!^) (parsed exec path^) 1>&2 + ) + set "%~2=!repository!" + ) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): (!normalized_caller_path!^) lies in the main repository (parsed exec path^) 1>&2 + ) + set %~2="" + ) + exit /b 0 + ) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): (!caller_path!^) has path (!rlocation_path^) relative to the runfiles directory (%RUNFILES_DIR%:-^) 1>&2 + ) + ) +) + +if "!rlocation_path!"=="" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: runfiles_current_repository(!idx!^): cannot determine repository for (!caller_path!^) since neither the runfiles directory (%RUNFILES_DIR%:-^) nor the runfiles manifest (%RUNFILES_MANIFEST_FILE%:-^) exist 1>&2 + ) + exit /b 1 +) + +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!caller_path!^) corresponds to rlocation path (!rlocation_path!^) 1>&2 +) + +REM Normalize the rlocation_path to be of the form repo/pkg/file. +set "rlocation_path=!rlocation_path:_main/external/=!" +set "rlocation_path=!rlocation_path:_main/../=!" + +set repository= +for /f "tokens=1 delims=/" %%a in ("!rlocation_path!") do ( + set "repository=%%a" +) + +if "!repository!"=="_main" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!rlocation_path!^) lies in the main repository 1>&2 + ) + set %~2="" +) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: runfiles_current_repository(!idx!^): (!rlocation_path!^) lies in repository (!repository!^) 1>&2 + ) + set %~2=!repository! +) + +endlocal +exit /b +:: end of runfiles_current_repository + +:parent_directory +set "input=%~1" +:: make it look like absolute windows path, to allow pnx to remove child directory +for %%a in ("\%input:/=\%") do for %%b in ("%%~dpa\.") do set "parent=%%~pnxb" +:: remove first character +set parent=%parent:~1% +:: convert back to unix path +set "parent=%parent:\=/%" +set "%~2=%parent%" +exit /b 0 + +:runfiles_rlocation_checked +set input=%~1 +:: there may be both a manifest file and runfiles dir. Look in the manifest first, then runfiles. +if exist "%RUNFILES_MANIFEST_FILE%" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): looking in RUNFILES_MANIFEST_FILE (!RUNFILES_MANIFEST_FILE!^) 1>&2 + ) + set result= + for /f "tokens=2 delims= " %%a in ('findstr /b /c:"%~1 " "%RUNFILES_MANIFEST_FILE%"') do ( + set "result=%%a" + ) + if "!result!"=="" ( + REM If path references a runfile that lies under a directory that itself + REM is a runfile, then only the directory is listed in the manifest. Look + REM up all prefixes of path in the manifest and append the relative path + REM from the prefix if there is a match. + set "prefix=%~1" + set "prefix_result=" + set "new_prefix=" + :while + set new_prefix= + call :parent_directory !prefix! new_prefix + if "!new_prefix!"=="!prefix!" ( + goto :end_while + ) + set "prefix=!new_prefix!" + for /f "tokens=2 delims= " %%a in ('findstr /b /c:"!prefix! " %RUNFILES_MANIFEST_FILE%') do ( + set "prefix_result=%%a" + ) + if "!prefix_result!"=="" ( + echo INFO[runfiles.bat]: rlocation(%~1^): did not find (!prefix!^) looping on parent directory 1>&2 + goto :while + ) + call set "suffix=%%input:!prefix!=%%" + if "!suffix!"=="!prefix!" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: rlocation(%~1^): could not find suffix from (!prefix!^) 1>&2 + ) + goto :end_while + ) + set "candidate=!prefix_result!!suffix!" + set "candidate=!candidate:/=\!" + if exist "!candidate!" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): found in manifest as (!candidate!^) via prefix (!prefix!^) 1>&2 + ) + set %~2=!candidate! + exit /b 0 + ) + REM At this point, the manifest lookup of prefix has been successful, + REM but the file at the relative path given by the suffix does not + REM exist. We do not continue the lookup with a shorter prefix for two + REM reasons: + REM 1. Manifests generated by Bazel never contain a path that is a + REM prefix of another path. + REM 2. Runfiles libraries for other languages do not check for file + REM existence and would have returned the non-existent path. It seems + REM better to return no path rather than a potentially different, + REM non-empty path. + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): found in manifest as (!candidate!^) via prefix (!prefix!^), but file does not exist 1>&2 + ) + goto :end_while + :end_while + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): not found in manifest 1>&2 + ) + set %~2="" + exit /b 0 + ) else ( + if exist "!result!" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): found in manifest as (!result!^) 1>&2 + ) + set "%~2=!result!" + exit /b 0 + ) else ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): found in manifest as (!result!^), but file does not exist 1>&2 + ) + set %~2="" + exit /b 0 + ) + ) +) +if exist "!RUNFILES_DIR!\%~1" ( + if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo INFO[runfiles.bat]: rlocation(%~1^): found under RUNFILES_DIR (!RUNFILES_DIR!^), return 1>&2 + ) + set "%~2=!RUNFILES_DIR!\%~1" + exit /b 0 +) +if "!RUNFILES_LIB_DEBUG!"=="1" ( + echo ERROR[runfiles.bat]: cannot look up runfile "%~1" (RUNFILES_DIR="!RUNFILES_DIR!", RUNFILES_MANIFEST_FILE="!RUNFILES_MANIFEST_FILE!"^) 1>&2 +) +exit /b 1 +::end of runfiles_rlocation_checked + +:end +:: leave these variables set with forward slashes, for compatibility with any +:: bash runfile calls made downstream +if not "%RUNFILES_MANIFEST_FILE%"=="" ( + set RUNFILES_MANIFEST_FILE=%RUNFILES_MANIFEST_FILE:\=/% +) +if not "%RUNFILES_DIR%"=="" ( + set RUNFILES_DIR=%RUNFILES_DIR:\=/% +) +if not "%RUNFILES_REPO_MAPPING%"=="" ( + set RUNFILES_REPO_MAPPING=%RUNFILES_REPO_MAPPING:\=/% +) """ def create_windows_native_launcher_script(ctx, shell_script): @@ -85,12 +447,15 @@ def create_windows_native_launcher_script(ctx, shell_script): win_launcher = ctx.actions.declare_file(name + ".bat", sibling = shell_script) ctx.actions.write( output = win_launcher, - content = r"""@echo off + content = r"""@echo off 1>&2 SETLOCAL ENABLEEXTENSIONS SETLOCAL ENABLEDELAYEDEXPANSION set RUNFILES_MANIFEST_ONLY=1 +::set RUNFILES_LIB_DEBUG=1 {rlocation_function} call :rlocation "{sh_script}" run_script +:: convert output path to unix style +set "run_script=!run_script:\=/!" for %%a in ("{bash_bin}") do set "bash_bin_dir=%%~dpa" set PATH=%bash_bin_dir%;%PATH% set args=%* @@ -108,3 +473,9 @@ if defined args ( is_executable = True, ) return win_launcher + +# TODO: +# decide: should rlocation return backslash or forward slash paths? +# - users intending to call bash -c would prefer forward slashes +# - users writing native bat would prefer backslashes +# look at commented out lines, are they needed?