Skip to main content

Dep Files

Dep files allow commands to declare which subset of their inputs were used when the command executed.

When a command produces a dep file and is later invalidated due to an inputs change, Buck2 uses the dep file to check whether the inputs that changed were in the set that the command reported as having used. If none of the inputs that changed were in that set, Buck2 omits re-running the command and reuses the previous result.

Use Cases

Dep files are used to make dependencies finer grained than what exists in the target graph, but they're not a substitute for avoiding unused dependencies. They're often useful when targets export many outputs (such as C++ headers) that aren't all used by all their dependents.

Dep files are currently used to skip recompilation steps in C++ when an unused header changed. They're also used in Java to skip recompilation when an unused class changed.

Using dep files

To use dep files, you need to do the following:

  • Declare what output is a dep file and associate it with your command.
  • Declare which inputs are covered by the dep file (this can be a subset of your inputs).
  • Have your command produce the dep file in a format Buck2 can use.

You must also enable Deferred Materialization to use dep files.

Declaring the dep files and associating inputs

To declare a dep file and associate it with your command, you need to tag your artifacts.

Specifically, you'll tag the output (the dep file) and the inputs it covers, as shown in the following code:

# First, create a tag

headers_tag = ctx.actions.artifact_tag()

# Then, tag inputs and the dep file itself in your command line.
# You do this using the `tag_artifacts` method on your tag.
# This method does not mutate the input, it wraps it, so you use the output.
# Any command-line-arg-like can be tagged.

tagged_headers = headers_tag.tag_artifacts(headers)

dep_file = ctx.actions.declare_output("deps").as_output()
tagged_dep_file = headers_tag.tag_artifacts(dep_file)

# Finally, declare your action.
# Use the tagged artifacts as you would regular command-line-arg-likes.
# Pass the tag in `dep_files` and give a name (this is used for logging).

ctx.actions.run(
["mycc", "-I", tagged_headers, "-MD", "-MF", tagged_dep_file, "-o", ...],
dep_files = { "headers": headers_tag }
)

Producing the dep file

Your command must produce dep files in the format Buck2 expects, which is simply a list of all the inputs that were used, one per line.

The paths must be the paths Buck2 would use for your inputs, which means paths relative to the project root.

If this is not the format your tool produces, use a wrapper to take whatever output your command produces and rewrite it in the format Buck2 expects.

Testing dep files

When writing a command that produces a dep file, you should test it! At a minimum, check that the inputs you expect are tagged properly.

To do so, build your target, then use buck2 audit dep-files TARGET CATEGORY IDENTIFIER, which will show you the set of inputs your command used and how they're tagged.

Extra notes to the implementer

Limitations

Dep files only work if a previous invocation of the command is known to your Buck2 daemon. Dep files are dropped when the daemon restarts or when you run buck2 debug flush-dep-files.

This means that, for example, if you change an unused header, then run a build on a fresh daemon, Buck2 will still need to execute this command in order to identify that the header was in fact unused. In contrast, if you did the build (and got a remote cache hit on the command), then applied your change and re-built, Buck2 would use the dep file on the second execution, and you wouldn't need to execute anything.

Dep files don't need to be covering

It's OK for the dep file to only cover a subset of the inputs of your action. However, within that subset, the dep file must declare all the inputs that were used.

If you fail to report some inputs you used, then your command will not re-run when they change, and you'll get stale output.

Dep files are lazy

Dep files aren't parsed by Buck2 unless the command needs to re-run. If the command ran on RE, they aren't even downloaded until then. This ensures dep files don't cause a performance hit unless they are used, at which point they stand a chance of giving a performance boost instead.

This means that if you produce an invalid dep file, Buck2 will not report this until your command runs again, at which point Buck2 will report that the dep file is invalid and refuse to proceed (note: you can unblock yourself using buck2 debug flush-dep-files).

To flush out issues during development, you can pass --eager-dep-files to Buck2 to force Buck2 to parse your dep files as they are produced.

If your dep file reports that a symlink was used, Buck2 will track the symlink's target as covered by this dep file.