Runfiles
runfiles.bat resolves Bazel runfiles paths from batch scripts, the way runfiles.bash does for shell scripts.
Setup
Add the runfiles target to the deps of your bat_binary or bat_test:
bat_binary(
name = "my_tool",
srcs = ["my_tool.bat"],
data = ["data/config.txt"],
deps = ["@rules_batch//batch/runfiles"],
)
Then paste this block at the top of your script, after setlocal enabledelayedexpansion. It locates runfiles.bat and stores its path in RLOCATION:
@REM --- begin runfiles.bat initialization v3 ---
set "_rf=batch/runfiles/runfiles.bat"
if not defined RUNFILES_DIR if exist "%~f0.runfiles\" set "RUNFILES_DIR=%~f0.runfiles"
set "_rf_mf="
if defined RUNFILES_MANIFEST_FILE if exist "!RUNFILES_MANIFEST_FILE!" set "_rf_mf=!RUNFILES_MANIFEST_FILE!"
if not defined _rf_mf if defined RUNFILES_DIR for %%m in ("!RUNFILES_DIR!\MANIFEST" "!RUNFILES_DIR!_manifest") do if not defined _rf_mf if exist "%%~m" set "_rf_mf=%%~m"
if not defined _rf_mf for %%m in ("%~f0.runfiles_manifest" "%~f0.exe.runfiles_manifest") do if not defined _rf_mf if exist "%%~m" set "_rf_mf=%%~m"
if defined _rf_mf (set "_rf_mf=!_rf_mf:/=\!" & set "RUNFILES_MANIFEST_FILE=!_rf_mf!")
@REM Map the apparent repo name rules_batch to its canonical runfiles directory.
set "_rf_rm="
if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\_repo_mapping" set "_rf_rm=!RUNFILES_DIR!\_repo_mapping"
if not defined _rf_rm if defined _rf_mf for /F "usebackq tokens=1,*" %%i in (`%SYSTEMROOT%\system32\findstr.exe /b /l /c:"_repo_mapping " "!_rf_mf!" 2^>nul`) do set "_rf_rm=%%j"
set "_rf_c="
if defined _rf_rm for /F "usebackq delims=" %%L in ("!_rf_rm:/=\!") do if not defined _rf_c (
set "_rf_l=%%L"
if not "!_rf_l:,rules_batch,=!"=="!_rf_l!" (set "_rf_c=!_rf_l:*,=!" & set "_rf_c=!_rf_c:*,=!")
)
if not defined _rf_c set "_rf_c=rules_batch"
set "_rf=!_rf_c!/!_rf!"
set "RLOCATION="
if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\!_rf:/=\!" set "RLOCATION=!RUNFILES_DIR!\!_rf:/=\!"
if not defined RLOCATION if defined _rf_mf for /F "usebackq tokens=1,*" %%i in (`%SYSTEMROOT%\system32\findstr.exe /b /l /c:"!_rf! " "!_rf_mf!" 2^>nul`) do if not defined RLOCATION set "RLOCATION=%%j"
if not defined RLOCATION (echo>&2 ERROR: cannot find !_rf! in runfiles & exit /b 1)
set "RLOCATION=!RLOCATION:/=\!"
set "_rf=" & set "_rf_mf=" & set "_rf_rm=" & set "_rf_c=" & set "_rf_l="
@REM --- end runfiles.bat initialization v3 ---
The block works under bazel test, bazel run, and when the built script is started directly from bazel-bin. If your MODULE.bazel renames the dependency with repo_name, replace ,rules_batch, in the block with that name.
Resolving paths
call "%RLOCATION%" "my_module/data/config.txt" CONFIG_PATH
if errorlevel 1 exit /b 1
type "%CONFIG_PATH%"
call "%RLOCATION%" PATH RESULT_VAR [SOURCE_REPO]
PATHisrepo/package/file, whererepois the name you use for that repository inMODULE.bazel(your own module's name for files in the main repository)./and\both work as separators. Files inside directory outputs resolve as well. Lookups are case sensitive.- On success
RESULT_VARholds the absolute, backslash separated location and the exit code is0. On failureRESULT_VARis empty, a message goes to stderr, and the exit code is1. SOURCE_REPOis only needed when the calling script lives in an external repository and must resolve names from that repository's point of view; pass its canonical name, for example+my_ext+my_repo. Batch cannot detect the calling script's repository on its own, so the default is the main repository.
Child processes
Scripts started by yours that use a runfiles library themselves need RUNFILES_DIR and RUNFILES_MANIFEST_FILE. Before starting them, make sure both are set:
call "%RLOCATION%" runfiles_export_envvars
if errorlevel 1 exit /b 1
It fills in whichever variable is missing from the other one and returns 1 if neither points to anything usable.
Inline mode
Instead of the preamble you can append runfiles.bat to your script at build time and call the labels directly. The script must exit /b before the end so execution does not run into the appended library.
genrule(
name = "my_tool_bundled",
srcs = ["my_tool_body.bat", "@rules_batch//batch/runfiles:runfiles.bat"],
outs = ["my_tool_bundled.bat"],
cmd = "cat $(SRCS) > $@",
)
call :rlocation "my_module/data/config.txt" CONFIG_PATH
call :runfiles_export_envvars
Both calls run in their own scope and only write their results to your environment, plus a few _rl_RM_* cache variables.
Examples
The examples module contains a working copy of each pattern, run both as tests and directly from bazel-bin in CI:
| Example | Shows |
|---|---|
greet.bat | The preamble and call "%RLOCATION%". |
inline_greet_body.bat | Inline mode with the genrule above. |
parent.bat / child.bat | runfiles_export_envvars before starting a script that uses runfiles itself. |
Limitations
- Paths containing
!cannot be resolved. - Batch files must use CRLF line endings; cmd fails to find labels in files with LF endings. Add
*.bat text eol=crlfto your.gitattributes. - The repository used for name translation defaults to the main repository; pass
SOURCE_REPOfrom scripts in external repositories.