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.
- 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."
- Register this field on the appropriate targets.
def rules():
return [
FortranSourceTarget.register_plugin_field(SkipFortranLintField),
]
- Add this field as part of your subsystems
opt_outmethod:
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
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.
-
Make the subsystem a subclass of
ExportableToolLanguage backends may have done this in their Tool base class.For example, the Python backend with
PythonToolRequirementsBaseand JVM withJvmToolBaseare already subclasses.from pants.backend.python.subsystems.python_tool_base import PythonToolBase
from pants.core.goals.resolves import ExportableTool
class FortranLint(PythonToolBase, ExportableTool):
... -
Register your class with a
UnionRulewithExportableTooldef rules():
return [
UnionRule(ExportableTool, FortranLint)
]
Loading config files
-
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.
"""
),
) -
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.
"""
),
) -
Add a helper function to generate the
ConfigFilesRequest. Thecheck_existencefield is used for config discovery.specifiedcan 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"],
) -
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 resultingConfigFilesinstance 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 yourProcess. If a custom value was provided for the config file, you may need to pass that as an argument to theProcess. You may also need to register rules frompants.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(),
]