class AvroSubsystem(JvmToolBase): options_scope = "java-avro" help = "Avro IDL compiler (https://avro.apache.org/)." default_version = "1.11.0" default_artifacts = ("org.apache.avro:avro-tools:{version}", ) default_lockfile_resource = ( "pants.backend.codegen.avro.java", "avro-tools.default.lockfile.txt", ) default_lockfile_path = ( "src/python/pants/backend/codegen/avro/java/avro-tools.default.lockfile.txt" ) default_lockfile_url = git_url(default_lockfile_path) runtime_dependencies = TargetListOption( "--runtime-dependencies", help=lambda cls: softwrap(f""" A list of addresses to `jvm_artifact` targets for the runtime dependencies needed for generated Java code to work. For example, `['3rdparty/jvm:avro-runtime']`. These dependencies will be automatically added to every `avro_sources` target. At the very least, this option must be set to a `jvm_artifact` for the `org.apache.avro:avro:{cls.default_version}` runtime library. """), )
class ApacheThriftJavaSubsystem(Subsystem): options_scope = "java-thrift" help = "Options specific to generating Java from Thrift using the Apache Thrift generator" gen_options = StrListOption( "--options", help=softwrap(""" Code generation options specific to the Java code generator to pass to the Apache `thrift` binary via the `-gen java` argument. See `thrift -help` for supported values. """), ) _runtime_dependencies = TargetListOption( "--runtime-dependencies", help=softwrap(""" A list of addresses to `jvm_artifact` targets for the runtime dependencies needed for generated Java code to work. For example, `['3rdparty/jvm:libthrift']`. These dependencies will be automatically added to every `thrift_source` target. At the very least, this option must be set to a `jvm_artifact` for the `org.apache.thrift:libthrift` runtime library. """), ) @property def runtime_dependencies(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._runtime_dependencies, owning_address=None)
class ScalaPBSubsystem(JvmToolBase): options_scope = "scalapb" help = "The ScalaPB protocol buffer compiler (https://scalapb.github.io/)." default_version = "0.11.6" default_artifacts = ("com.thesamet.scalapb:scalapbc_2.13:{version}",) default_lockfile_resource = ( "pants.backend.codegen.protobuf.scala", "scalapbc.default.lockfile.txt", ) default_lockfile_path = ( "src/python/pants/backend/codegen/protobuf/scala/scalapbc.default.lockfile.txt" ) default_lockfile_url = git_url(default_lockfile_path) _runtime_dependencies = TargetListOption( "--runtime-dependencies", help=lambda cls: softwrap( f""" A list of addresses to `jvm_artifact` targets for the runtime dependencies needed for generated Scala code to work. For example, `['3rdparty/jvm:scalapb-runtime']`. These dependencies will be automatically added to every `protobuf_sources` target. At the very least, this option must be set to a `jvm_artifact` for the `com.thesamet.scalapb:scalapb-runtime_SCALAVER:{cls.default_version}` runtime library. """ ), ) _jvm_plugins = StrListOption( "--jvm-plugins", help=softwrap( """ A list of JVM-based `protoc` plugins to invoke when generating Scala code from protobuf files. The format for each plugin specifier is `NAME=ARTIFACT` where NAME is the name of the plugin and ARTIFACT is either the address of a `jvm_artifact` target or the colon-separated Maven coordinate for the plugin's jar artifact. For example, to invoke the fs2-grpc protoc plugin, the following option would work: `--scalapb-jvm-plugins=fs2=org.typelevel:fs2-grpc-codegen_2.12:2.3.1`. (Note: you would also need to set --scalapb-runtime-dependencies appropriately to include the applicable runtime libraries for your chosen protoc plugins.) """ ), ) @property def runtime_dependencies(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._runtime_dependencies, owning_address=None) @property def jvm_plugins(self) -> tuple[PluginArtifactSpec, ...]: return tuple(PluginArtifactSpec.from_str(pa_str) for pa_str in self._jvm_plugins)
class ScroogeJavaSubsystem(Subsystem): options_scope = "java-scrooge" help = "Java-specific options for the Scrooge Thrift IDL compiler (https://twitter.github.io/scrooge/)." _runtime_dependencies = TargetListOption( "--runtime-dependencies", help=softwrap(""" A list of addresses to `jvm_artifact` targets for the runtime dependencies needed for generated Java code to work. For example, `['3rdparty/jvm:libthrift']`. These dependencies will be automatically added to every `thrift_source` target. At the very least, this option must be set to a `jvm_artifact` for the `org.apache.thrift:libthrift` runtime library. """), ) @property def runtime_dependencies(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._runtime_dependencies, owning_address=None)
class ScroogeScalaSubsystem(Subsystem): options_scope = "scala-scrooge" help = "Scala-specific options for the Scrooge Thrift IDL compiler (https://twitter.github.io/scrooge/)." _runtime_dependencies = TargetListOption( "--runtime-dependencies", help=softwrap(f""" A list of addresses to `jvm_artifact` targets for the runtime dependencies needed for generated Scala code to work. For example, `['3rdparty/jvm:scrooge-runtime']`. These dependencies will be automatically added to every `thrift_source` target. At the very least, this option must be set to a `jvm_artifact` for the `com.twitter:scrooge-runtime_SCALAVER:{ScroogeSubsystem.default_version}` runtime library. """), ) @property def runtime_dependencies(self) -> UnparsedAddressInputs: return UnparsedAddressInputs( self._runtime_dependencies, owning_address=None, description_of_origin= f"the option `[{self.options_scope}].runtime_dependencies`", )
class MyPy(PythonToolBase): options_scope = "mypy" name = "MyPy" help = "The MyPy Python type checker (http://mypy-lang.org/)." default_version = "mypy==0.910" default_main = ConsoleScript("mypy") # See `mypy/rules.py`. We only use these default constraints in some situations. register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.typecheck.mypy", "mypy.lock") default_lockfile_path = "src/python/pants/backend/python/typecheck/mypy/mypy.lock" default_lockfile_url = git_url(default_lockfile_path) uses_requirements_from_source_plugins = True skip = SkipOption("check") args = ArgsListOption(example="--python-version 3.7 --disallow-any-expr") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: ("Path to a config file understood by MyPy " "(https://mypy.readthedocs.io/en/stable/config_file.html).\n\n" f"Setting this option will disable `[{cls.options_scope}].config_discovery`. Use " f"this option if the config is located in a non-standard location."), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: ("If true, Pants will include any relevant config files during " "runs (`mypy.ini`, `.mypy.ini`, and `setup.cfg`)." f"\n\nUse `[{cls.options_scope}].config` instead if your config is in a " f"non-standard location."), ) _source_plugins = TargetListOption( "--source-plugins", advanced=True, help= ("An optional list of `python_sources` target addresses to load first-party " "plugins.\n\n" "You must also set `plugins = path.to.module` in your `mypy.ini`, and " "set the `[mypy].config` option in your `pants.toml`.\n\n" "To instead load third-party plugins, set the option `[mypy].extra_requirements` " "and set the `plugins` option in `mypy.ini`." "Tip: it's often helpful to define a dedicated 'resolve' via " "`[python].resolves` for your MyPy plugins such as 'mypy-plugins' " "so that the third-party requirements used by your plugin, like `mypy`, do not " "mix with the rest of your project. Read that option's help message for more info " "on resolves."), ) extra_type_stubs = StrListOption( "--extra-type-stubs", advanced=True, help= ("Extra type stub requirements to install when running MyPy.\n\n" "Normally, type stubs can be installed as typical requirements, such as putting " "them in `requirements.txt` or using a `python_requirement` target." "Alternatively, you can use this option so that the dependencies are solely " "used when running MyPy and are not runtime dependencies.\n\n" "Expects a list of pip-style requirement strings, like " "`['types-requests==2.25.9']`."), ) @property def config_request(self) -> ConfigFilesRequest: # Refer to https://mypy.readthedocs.io/en/stable/config_file.html. return ConfigFilesRequest( specified=self.config, specified_option_name=f"{self.options_scope}.config", discovery=self.config_discovery, check_existence=["mypy.ini", ".mypy.ini"], check_content={ "setup.cfg": b"[mypy", "pyproject.toml": b"[tool.mypy" }, ) @property def source_plugins(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._source_plugins, owning_address=None) def check_and_warn_if_python_version_configured( self, config: FileContent | None) -> bool: """Determine if we can dynamically set `--python-version` and warn if not.""" configured = [] if config and b"python_version" in config.content: configured.append( f"`python_version` in {config.path} (which is used because of either config " "discovery or the `[mypy].config` option)") if "--py2" in self.args: configured.append("`--py2` in the `--mypy-args` option") if any(arg.startswith("--python-version") for arg in self.args): configured.append("`--python-version` in the `--mypy-args` option") if configured: formatted_configured = " and you set ".join(configured) logger.warning( f"You set {formatted_configured}. Normally, Pants would automatically set this " "for you based on your code's interpreter constraints " f"({doc_url('python-interpreter-compatibility')}). Instead, it will " "use what you set.\n\n" "(Automatically setting the option allows Pants to partition your targets by their " "constraints, so that, for example, you can run MyPy on Python 2-only code and " "Python 3-only code at the same time. This feature may no longer work.)" ) return bool(configured)
class Pylint(PythonToolBase): options_scope = "pylint" name = "Pylint" help = "The Pylint linter for Python code (https://www.pylint.org/)." default_version = "pylint>=2.11.0,<2.12" default_main = ConsoleScript("pylint") register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.pylint", "pylint.lock") default_lockfile_path = "src/python/pants/backend/python/lint/pylint/pylint.lock" default_lockfile_url = git_url(default_lockfile_path) uses_requirements_from_source_plugins = True skip = SkipOption("lint") args = ArgsListOption( example="--ignore=foo.py,bar.py --disable=C0330,W0311") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: ("Path to a config file understood by Pylint " "(http://pylint.pycqa.org/en/latest/user_guide/run.html#command-line-options).\n\n" f"Setting this option will disable `[{cls.options_scope}].config_discovery`. Use " f"this option if the config is located in a non-standard location."), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: ("If true, Pants will include any relevant config files during " "runs (`.pylintrc`, `pylintrc`, `pyproject.toml`, and `setup.cfg`)." f"\n\nUse `[{cls.options_scope}].config` instead if your config is in a " f"non-standard location."), ) _source_plugins = TargetListOption( "--source-plugins", advanced=True, help= ("An optional list of `python_sources` target addresses to load first-party " "plugins.\n\nYou must set the plugin's parent directory as a source root. For " "example, if your plugin is at `build-support/pylint/custom_plugin.py`, add " "'build-support/pylint' to `[source].root_patterns` in `pants.toml`. This is " "necessary for Pants to know how to tell Pylint to discover your plugin. See " f"{doc_url('source-roots')}\n\n" f"You must also set `load-plugins=$module_name` in your Pylint config file.\n\n" "While your plugin's code can depend on other first-party code and third-party " "requirements, all first-party dependencies of the plugin must live in the same " "directory or a subdirectory.\n\n" "To instead load third-party plugins, set the " "option `[pylint].extra_requirements` and set the `load-plugins` option in your " "Pylint config.\n\n" "Tip: it's often helpful to define a dedicated 'resolve' via " "`[python].resolves` for your Pylint plugins such as 'pylint-plugins' " "so that the third-party requirements used by your plugin, like `pylint`, do not " "mix with the rest of your project. Read that option's help message for more info " "on resolves."), ) def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to http://pylint.pycqa.org/en/latest/user_guide/run.html#command-line-options for # how config files are discovered. return ConfigFilesRequest( specified=self.config, specified_option_name=f"[{self.options_scope}].config", discovery=self.config_discovery, check_existence=[ ".pylinrc", *(os.path.join(d, "pylintrc") for d in ("", *dirs)) ], check_content={ "pyproject.toml": b"[tool.pylint]", "setup.cfg": b"[pylint." }, ) @property def source_plugins(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._source_plugins, owning_address=None)
class Flake8(PythonToolBase): options_scope = "flake8" name = "Flake8" help = "The Flake8 Python linter (https://flake8.pycqa.org/)." default_version = "flake8>=3.9.2,<4.0" default_main = ConsoleScript("flake8") register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.flake8", "flake8.lock") default_lockfile_path = "src/python/pants/backend/python/lint/flake8/flake8.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("lint") args = ArgsListOption(example="--ignore E123,W456 --enable-extensions H111") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: ( "Path to an INI config file understood by Flake8 " "(https://flake8.pycqa.org/en/latest/user/configuration.html).\n\n" f"Setting this option will disable `[{cls.options_scope}].config_discovery`. Use " f"this option if the config is located in a non-standard location." ), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: ( "If true, Pants will include any relevant config files during " "runs (`.flake8`, `flake8`, `setup.cfg`, and `tox.ini`)." f"\n\nUse `[{cls.options_scope}].config` instead if your config is in a " f"non-standard location." ), ) _source_plugins = TargetListOption( "--source-plugins", advanced=True, help=( "An optional list of `python_sources` target addresses to load first-party " "plugins.\n\nYou must set the plugin's parent directory as a source root. For " "example, if your plugin is at `build-support/flake8/custom_plugin.py`, add " "'build-support/flake8' to `[source].root_patterns` in `pants.toml`. This is " "necessary for Pants to know how to tell Flake8 to discover your plugin. See " f"{doc_url('source-roots')}\n\nYou must also set `[flake8:local-plugins]` in " "your Flake8 config file. " "For example:\n\n" "```\n" "[flake8:local-plugins]\n" " extension =\n" " CUSTOMCODE = custom_plugin:MyChecker\n" "```\n\n" "While your plugin's code can depend on other first-party code and third-party " "requirements, all first-party dependencies of the plugin must live in the same " "directory or a subdirectory.\n\n" "To instead load third-party plugins, set the option " "`[flake8].extra_requirements`.\n\n" "Tip: it's often helpful to define a dedicated 'resolve' via " "`[python].resolves` for your Flake8 plugins such as 'flake8-plugins' " "so that the third-party requirements used by your plugin, like `flake8`, do not " "mix with the rest of your project. Read that option's help message for more info " "on resolves." ), ) @property def config_request(self) -> ConfigFilesRequest: # See https://flake8.pycqa.org/en/latest/user/configuration.html#configuration-locations # for how Flake8 discovers config files. return ConfigFilesRequest( specified=self.config, specified_option_name=f"[{self.options_scope}].config", discovery=self.config_discovery, check_existence=["flake8", ".flake8"], check_content={"setup.cfg": b"[flake8]", "tox.ini": b"[flake8]"}, ) @property def source_plugins(self) -> UnparsedAddressInputs: return UnparsedAddressInputs(self._source_plugins, owning_address=None)
class MySubsystem(Subsystem): def __init__(self): pass str_opt = StrOption("--opt", default="", help="") optional_str_opt = StrOption("--opt", default=None, help="") int_opt = IntOption("--opt", default=0, help="") optional_int_opt = IntOption("--opt", default=None, help="") float_opt = FloatOption("--opt", default=1.0, help="") optional_float_opt = FloatOption("--opt", default=None, help="") bool_opt = BoolOption("--opt", default=True, help="") optional_bool_opt = BoolOption("--opt", default=None, help="") target_opt = TargetOption("--opt", default="", help="") optional_target_opt = TargetOption("--opt", default=None, help="") dir_opt = DirOption("--opt", default="", help="") optional_dir_opt = DirOption("--opt", default=None, help="") file_opt = FileOption("--opt", default="", help="") optional_file_opt = FileOption("--opt", default=None, help="") shellstr_opt = ShellStrOption("--opt", default="", help="") optional_shellstr_opt = ShellStrOption("--opt", default=None, help="") memorysize_opt = MemorySizeOption("--opt", default=1, help="") optional_memorysize_opt = MemorySizeOption("--opt", default=None, help="") # List opts str_list_opt = StrListOption("--opt", help="") int_list_opt = IntListOption("--opt", help="") float_list_opt = FloatListOption("--opt", help="") bool_list_opt = BoolListOption("--opt", help="") target_list_opt = TargetListOption("--opt", help="") dir_list_opt = DirListOption("--opt", help="") file_list_opt = FileListOption("--opt", help="") shellstr_list_opt = ShellStrListOption("--opt", help="") memorysize_list_opt = MemorySizeListOption("--opt", help="") # And just test one dynamic default dyn_str_list_opt = StrListOption("--opt", default=lambda cls: cls.default, help="") # Enum opts enum_opt = EnumOption("--opt", default=MyEnum.Val1, help="") optional_enum_opt = EnumOption("--opt", enum_type=MyEnum, default=None, help="") dyn_enum_opt = EnumOption( "--opt", enum_type=MyEnum, default=lambda cls: cls.default, help="" ) # mypy correctly complains about not matching any possibilities enum_opt_bad = EnumOption("--opt", default=None, help="") # type: ignore[call-overload] enum_list_opt1 = EnumListOption("--opt", default=[MyEnum.Val1], help="") enum_list_opt2 = EnumListOption("--opt", enum_type=MyEnum, help="") dyn_enum_list_opt = EnumListOption( "--opt", enum_type=MyEnum, default=lambda cls: cls.default_list, help="" ) # mypy correctly complains about needing a type annotation enum_list_bad_opt = EnumListOption("--opt", default=[], help="") # type: ignore[var-annotated] # Dict opts dict_opt1 = DictOption[str]("--opt", help="") dict_opt2 = DictOption[Any]("--opt", default=dict(key="val"), help="") # mypy correctly complains about needing a type annotation dict_opt3 = DictOption("--opt", help="") # type: ignore[var-annotated] dict_opt4 = DictOption("--opt", default={"key": "val"}, help="") dict_opt5 = DictOption("--opt", default=dict(key="val"), help="") dict_opt6 = DictOption("--opt", default=dict(key=1), help="") dict_opt7 = DictOption("--opt", default=dict(key1=1, key2="str"), help="") dyn_dict_opt = DictOption[str]("--opt", default=lambda cls: cls.default, help="") # Specialized Opts skip_opt = SkipOption("fmt") args_opt = ArgsListOption(example="--whatever")