diff --git a/lib/tests/windows_utils/BUILD.bazel b/lib/tests/windows_utils/BUILD.bazel index 99c4e628e..502cf586a 100644 --- a/lib/tests/windows_utils/BUILD.bazel +++ b/lib/tests/windows_utils/BUILD.bazel @@ -1,10 +1,48 @@ +load(":directory_runfiles_test.bzl", "windows_directory_runfiles_test") load(":exact_manifest_match_test.bzl", "windows_exact_manifest_match_test") +WINDOWS_ONLY = select({ + "@platforms//os:windows": [], + "//conditions:default": ["@platforms//:incompatible"], +}) + windows_exact_manifest_match_test( name = "exact_manifest_match_test", size = "small", - target_compatible_with = select({ - "@platforms//os:windows": [], - "//conditions:default": ["@platforms//:incompatible"], - }), + target_compatible_with = WINDOWS_ONLY, +) + +windows_directory_runfiles_test( + name = "explicit_manifest_test", + size = "small", + scenario = "explicit_manifest", + target_compatible_with = WINDOWS_ONLY, +) + +windows_directory_runfiles_test( + name = "explicit_directory_test", + size = "small", + scenario = "explicit_directory", + target_compatible_with = WINDOWS_ONLY, +) + +windows_directory_runfiles_test( + name = "adjacent_nested_manifest_test", + size = "small", + scenario = "adjacent_nested_manifest", + target_compatible_with = WINDOWS_ONLY, +) + +windows_directory_runfiles_test( + name = "adjacent_sibling_manifest_test", + size = "small", + scenario = "adjacent_sibling_manifest", + target_compatible_with = WINDOWS_ONLY, +) + +windows_directory_runfiles_test( + name = "adjacent_directory_test", + size = "small", + scenario = "adjacent_directory", + target_compatible_with = WINDOWS_ONLY, ) diff --git a/lib/tests/windows_utils/directory_runfiles_test.bzl b/lib/tests/windows_utils/directory_runfiles_test.bzl new file mode 100644 index 000000000..3b92ac8ad --- /dev/null +++ b/lib/tests/windows_utils/directory_runfiles_test.bzl @@ -0,0 +1,211 @@ +"Tests for Windows launcher runfiles lookup." + +load("//lib:paths.bzl", "BASH_RLOCATION_FUNCTION") +load("//lib:windows_utils.bzl", "create_windows_native_launcher_script") +load("//lib/private:paths.bzl", "paths") + +_SUCCESS_MARKER = "WINDOWS_LAUNCHER_RUNFILES_OK" +_DOWNSTREAM_MARKER = "WINDOWS_LAUNCHER_DOWNSTREAM_RLOCATION_OK" + +_SCENARIOS = { + "explicit_manifest": r""" +set "case_manifest=!test_root!\explicit.MANIFEST" +set "case_runfiles_dir=!test_root!\explicit.runfiles" +call :copy_runtime_runfiles "!case_runfiles_dir!" +if errorlevel 1 exit /b 1 +> "!case_runfiles_dir!\{shell_script_windows}" echo echo WRONG_RUNFILES_SOURCE +> "!case_runfiles_dir!\{downstream_windows}" echo WRONG_RUNFILES_SOURCE +call :write_manifest "!case_manifest!" +set "RUNFILES_MANIFEST_FILE=!case_manifest!" +set "RUNFILES_DIR=!case_runfiles_dir!" +set "RUNFILES_MANIFEST_ONLY=" +""", + "explicit_directory": r""" +set "case_runfiles_dir=!test_root!\explicit.runfiles" +call :copy_runtime_runfiles "!case_runfiles_dir!" +if errorlevel 1 exit /b 1 +> "!case_launcher!.runfiles_manifest" echo {shell_script_rlocation} !test_root!\stale-sibling-payload.sh +mkdir "!case_launcher!.runfiles" +> "!case_launcher!.runfiles\MANIFEST" echo {shell_script_rlocation} !test_root!\stale-nested-payload.sh +set "RUNFILES_MANIFEST_FILE=!test_root!\missing.MANIFEST" +set "RUNFILES_DIR=!case_runfiles_dir!" +set "RUNFILES_MANIFEST_ONLY=1" +""", + "adjacent_nested_manifest": r""" +mkdir "!case_launcher!.runfiles" +call :write_manifest "!case_launcher!.runfiles\MANIFEST" +> "!case_launcher!.runfiles_manifest" echo {shell_script_rlocation} !test_root!\stale-sibling-payload.sh +set "RUNFILES_MANIFEST_FILE=" +set "RUNFILES_DIR=" +set "RUNFILES_MANIFEST_ONLY=" +""", + "adjacent_sibling_manifest": r""" +call :write_manifest "!case_launcher!.runfiles_manifest" +set "RUNFILES_MANIFEST_FILE=" +set "RUNFILES_DIR=" +set "RUNFILES_MANIFEST_ONLY=" +""", + "adjacent_directory": r""" +set "case_runfiles_dir=!case_launcher!.runfiles" +call :copy_runtime_runfiles "!case_runfiles_dir!" +if errorlevel 1 exit /b 1 +set "RUNFILES_MANIFEST_FILE=" +set "RUNFILES_DIR=" +set "RUNFILES_MANIFEST_ONLY=1" +""", +} + +def _windows_directory_runfiles_test_impl(ctx): + downstream = ctx.actions.declare_file(ctx.label.name + "_downstream.txt") + ctx.actions.write( + output = downstream, + content = _DOWNSTREAM_MARKER + "\n", + ) + + shell_script = ctx.actions.declare_file(ctx.label.name + "_payload.sh") + ctx.actions.write( + output = shell_script, + content = "\n".join([ + "#!/usr/bin/env bash", + BASH_RLOCATION_FUNCTION, + 'downstream="$(rlocation "{}")"'.format(paths.to_rlocation_path(ctx, downstream)), + 'grep -Fqx "{}" "$downstream"'.format(_DOWNSTREAM_MARKER), + "echo {}".format(_SUCCESS_MARKER), + "", + ]), + is_executable = True, + ) + launcher = create_windows_native_launcher_script(ctx, shell_script) + + launcher_rlocation = paths.to_rlocation_path(ctx, launcher) + downstream_rlocation = paths.to_rlocation_path(ctx, downstream) + runfiles_bash_rlocation = paths.to_rlocation_path(ctx, ctx.file._runfiles_bash) + shell_script_rlocation = paths.to_rlocation_path(ctx, shell_script) + substitutions = { + "downstream_rlocation": downstream_rlocation, + "downstream_windows": downstream_rlocation.replace("/", "\\"), + "launcher_rlocation": launcher_rlocation, + "runfiles_bash_rlocation": runfiles_bash_rlocation, + "runfiles_bash_windows": runfiles_bash_rlocation.replace("/", "\\"), + "shell_script_rlocation": shell_script_rlocation, + "shell_script_windows": shell_script_rlocation.replace("/", "\\"), + "success_marker": _SUCCESS_MARKER, + } + scenario_setup = _SCENARIOS[ctx.attr.scenario].format(**substitutions) + + test = ctx.actions.declare_file(ctx.label.name + ".bat") + ctx.actions.write( + output = test, + content = "\r\n".join(r"""@echo off +SETLOCAL ENABLEEXTENSIONS +SETLOCAL ENABLEDELAYEDEXPANSION +goto :test_main + +:resolve_test_runfile +set "logical_path=%~1" +set "resolved_path=" +if defined RUNFILES_MANIFEST_FILE if exist "!RUNFILES_MANIFEST_FILE!" ( + set "test_manifest=!RUNFILES_MANIFEST_FILE:/=\!" + for /F "tokens=1,* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /l /c:"!logical_path! " "!test_manifest!"`) do ( + set "resolved_path=%%j" + ) +) +if not defined resolved_path if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\!logical_path:/=\!" ( + set "resolved_path=!RUNFILES_DIR!\!logical_path:/=\!" +) +if not defined resolved_path ( + echo>&2 ERROR: Test setup could not resolve !logical_path! + exit /b 1 +) +set "resolved_path=!resolved_path:/=\!" +set "%~2=!resolved_path!" +exit /b 0 + +:copy_runfile +set "runfile_source=%~1" +set "runfile_destination=%~2" +for %%i in ("!runfile_destination!") do if not exist "%%~dpi" mkdir "%%~dpi" +copy /y "!runfile_source!" "!runfile_destination!" >NUL +if errorlevel 1 ( + echo>&2 ERROR: Could not copy !runfile_source! to !runfile_destination! + exit /b 1 +) +exit /b 0 + +:copy_runtime_runfiles +set "runtime_runfiles_dir=%~1" +call :copy_runfile "!payload!" "!runtime_runfiles_dir!\{shell_script_windows}" +if errorlevel 1 exit /b 1 +call :copy_runfile "!downstream!" "!runtime_runfiles_dir!\{downstream_windows}" +if errorlevel 1 exit /b 1 +call :copy_runfile "!runfiles_bash!" "!runtime_runfiles_dir!\{runfiles_bash_windows}" +exit /b !ERRORLEVEL! + +:write_manifest +set "manifest_destination=%~1" +> "!manifest_destination!" echo {shell_script_rlocation} !payload! +>> "!manifest_destination!" echo {downstream_rlocation} !downstream! +>> "!manifest_destination!" echo {runfiles_bash_rlocation} !runfiles_bash! +exit /b 0 + +:test_main +call :resolve_test_runfile "{launcher_rlocation}" launcher +if errorlevel 1 exit /b 1 +call :resolve_test_runfile "{shell_script_rlocation}" payload +if errorlevel 1 exit /b 1 +call :resolve_test_runfile "{downstream_rlocation}" downstream +if errorlevel 1 exit /b 1 +call :resolve_test_runfile "{runfiles_bash_rlocation}" runfiles_bash +if errorlevel 1 exit /b 1 + +set "test_root=%TEST_TMPDIR:/=\%\windows-launcher-{scenario}" +if exist "!test_root!" rmdir /s /q "!test_root!" +mkdir "!test_root!" +set "case_launcher=!test_root!\launcher.bat" +copy /y "!launcher!" "!case_launcher!" >NUL +if errorlevel 1 exit /b 1 + +{scenario_setup} + +set "case_output=%TEST_TMPDIR:/=\%\{scenario}-output.txt" +call "!case_launcher!" > "!case_output!" 2>&1 +if errorlevel 1 ( + echo>&2 ERROR: Launcher failed for {scenario} + type "!case_output!" 1>&2 + exit /b 1 +) +%SYSTEMROOT%\system32\findstr.exe /c:"{success_marker}" "!case_output!" >NUL +if errorlevel 1 ( + echo>&2 ERROR: Launcher did not execute the runfile for {scenario} + type "!case_output!" 1>&2 + exit /b 1 +) +exit /b 0 +""".format( + scenario = ctx.attr.scenario, + scenario_setup = scenario_setup, + **substitutions + ).splitlines()), + is_executable = True, + ) + + return [DefaultInfo( + executable = test, + runfiles = ctx.runfiles(files = [ctx.file._runfiles_bash, downstream, launcher, shell_script]), + )] + +windows_directory_runfiles_test = rule( + attrs = { + "scenario": attr.string( + mandatory = True, + values = _SCENARIOS.keys(), + ), + "_runfiles_bash": attr.label( + allow_single_file = True, + default = Label("@bazel_tools//tools/bash/runfiles:runfiles"), + ), + }, + implementation = _windows_directory_runfiles_test_impl, + test = True, + toolchains = ["@bazel_tools//tools/sh:toolchain_type"], +) diff --git a/lib/windows_utils.bzl b/lib/windows_utils.bzl index 64683b27d..c53ad0f2c 100644 --- a/lib/windows_utils.bzl +++ b/lib/windows_utils.bzl @@ -26,8 +26,8 @@ rem Usage of rlocation function: rem call :rlocation rem The rlocation function maps the given to its absolute rem path and stores the result in a variable named . -rem This function fails if the doesn't exist in mainifest -rem file. +rem This function fails if the cannot be found in either +rem the runfiles manifest or directory. :: Start of rlocation goto :rlocation_end :rlocation @@ -35,34 +35,54 @@ if "%~2" equ "" ( echo>&2 ERROR: Expected two arguments for rlocation function. exit 1 ) -if "%RUNFILES_MANIFEST_ONLY%" neq "1" ( - set %~2=%~1 - exit /b 0 +set "MF=" +set "RF=" +if defined RUNFILES_MANIFEST_FILE if exist "!RUNFILES_MANIFEST_FILE!" ( + set "MF=!RUNFILES_MANIFEST_FILE:/=\!" ) -if exist "%RUNFILES_DIR%" ( - set RUNFILES_MANIFEST_FILE=%RUNFILES_DIR%_manifest +if not defined MF if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\" ( + set "RF=!RUNFILES_DIR:/=\!" ) -if "%RUNFILES_MANIFEST_FILE%" equ "" ( - set RUNFILES_MANIFEST_FILE=%~f0.runfiles\MANIFEST +if not defined MF if not defined RF if exist "%~f0.runfiles\MANIFEST" ( + set "MF=%~f0.runfiles\MANIFEST" ) -if not exist "%RUNFILES_MANIFEST_FILE%" ( - set RUNFILES_MANIFEST_FILE=%~f0.runfiles_manifest +if not defined MF if not defined RF if exist "%~f0.runfiles_manifest" ( + set "MF=%~f0.runfiles_manifest" ) -set MF=%RUNFILES_MANIFEST_FILE:/=\% -if not exist "%MF%" ( - echo>&2 ERROR: Manifest file %MF% does not exist. - exit 1 +if not defined MF if not defined RF if exist "%~f0.runfiles\" ( + set "RF=%~f0.runfiles" ) -set runfile_path=%~1 -for /F "tokens=2* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /b /l /c:"!runfile_path! " "%MF%"`) do ( - set abs_path=%%i +if defined MF ( + set "RUNFILES_DIR=" + set "RUNFILES_MANIFEST_FILE=!MF:\=/!" + set "RUNFILES_MANIFEST_ONLY=1" + set "runfile_path=%~1" + set "abs_path=" + for /F "tokens=2* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /b /l /c:"!runfile_path! " "!MF!"`) do ( + set "abs_path=%%i" + ) + if "!abs_path!" equ "" ( + echo>&2 ERROR: !runfile_path! not found in runfiles manifest + exit 1 + ) + set "%~2=!abs_path!" + exit /b 0 ) -if "!abs_path!" equ "" ( - echo>&2 ERROR: !runfile_path! not found in runfiles manifest - exit 1 +if defined RF ( + set "RUNFILES_MANIFEST_FILE=" + set "RUNFILES_DIR=!RF:\=/!" + set "RUNFILES_MANIFEST_ONLY=" + set "runfile_path=%~1" + set "abs_path=!RF!\!runfile_path:/=\!" + if not exist "!abs_path!" ( + echo>&2 ERROR: !runfile_path! not found under runfiles directory !RF! + exit 1 + ) + set "%~2=!abs_path!" + exit /b 0 ) -set %~2=!abs_path! -exit /b 0 +echo>&2 ERROR: Cannot find a runfiles manifest or directory. +exit 1 :rlocation_end :: End of rlocation """ @@ -88,9 +108,9 @@ def create_windows_native_launcher_script(ctx, shell_script): content = "\r\n".join(r"""@echo off SETLOCAL ENABLEEXTENSIONS SETLOCAL ENABLEDELAYEDEXPANSION -set RUNFILES_MANIFEST_ONLY=1 {rlocation_function} call :rlocation "{sh_script}" run_script +set "run_script=!run_script:\=/!" for %%a in ("{bash_bin}") do set "bash_bin_dir=%%~dpa" set PATH=%bash_bin_dir%;%PATH% set args=%*