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 .bat or .cmd in srcs (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 DefaultInfo runfiles.
  • Sources: Any number of .bat / .cmd files in srcs, plus optional data and deps on 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

UsePrefer
Reusable batch helpers depended on by bat_binary / bat_librarybat_library
Arbitrary files in runfiles without batch-specific meaningfilegroup 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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsDependencies (e.g. bat_library) merged into the executable runfiles.List of labelsoptional[]
srcsThe batch script source file. Must be a singleton list.List of labelsoptional[]
dataData dependencies merged into the executable runfiles.List of labelsoptional[]

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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsDependencies (e.g. bat_library) merged into the executable runfiles.List of labelsoptional[]
srcsThe batch script source file. Must be a singleton list.List of labelsoptional[]
dataData dependencies merged into the executable runfiles.List of labelsoptional[]

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

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
depsOther batch libraries whose scripts and runfiles are merged in.List of labelsoptional[]
srcsBatch script sources in this library.List of labelsoptional[]
dataAdditional runfiles (any files or targets with runfiles).List of labelsoptional[]

Providers

rules_batch defines two marker providers for type-safe dependency graphs:

ProviderAdvertised byRequired by
BatInfobat_librarydeps of bat_binary, bat_test, bat_library
BatBinaryInfobat_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]
  • PATH is repo/package/file, where repo is the name you use for that repository in MODULE.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_VAR holds the absolute, backslash separated location and the exit code is 0. On failure RESULT_VAR is empty, a message goes to stderr, and the exit code is 1.
  • SOURCE_REPO is 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:

ExampleShows
greet.batThe preamble and call "%RLOCATION%".
inline_greet_body.batInline mode with the genrule above.
parent.bat / child.batrunfiles_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=crlf to your .gitattributes.
  • The repository used for name translation defaults to the main repository; pass SOURCE_REPO from scripts in external repositories.