4.2.5 DefaultInfo & Runfiles
DefaultInfo is the default public face of a target. A rule may declare many actions and internal files, but DefaultInfo answers the questions most callers ask first: which files does this target build by default, which file should Bazel run if it is executable, and which runtime files must be present when that executable starts.1 That makes it the handoff between the rule skeleton from 4.2.1 Rule Function, the action outputs from 4.2.2 Actions, and the runnable target contract expanded in 4.2.6 Executable & Test Rules.
The action's inputs produce outputs during the build. They do not automatically become runtime files.
Default Outputs
DefaultInfo.files is a depset of File objects representing the default outputs built when a target is requested on the command line.2 If a rule implementation does not return DefaultInfo, or returns one without files, Bazel defaults this field to all predeclared outputs.3 For a rule with derived outputs, returning files explicitly is usually clearer because it says which generated artifacts are part of the target's normal contract.
def _manifest_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + ".txt")
ctx.actions.write(
output = out,
content = "name = %s\n" % ctx.label.name,
)
return [DefaultInfo(files = depset([out]))]
The depset matters for the same reason it mattered in 4.1.5 depset vs list: default outputs may be consumed through other providers and action inputs, so the rule should preserve Bazel's graph-shaped data model instead of flattening too early.4 Files that are not in DefaultInfo.files can still be useful, but they need another route: a custom provider, an output group, or direct use by another action. Named output groups are covered separately in 4.2.9 OutputGroupInfo & Output Groups.
Build Inputs Are Not Runtime Files
Action inputs are the files needed to produce an output. Runfiles are the files needed when an executable runs. The same physical file can be both, but the contracts are different: action inputs are visible to the build action, while runfiles are staged for bazel run, bazel test, or for tools used by later actions.5 This is the rule-authoring version of the lesson from 2.3.2 Sandboxing: Bazel only makes declared files visible in the context where they were declared. The lower-level mechanics of how those prepared directories are assembled come later in 5.8 Sandboxing.
Create a runfiles object with ctx.runfiles() and pass it through DefaultInfo(runfiles = ...).6
def _viewer_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(
output = out,
content = "#!/usr/bin/env bash\necho viewer ready\n",
is_executable = True,
)
runfiles = ctx.runfiles(files = ctx.files.data)
return [DefaultInfo(
files = depset([out]),
executable = out,
runfiles = runfiles,
)]
ctx.runfiles(files = ...) adds direct runtime files. ctx.runfiles(transitive_files = ...) adds a depset of runtime files, and that depset should use the default order.7 The older collect_data and collect_default parameters still exist, but the API docs mark both as not recommended. Write the collection logic explicitly so the rule's runtime contract is visible in the implementation.8
Merging Dependency Runfiles
Runtime files usually do not stop at the current target. A binary may need its own data, the runtime files of libraries in deps, and the runtime files attached to filegroup-like targets in srcs or data. The common pattern starts with direct files, collects target[DefaultInfo].default_runfiles from dependency attributes, and merges them into one runfiles object.9
def _collect_runfiles(ctx):
direct = ctx.runfiles(files = ctx.files.data)
transitive = []
for attr in (ctx.attr.srcs, ctx.attr.deps, ctx.attr.data):
for target in attr:
transitive.append(target[DefaultInfo].default_runfiles)
return direct.merge_all(transitive)
Use merge_all() when combining many runfiles objects. The runfiles API explicitly warns that calling merge() repeatedly in a loop can construct deep depset structures and cause build failures.10 This is the same performance shape as repeated depset wrapping: collect the children, merge once, and keep the structure shallow.
The default_runfiles field appears in that collection code because dependencies expose their already-computed runfiles through DefaultInfo. New rules should still return the unified runfiles parameter, not the legacy constructor split between data_runfiles and default_runfiles.11 Maintaining old rulesets that rely on the split belongs in 4.4.5 Legacy Runfiles Split.
Trace The Producer, Consumer, And Runtime
A useful way to review any runfiles design is to trace one file across three boundaries:
- Producer: a rule declares the file as runtime data and publishes it in a
DefaultInforunfiles set. - Consumer: an executable or an intermediate rule depends on that target and, when it republishes a runtime contract, merges the dependency's runfiles into the set it carries forward.
- Runtime: the launched program resolves the file's logical runfiles name through its language library instead of guessing a working-directory or output-tree path.
Each boundary has a different failure. Omitting the file at the producer loses it from the contract. Failing to merge it at an intermediate consumer breaks transitive propagation. Hard-coding a physical path at runtime makes the program depend on one materialization layout. 1.1.2 bazel run & Runfiles shows the operator-visible result of a missing or mislocated runfile. L2.1.7 Generated Sources, Data & Runfiles applies the final lookup step with the C++ library, and L11.5 Publishing, RIDs & Runfiles shows why a later publish or packaging step must preserve the same runtime set.
Executable Targets And Files-To-Run
For executable or test rules, DefaultInfo(executable = out) tells Bazel which output to invoke for bazel run or bazel test.12 The executable is added to the rule's default outputs and to its runfiles, so you do not need to list the same file in both files and executable.13
DefaultInfo.files_to_run exposes a FilesToRunProvider: the executable plus the metadata Bazel needs to run it, including runfiles manifest and repository mapping manifest fields when present.14 You do not construct this provider directly. Bazel derives it from executable targets and makes it available through DefaultInfo.files_to_run.15
That distinction matters when one rule uses another target as a tool. ctx.executable._tool is the convenient File for an executable label attr, but ctx.actions.run() can also accept a FilesToRunProvider as the action executable, and its tools parameter can accept FilesToRunProvider values so tool runfiles are automatically made available to the action.16 Use the file shortcut for simple tools. Reach for the files-to-run bundle when the tool's own runfiles are part of the action contract.
Runtime Lookup Is A Program Concern
A rule author decides what belongs in runfiles. The program decides how to locate those files at runtime. Do not design a rule API that assumes every platform exposes a Unix-style symlink tree. Bazel may materialize runfiles as a symlink tree on Linux/macOS, while Windows commonly uses a manifest file, and Bzlmod adds repository mapping to the lookup problem.17
The practical rule is: put the right files in runfiles, then let runtime code use the language's Bazel runfiles library or $(rlocationpath ...)-style values when it needs stable lookup paths.18 If the executable starts subprocesses that also need runfiles, propagate the runfiles library's environment variables to those subprocesses.19
The mini-ruleset shows both shapes in one file: glyph_library returns default
outputs plus transitive runfiles, while
glyph_binary
returns an executable with its launch-time runfiles.
DefaultInfo(files = ...) is the build contract, DefaultInfo(runfiles = ...) is the runtime contract, and DefaultInfo(executable = ...) is the launch contract.
Keep those three questions separate. An action input makes a file available while building. A runfile makes it available while running. Trace every runfile from the producer that publishes it, through consumers that merge and propagate it, to runtime code that resolves its logical name. A plain executable file starts a process. A files-to-run bundle carries the executable plus the runtime metadata Bazel needs around it.
Check your understanding · 3 questions
1.A custom rule registers an action that writes out.txt and returns DefaultInfo(files = depset([out])). What does that contract guarantee for consumers?
Select one answer
2.Which statements correctly describe runfiles in a custom rule?
Select all that apply
3.True or false: executable targets and files-to-run.
Choose True or False for each sentence
DefaultInfo(executable = out) tells Bazel which output to invoke for an executable rule.FilesToRunProvider is normally constructed directly by rule implementations.FilesToRunProvider can carry executable metadata such as runfiles and repository mapping manifests.files and executable because Bazel does not add it to default outputs.Footnotes
-
DefaultInfo — provider purpose and constructor parameters. ↩
-
DefaultInfo —
filesfield as default command-line outputs. ↩ -
Rules — default outputs and fallback to predeclared outputs when
DefaultInfo.filesis absent. ↩ -
Depsets — depsets preserve efficient transitive data structure for rule outputs and inputs. ↩
-
Rules — runfiles as runtime files staged for executable targets. ↩
-
runfiles — runfiles object should be passed through
DefaultInfo. ↩ -
ctx —
ctx.runfiles()filesandtransitive_filesparameters. ↩ -
ctx —
collect_dataandcollect_defaultare marked not recommended. ↩ -
Rules — explicit runfiles merge pattern across
srcs,deps, anddata. ↩ -
runfiles — prefer
merge_all()over repeatedmerge()to avoid deep depset structures. ↩ -
DefaultInfo —
data_runfilesanddefault_runfilesare legacy/not recommended constructor paths. ↩ -
DefaultInfo —
executableparameter for executable and test rules. ↩ -
Rules — executable output is added to default outputs and runfiles. ↩
-
FilesToRunProvider — executable, runfiles manifest, and repo mapping manifest fields. ↩
-
FilesToRunProvider — provider is implicit and accessible through
DefaultInfo.files_to_run. ↩ -
actions —
ctx.actions.run()executable and tools parameters acceptFilesToRunProvider. ↩ -
Runfiles and where to find them — symlink tree, manifest mode, and repository mapping complexity. ↩
-
Writing Bazel rules: data and runfiles — use runfiles libraries for cross-platform lookup. ↩
-
Runfiles and where to find them — propagate runfiles environment variables to subprocesses. ↩