The Target API defines how you interact with targets in your plugin. For example, you would use the Target API to read the
sources field of a target to know which files to run on.
The Target API can also be used to add new target types—such as adding support for a new language. Additionally, the Target API can be used to extend pre-existing target types.
v1 plugin author upgrading to the Target API?
These docs are written from the perspective of writing a brand new plugin using the Target API and v2 engine, rather than the perspective of already having a v1 plugin and writing bindings for your plugin. However, these docs are still relevant.
We recommend reading the docs in this order:
- Skim this "Concepts" page. The main difference from V1 targets is that fields are the most important part of the Target API. Rather than defining your fields in the
__init__()of your target, you create a new class for each field.
- Read "Creating new fields". The majority of your bindings will be creating fields for each custom target you have.
- Read "Creating new targets". This shows how to hook up the fields you created in the previous step and how to register your target in
While writing your binding, run
./pants help my_custom_targetto check that everything looks right.
See here for an example of writing a binding.
Please message us on Slack if you have any questions or you would like help writing bindings! We are eager to help.
As described in Targets and BUILD files, a target is a set of metadata describing some of your code.
For example, this BUILD file defines a
python_tests( sources=['app_test.py'], compatibility='==3.7.*' timeout=120, )
A field is a single value of metadata belonging to a target.
In the above example,
timeout are all fields.
Each field has a Python class that defines its BUILD file alias, data type, and optional settings like default values. For example:
from pants.engine.target import IntField, StringField class PythonInterpreterCompatibility(StringField): alias = "compatibility" class PythonTestsTimeout(IntField): alias = "timeout" default = 60
Precisely, a target is a combination of fields, along with a BUILD file alias.
These fields should make sense together. For example, it does not make sense for a
python_library target to have a
In fact, it only takes 3 lines of code to create a new target:
from pants.engine.target import Dependencies, Sources, Target, Tags class CustomTarget(Target): alias = "custom_target" core_fields = (Sources, Dependencies, Tags)
Any unrecognized fields will cause an exception when used in a BUILD file.
Because fields are stand-alone Python classes, the same field definition may be reused across multiple different target types.
For example, most target types have the
resources( name="files_tgt", sources=["demo.txt"], ) python_library( name="python_tgt", sources=["demo.py"], )
This gives you reuse of code (DRY) and is important for your plugin to work with multiple different target types, as explained below.
Pants plugins do not care about specific target types; they only care that the target type has the right combination of field types that the plugin needs to operate.
For example, the Python autoformatter Black does not actually care whether you have a
custom_target target; all that it cares about is that your target type has the field
Targets are only used to get access to the underlying fields through the methods
if target.has_field(PythonSources): print("My plugin can work on this target.") timeout_field = target.get(PythonTestsTimeout) print(timeout_field.value)
This means when creating new target types, the fields you choose for your target will determine the functionality it has.
Often, you may like how a field behaves, but want to make some tweaks. For example, you may want to give a default value to the
To modify a pre-existing field, simply subclass it.
from pants.engine.target import Sources class JsonSources(Sources): default = ("*.json",)
.get() understand this subclass relationship, as follows:
>>> json_target.has_field(JsonSources) True >>> json_target.has_field(Sources) True >>> python_target.has_field(JsonSources) False >>> python_target.has_field(Sources) True
This subclass mechanism is key to how the Target API behaves:
- You can use subclasses of fields—along with
Target.has_field()— to filter out irrelevant targets. For example, the Black autoformatter doesn't work with any plain
Sourcesfield; it needs
PythonSources. The Python test runner is even more specific: it needs
- You can create custom fields and custom target types that still work with pre-existing functionality. For example, you can subclass
DjangoSources, and the Black autoformatter will still be able to operate on your target.
Updated 6 months ago