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
Utility for resolving Bazel runfiles paths at runtime in batch scripts.
Setup
Add the runfiles target to your bat_binary or bat_test dependencies:
bat_binary(
name = "my_tool",
srcs = ["my_tool.bat"],
deps = ["@rules_batch//batch/runfiles"],
)
Preamble
Copy-paste this block at the top of your script (after setlocal enabledelayedexpansion) to locate runfiles.bat and set the RLOCATION variable:
@REM --- begin runfiles.bat initialization v1 ---
set "_rf=rules_batch/batch/runfiles/runfiles.bat"
set "RLOCATION="
if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\!_rf:/=\!" set "RLOCATION=!RUNFILES_DIR!\!_rf:/=\!"
if not defined RLOCATION if exist "%~f0.runfiles\!_rf:/=\!" (
set "RUNFILES_DIR=%~f0.runfiles"
set "RLOCATION=!RUNFILES_DIR!\!_rf:/=\!"
)
if not defined RLOCATION (
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 (
if exist "!RUNFILES_DIR!\MANIFEST" (set "_rf_mf=!RUNFILES_DIR!\MANIFEST") else if exist "!RUNFILES_DIR!_manifest" set "_rf_mf=!RUNFILES_DIR!_manifest"
)
if not defined _rf_mf for %%m in ("%~f0.runfiles\MANIFEST" "%~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:/=\!"
for /F "tokens=1,* usebackq" %%i in ("!_rf_mf!") do if not defined RLOCATION (
set "_k=%%i"
if "%%i" equ "!_rf!" (set "RLOCATION=%%j") else if "!_k:~-28!" equ "/batch/runfiles/runfiles.bat" set "RLOCATION=%%j"
)
)
if defined _rf_mf set "RUNFILES_MANIFEST_FILE=!_rf_mf!"
set "_rf_mf="
set "_k="
)
if not defined RLOCATION (echo>&2 ERROR: cannot find !_rf! & exit /b 1)
set "_rf="
@REM --- end runfiles.bat initialization v1 ---
After the preamble, %RLOCATION% points to runfiles.bat and you can resolve runfiles:
call "%RLOCATION%" "my_workspace/data/config.txt" CONFIG_PATH
echo Config is at: %CONFIG_PATH%
The preamble tries three strategies in order:
- Directory lookup via
RUNFILES_DIR-- fast path when the runfiles tree exists. - Sibling
.runfilesdirectory next to the script (%~f0.runfiles). - Manifest scan -- reads the manifest file line-by-line to find the
runfiles.batentry. A suffix fallback matches any canonical repo prefix ending in/batch/runfiles/runfiles.batto handle Bzlmod version suffixes.
Full example
@echo off
setlocal enableextensions enabledelayedexpansion
@REM --- begin runfiles.bat initialization v1 ---
set "_rf=rules_batch/batch/runfiles/runfiles.bat"
set "RLOCATION="
if defined RUNFILES_DIR if exist "!RUNFILES_DIR!\!_rf:/=\!" set "RLOCATION=!RUNFILES_DIR!\!_rf:/=\!"
if not defined RLOCATION if exist "%~f0.runfiles\!_rf:/=\!" (
set "RUNFILES_DIR=%~f0.runfiles"
set "RLOCATION=!RUNFILES_DIR!\!_rf:/=\!"
)
if not defined RLOCATION (
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 (
if exist "!RUNFILES_DIR!\MANIFEST" (set "_rf_mf=!RUNFILES_DIR!\MANIFEST") else if exist "!RUNFILES_DIR!_manifest" set "_rf_mf=!RUNFILES_DIR!_manifest"
)
if not defined _rf_mf for %%m in ("%~f0.runfiles\MANIFEST" "%~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:/=\!"
for /F "tokens=1,* usebackq" %%i in ("!_rf_mf!") do if not defined RLOCATION (
set "_k=%%i"
if "%%i" equ "!_rf!" (set "RLOCATION=%%j") else if "!_k:~-28!" equ "/batch/runfiles/runfiles.bat" set "RLOCATION=%%j"
)
)
if defined _rf_mf set "RUNFILES_MANIFEST_FILE=!_rf_mf!"
set "_rf_mf="
set "_k="
)
if not defined RLOCATION (echo>&2 ERROR: cannot find !_rf! & exit /b 1)
set "_rf="
@REM --- end runfiles.bat initialization v1 ---
call "%RLOCATION%" "my_workspace/data/config.txt" CONFIG_PATH
echo Config is at: %CONFIG_PATH%
Repo mapping
When Bazel provides a _repo_mapping file in the runfiles manifest, rlocation automatically translates the first path segment (the apparent repo name) to the canonical runfiles directory name before the manifest lookup. This supports both the standard format and the compact wildcard format introduced by --incompatible_compact_repo_mapping_manifest in Bazel 9.
The translation uses the source repository to determine which view of the repo mapping applies. By default, the main repository is assumed (empty source repo). An optional third argument can override this:
@REM Default: resolves using the main repo's mapping (most common case).
call "%RLOCATION%" "my_dep/data/file.txt" RESULT
@REM Explicit: resolves using +my_ext+my_repo's mapping.
call "%RLOCATION%" "my_dep/data/file.txt" RESULT "+my_ext+my_repo"
In practice, the third argument is almost never needed. Batch scripts live in the main repository in the vast majority of cases, and the default covers that. The explicit form exists for the rare case where a batch script in an external repository needs to resolve paths from its own repo mapping context.
Exporting environment variables
Before spawning child processes that depend on runfiles (e.g. tools built with rules_venv), call :runfiles_export_envvars to ensure both RUNFILES_DIR and RUNFILES_MANIFEST_FILE are set:
call :runfiles_export_envvars
if errorlevel 1 (
echo>&2 ERROR: runfiles environment not available
exit /b 1
)
The preamble already sets whichever variable it discovers during initialization (mirroring the shell init block). This function fills in the other variable so children see a consistent pair:
- If only
RUNFILES_DIRis set, derivesRUNFILES_MANIFEST_FILEfromRUNFILES_DIR\MANIFESTorRUNFILES_DIR_manifest. - If only
RUNFILES_MANIFEST_FILEis set, strips the\MANIFESTor_manifestsuffix to deriveRUNFILES_DIR. - If both are already set and valid, no changes are made.
- Returns
exit /b 1if neither variable points to a valid path.
This mirrors runfiles_export_envvars from rules_shell.
Inline mode
Instead of calling runfiles.bat externally, you can concatenate or paste its content into your script at build time. The :rlocation subroutine is then available as a local label call:
call :rlocation "my_workspace/data/config.txt" CONFIG_PATH
echo Config is at: !CONFIG_PATH!
When inlined, only the subroutine block (between goto :_rl_end and :_rl_end) is active. The standalone entry point at the bottom of the file is unreachable because the user's script never falls through to it. Intermediate variables use a _rl_ prefix to minimize collisions.