HomeDocs
DocsCommunityTestimonialsUsersGitHubTwitterBlogJobsTermsPrivacyCookies
TermsPrivacyCookies

Creating new targets

How to add a custom target type.

When to create a new target type?

Adding new target types is most helpful when you are adding support for a new language.

If you instead want to reduce boilerplate in BUILD files, such as changing default values, use macros .

If you are already using a target type, but need to store additional metadata for your plugin, add a new field to the target type.

Step 1: Define the target type

To define a new target:

  1. Subclass pants.engine.target.Target.
  2. Define the class property alias. This is the symbol that people use in BUILD files.
  3. Define the class property core_fields.

For core_fields, we recommend including COMMON_TARGET_FIELDS to add the useful tags and description fields. You also likely want to include Dependencies and Sources.

from pants.engine.target import (
    COMMON_TARGET_FIELDS,
    Dependencies,
    Sources,
    StringField,
    Target,
)


class CustomField(StringField):
    alias = "custom_field"


class CustomTarget(Target):
    """A custom target to demo the Target API.
    
    This docstring will be used in the output of `<<pantscmd>> target-types`.
    """

    alias = "custom_target"
    core_fields = (*COMMON_TARGET_FIELDS, Dependencies, Sources, CustomField)

📘

Tip: subclass the Sources field

You will usually want to subclass the Sources field to give custom functionality, such as setting a default value.

You can also set the class property expected_file_extensions to enforce that only certain files are used, such as expected_file_extensions = (".json", ".txt").

📘

Using the fields of a pre-existing target type

Sometimes, you may want to create a new target type that behaves similarly to one that already exists, except for some small changes.

For example, you might like how python_binary() behaves in general, but you have a Django application and keep writing sources=["manage.py"]. Normally, you should write a macro to set this default value; but, here, you also want to add new Django-specific fields, so you decide to create a new target type.

Rather than subclassing the original target type, use this pattern:

from pants.backend.python.target_types import PythonBinary, PythonBinarySources
from pants.engine.target import Target
from pants.util.ordered_set import FrozenOrderedSet

class DjangoManagePySources(PythonBinarySources):
   default = ("manage.py",)


class DjangoManagePy(Target):
   alias = "django_manage_py"
   core_fields = (
       *(FrozenOrderedSet(PythonBinary.core_fields) - {PythonBinarySources}),
       DjangoManagePySources,
   )

In this example, we register all of the fields of PythonBinary, except for the field PythonBinarySources. We instead register our custom field DjangoManagePySources.

Step 2: Register the target type in register.py

Now, in your register.py, add the target type to the def target_types() entry point.

from plugins.target_types import CustomTarget

def target_types():
    return [CustomTarget]

You can confirm this works by running <<pantscmd>> target-types --details=custom_target.

🚧

Have --v1 activated? Write a V1 binding.

If you still use V1, you must write a V1 binding for your new target type so that V1 code can parse your target.

Precisely, you must define a class that inherits pants.build_graph.target.Target, set its constructor to take all of that target's field as parameters, then register the target with def build_file_aliases().

For example, in your plugin's register.py file:

from pants.build_graph.build_file_aliases import BuildFileAliases
from pants.build_graph.target import Target as TargetV1


class LegacyCustomTarget(TargetV1):
    def __init__(
        self,
        sources=(),
        dependencies=(),
        custom_field=None,
        custom_field2=None,
        **kwargs,
    ):
       super().__init__(**kwargs)


def build_file_aliases():
    return BuildFileAliases(targets={"custom_target": LegacyCustomTarget})