Hey! These docs are for version 2.14, which is no longer officially supported. Click here for the latest version, 2.17!

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:

  1. Define a subclass of `GoalSubsystem`. This is the API to your goal.

    1. Set the class property `name` to the name of your goal.

    2. Set the class property `help`, which is used by `./pants help`.

    3. You may register options through attributes of `pants.option.option_types` types. See [Options and subsystems](🔗).

  2. 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.

    1. Set the class property `subsystem_cls` to the `GoalSubsystem` from the previous step.

    2. 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.

  3. Define an `@goal_rule`, which must return the `Goal` from the previous step and set its `exit_code`.

    1. 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.

  4. 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` signature

For 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 `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 `SpecsPaths` instead of `Targets`.

`SpecsPaths.files` will list all files matched by the specs, e.g. `::` will match every file in the project (regardless of if targets own the files).

To convert `SpecsPaths` into a [`Digest`](🔗), use `await Get(Digest, PathGlobs(globs=specs_paths.files))`.