Skip to main content
Version: 2.32 (dev)

Common subsystem tasks

Common tasks for Subsystems


Skipping individual targets​

Many subsystems allow skipping specific targets. For example, you might have Python files that you want to not typecheck with mypy. In Pants, this is achieved with a skip_* field on the target. This is simple to implement.

  1. Create a field for skipping your tool
from pants.engine.target import BoolField

class SkipFortranLintField(BoolField):
alias = "skip_fortran_lint"
default = False
help = "If true, don't run fortran-lint on this target's code."
  1. Register this field on the appropriate targets.
def rules():
return [
FortranSourceTarget.register_plugin_field(SkipFortranLintField),
]
  1. Add this field as part of your subsystems opt_out method:
from dataclasses import dataclass

from pants.engine.target import FieldSet, Target


@dataclass
class FortranLintFieldSet(FieldSet):
required_fields = (FortranSourceField,)

source: FortranSourceField

@classmethod
def opt_out(cls, tgt: Target) -> bool:
return tgt.get(SkipFortranLintField).value

Making subsystems exportable with their default lockfile​

Support depends on language backend of the subsystem

Only some language backends support pants export. These include the Python and JVM backends. Only tools which are themselves written to use a backend with this feature can be exported. For example, a Python-based tool which operates on a different language is exportable.

  1. Make the subsystem a subclass of ExportableTool

    Language backends may have done this in their Tool base class.

    For example, the Python backend with PythonToolRequirementsBase and JVM with JvmToolBase are already subclasses.

    from pants.backend.python.subsystems.python_tool_base import PythonToolBase
    from pants.core.goals.resolves import ExportableTool

    class FortranLint(PythonToolBase, ExportableTool):
    ...
  2. Register your class with a UnionRule with ExportableTool

    def rules():
    return [
    UnionRule(ExportableTool, FortranLint)
    ]

Loading config files​

  1. Add an option to toggle config discovery:

    from pants.option.subsystem import Subsystem
    from pants.option.option_types import BoolOption
    from pants.util.strutil import softwrap

    class FortranLint(Subsystem):
    config_discovery = BoolOption(
    default=True,
    advanced=True,
    help=lambda cls: softwrap(
    f"""
    If true, Pants will include all relevant config files during runs.

    Use `[{cls.options_scope}].config` instead if your config is in a non-standard location.
    """
    ),
    )
  2. Add an option for the configuration file itself. Several options are useful depending on what types of config files you need: FileOption, FileListOption, DirOption, DirListOption.

    from pants.option.subsystem import Subsystem
    from pants.option.option_types import FileOption
    from pants.util.strutil import softwrap

    class FortranLint(Subsystem):
    config = FileOption(
    default=None,
    advanced=True,
    help=lambda cls: softwrap(
    """
    Path to the fortran-lint config file.

    Setting this option will disable config discovery for the config file. Use this option if the config is located in a non-standard location.
    """
    ),
    )
  3. Add a helper function to generate the ConfigFilesRequest. The check_existence field is used for config discovery. specified can also be a list for using one of the list options.

    from pants.core.util_rules.config_files import ConfigFilesRequest
    from pants.option.subsystem import Subsystem

    class FortranLint(Subsystem):
    def config_request(self) -> ConfigFilesRequest:
    return ConfigFilesRequest(
    specified=self.config,
    specified_option_name=f"[{self.options_scope}].config",
    discovery=self.config_discovery,
    check_existence=["fortran_lint.ini"],
    )
  4. Make a request for the config files in a rule for running the tool. Use find_config_file(ConfigFilesRequest(...)) to get the config files. The resulting ConfigFiles instance has a snapshot containing the config files (or will be empty if none are found). You can merge these with the other digests to pass the files to your Process. If a custom value was provided for the config file, you may need to pass that as an argument to the Process. You may also need to register rules from pants.core.util_rules.config_files.

    from pants.core.goals.lint import LintResult
    from pants.core.util_rules import config_files
    from pants.core.util_rules.config_files import ConfigFilesRequest, find_config_file
    from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
    from pants.engine.fs import MergeDigests
    from pants.engine.intrinsics import merge_digests
    from pants.engine.rules import collect_rules, concurrently, rule

    @rule
    async def run_fortran_lint(request: FortranlintRequest.Batch, subsystem: FortranLint) -> LintResult:
    sources, config_file = await concurrently(
    determine_source_files(
    SourceFilesRequest(fs.sources for fs in request.elements)
    ),
    find_config_file(subsystem.config_request()),
    )

    input_digest = await merge_digests(
    MergeDigests((sources.snapshot.digest, config_file.snapshot.digest))
    )

    args = []
    if subsystem.config:
    args.append(f"--config-file={subsystem.config}")

    # run your process with the digest and args

    def rules():
    return [
    *collect_rules(),
    *config_files.rules(),
    ]