For many [plugin tasks](🔗), you will be extending existing goals, such as adding a new linter to the `lint
` goal. However, you may instead want to create a new goal, such as a `publish
` goal. This page explains how to create a new goal.
As explained in [Concepts](🔗), `@goal_rule
`s are the entry-point into the rule graph. When a user runs `./pants my-goal
`, the Pants engine will look for the respective `@goal_rule
`. That `@goal_rule
` will usually request other types, either as parameters in the `@goal_rule
` signature or through `await Get
`. But unlike a `@rule
`, a `@goal_rule
` may also trigger side-effects (such as running interactive processes, writing to the filesystem, etc) via `await Effect
`.
Often, you can keep all of your logic inline in the `@goal_rule
`. As your `@goal_rule
` gets more complex, you may end up factoring out helper `@rule
`s, but you do not need to start with writing helper `@rule
`s.
## How to register a new goal
There are four steps to creating a new [goal](🔗) with Pants:
Define a subclass of `
GoalSubsystem
`. This is the API to your goal.Set the class property `
name
` to the name of your goal.Set the class property `
help
`, which is used by `./pants help
`.You may register options through the class method `
register_options()
`. See [Options and subsystems](🔗).
Define a subclass of `
Goal
`. When a user runs `./pants my-goal
`, the engine will request your subclass, which is what causes the `@goal_rule
` to run.Set the class property `
subsystem_cls
` to the `GoalSubsystem
` from the previous step.A `
Goal
` takes a single argument in its constructor, `exit_code: int
`. Pants will use this to determine what its own exit code should be.
Define an `
@goal_rule
`, which must return the `Goal
` from the previous step and set its `exit_code
`.For most goals, simply return `
MyGoal(exit_code=0)
`. Some goals like `lint
` and `test
` will instead propagate the error code from the tools they run.
Register the `
@goal_rule
` in a `register.py
` file.
You may now run `./pants hello-world
`, which should cause Pants to return with an error code of 1 (run `echo $?
` to verify). Precisely, this causes the engine to request the type `HelloWorld
`, which results in running the `@goal_rule
` `hello_world
`.
## `Console
`: output to stdout/stderr
To output to the user, request the type `Console
` as a parameter in your `@goal_rule
`. This is a special type that may only be requested in `@goal_rules
` and allows you to output to stdout and stderr.
### Using colors
You may output in color by using the methods `.blue()
`, `.cyan()
`, `.green()
`, `.magenta()
`, `.red()
`, and `.yellow()
`. The colors will only be used if the global option `--colors
` is True.
### `Outputting
` mixin (optional)
If your goal's purpose is to emit output, it may be helpful to use the mixin `Outputting
`. This mixin will register the output `--output-file
`, which allows the user to redirect the goal's stdout.
### `LineOriented
` mixin (optional)
If your goal's purpose is to emit output—and that output is naturally split by new lines—it may be helpful to use the mixin `LineOriented
`. This subclasses `Outputting
`, so will register both the options `--output-file
` and `--sep
`, which allows the user to change the separator to not be `\n
`.
## How to operate on Targets
Most goals will want to operate on targets. To do this, specify `Targets
` as a parameter of your goal rule.
This example will print the address of any targets specified by the user, just as the `list
` goal behaves.
See [Rules and the Target API](🔗) for detailed information on how to use these targets in your rules, including accessing the metadata specified in BUILD files.
Common mistake: requesting the type of target you want in the `
@goal_rule
` signatureFor example, if you are writing a `
publish
` goal, and you expect to operate on `python_distribution
` targets, you might think to request `PythonDistribution
` in your `@goal_rule
` signature:This will not work because the engine has no path in the rule graph to resolve a `
PythonDistribution
` type given the initial input types to the rule graph (the "roots").Instead, request `
Targets
`, which will give you all of the targets that the user specified on the command line. The engine knows how to resolve this type because it can go from `AddressSpecs + FilesystemSpecs
` -> `Specs
` -> `Addresses
` -> `Targets
`.From here, filter out the relevant targets you want using the Target API (see [Rules and the Target API](🔗).
### Only care about source files?
If you only care about files, and you don't need any metadata from BUILD files, then you can request `SpecsSnapshot
` instead of `Targets
`.
When users use address arguments like `::
`, you will get all the sources belonging to the matched targets. When users use file arguments like `'**'
`, you will get all matching files, even if the file doesn't have any owning target.