Goal rules
How to create new goals.
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 bypants help
. - You may register options through attributes of
pants.option.option_types
types. See Options and subsystems.
- Set the class property
- Define a subclass of
Goal
. When a user runspants my-goal
, the engine will request your subclass, which is what causes the@goal_rule
to run.- Set the class property
subsystem_cls
to theGoalSubsystem
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.
- Set the class property
- Define an
@goal_rule
, which must return theGoal
from the previous step and set itsexit_code
.- For most goals, simply return
MyGoal(exit_code=0)
. Some goals likelint
andtest
will instead propagate the error code from the tools they run.
- For most goals, simply return
- Register the
@goal_rule
in aregister.py
file.
- pants-plugins/example/hello_world.py
- pants-plugins/example/register.py
from pants.engine.goal import Goal, GoalSubsystem
from pants.engine.rules import collect_rules, goal_rule
class HelloWorldSubsystem(GoalSubsystem):
name = "hello-world"
help = "An example goal."
class HelloWorld(Goal):
subsystem_cls = HelloWorldSubsystem
environment_behavior = Goal.EnvironmentBehavior.LOCAL_ONLY
@goal_rule
async def hello_world() -> HelloWorld:
return HelloWorld(exit_code=1)
def rules():
return collect_rules()
from example import hello_world
def rules():
return [*hello_world.rules()]
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.
from pants.engine.console import Console
...
@goal_rule
async def hello_world(console: Console) -> HelloWorld:
console.print_stdout("Hello!")
console.print_stderr("Uh oh, an error.")
return HelloWorld(exit_code=1)
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.
console.print_stderr(f"{console.red('