rules_batch
Bazel rules for Microsoft Batch.
Setup
bazel_dep(name = "rules_batch", version = "{version}")
Overview
rules_batch provides Bazel rules for building and testing Microsoft Batch
(.bat / .cmd) scripts. The rules handle runfiles propagation, dependency
tracking, and runtime path resolution so that batch scripts integrate cleanly
into Bazel builds.
The rule set consists of:
bat_binary-- declares an executable batch script.bat_test-- declares a test batch script.bat_library-- groups batch scripts and data for reuse as dependencies.
Example
load("@rules_batch//batch:bat_binary.bzl", "bat_binary")
load("@rules_batch//batch:bat_library.bzl", "bat_library")
bat_library(
name = "helpers",
srcs = ["helper.bat"],
)
bat_binary(
name = "tool",
srcs = ["main.bat"],
deps = [":helpers"],
)
Runfiles
To resolve runfiles paths at runtime, add @rules_batch//batch/runfiles to
your target's deps and use the runfiles preamble in your script:
call "%RLOCATION%" "workspace/path/to/file" RESULT_VAR
See the Runfiles page for the full preamble, repo mapping, and manifest discovery details.
Rules (rules_batch)
This repository provides Microsoft Batch rules for Bazel.
bat_binary
- Purpose: Declares an executable batch script with a runfiles-aware launcher.
- Sources: Exactly one
.bator.cmdinsrcs(the entry script). - Runfiles: Merges
deps,data, the entry script, and runfiles support from//batch/runfiles.
Runtime resolution uses the runfiles preamble to set the RLOCATION variable, then resolves paths via:
call "%RLOCATION%" "workspace/path/to/file" RESULT_VAR
bat_library
- Purpose: Groups batch scripts (and optional data) for reuse. There is no link step; libraries only bundle files and propagate
DefaultInforunfiles. - Sources: Any number of
.bat/.cmdfiles insrcs, plus optionaldataanddepson other libraries.
Depend on a library from bat_binary via deps so helper scripts appear next to the binary in the runfiles tree.
bat_library vs filegroup
| Use | Prefer |
|---|---|
Reusable batch helpers depended on by bat_binary / bat_library | bat_library |
| Arbitrary files in runfiles without batch-specific meaning | filegroup and data on bat_binary |
Both end up in runfiles once attached; the distinction is clarity and convention, not a separate mechanism.
Example
load("@rules_batch//batch:bat_binary.bzl", "bat_binary")
load("@rules_batch//batch:bat_library.bzl", "bat_library")
bat_library(
name = "helpers",
srcs = ["helper.bat"],
)
bat_binary(
name = "tool",
srcs = ["main.bat"],
deps = [":helpers"],
)
Use the module name from your bazel_dep in place of @rules_batch if needed.
bat_binary rule.
Rules
bat_binary
load("@rules_batch//batch:bat_binary.bzl", "bat_binary")
bat_binary(name, deps, srcs, data)
Declares an executable batch script.
The user script is symlinked as the executable entry point. Dependencies
declared via deps and data are merged into the runfiles tree.
To resolve runfiles at runtime, add a dependency on
@rules_batch//batch/runfiles and use the runfiles preamble in your script.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Dependencies (e.g. bat_library) merged into the executable runfiles. | List of labels | optional | [] |
| srcs | The batch script source file. Must be a singleton list. | List of labels | optional | [] |
| data | Data dependencies merged into the executable runfiles. | List of labels | optional | [] |
bat_test rule.
Rules
bat_test
load("@rules_batch//batch:bat_test.bzl", "bat_test")
bat_test(name, deps, srcs, data)
Declares a test batch script.
The user script is symlinked as the test entry point. Dependencies
declared via deps and data are merged into the runfiles tree.
To resolve runfiles at runtime, add a dependency on
@rules_batch//batch/runfiles and use the runfiles preamble in your script.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Dependencies (e.g. bat_library) merged into the executable runfiles. | List of labels | optional | [] |
| srcs | The batch script source file. Must be a singleton list. | List of labels | optional | [] |
| data | Data dependencies merged into the executable runfiles. | List of labels | optional | [] |
bat_library rule implementation.
Rules
bat_library
load("@rules_batch//batch:bat_library.bzl", "bat_library")
bat_library(name, deps, srcs, data)
Groups batch scripts and optional data for use as dependencies of bat_binary
or other bat_library targets. Batch has no link step; this rule only bundles
files and propagates runfiles, similar in spirit to sh_library.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | Other batch libraries whose scripts and runfiles are merged in. | List of labels | optional | [] |
| srcs | Batch script sources in this library. | List of labels | optional | [] |
| data | Additional runfiles (any files or targets with runfiles). | List of labels | optional | [] |
Providers
rules_batch defines two marker providers for type-safe dependency graphs:
| Provider | Advertised by | Required by |
|---|---|---|
BatInfo | bat_library | deps of bat_binary, bat_test, bat_library |
BatBinaryInfo | bat_binary, bat_test | -- |
These providers carry no fields; they exist so that deps attributes can
restrict the set of allowed targets to the appropriate rule types.
Load them from the public API:
load("@rules_batch//batch:bat_info.bzl", "BatInfo")
load("@rules_batch//batch:bat_binary_info.bzl", "BatBinaryInfo")
BatInfo provider definition.
Providers
BatInfo
load("@rules_batch//batch:bat_info.bzl", "BatInfo")
BatInfo()
A provider for batch library rules.
BatBinaryInfo provider definition.
Providers
BatBinaryInfo
load("@rules_batch//batch:bat_binary_info.bzl", "BatBinaryInfo")
BatBinaryInfo()
A provider for batch binary rules.
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.