class BufSubsystem(TemplatedExternalTool): options_scope = "buf" name = "Buf" help = "A linter and formatter for Protocol Buffers (https://github.com/bufbuild/buf)." default_version = "v1.3.0" default_known_versions = [ "v1.3.0|linux_arm64 |fbfd53c501451b36900247734bfa4cbe86ae05d0f51bc298de8711d5ee374ee5|13940828", "v1.3.0|linux_x86_64|e29c4283b1cd68ada41fa493171c41d7605750d258fcd6ecdf692a63fae95213|15267162", "v1.3.0|macos_arm64 |147985d7f2816a545792e38b26178ff4027bf16cd3712f6e387a4e3692a16deb|15391890", "v1.3.0|macos_x86_64|3b6bd2e5a5dd758178aee01fb067261baf5d31bfebe93336915bfdf7b21928c4|15955291", ] default_url_template = ( "https://github.com/bufbuild/buf/releases/download/{version}/buf-{platform}.tar.gz" ) default_url_platform_mapping = { "macos_arm64": "Darwin-arm64", "macos_x86_64": "Darwin-x86_64", "linux_arm64": "Linux-aarch64", "linux_x86_64": "Linux-x86_64", } skip_format = SkipOption("fmt", "lint", flag_name="--format-skip") skip_lint = SkipOption("lint", flag_name="--lint-skip") format_args = ArgsListOption(example="--error-format json", flag_name="--format-args") lint_args = ArgsListOption(example="--error-format json", flag_name="--lint-args") def generate_exe(self, plat: Platform) -> str: return "./buf/bin/buf"
class MySubsystem(Subsystem): options_scope = "my-subsystem" name = "Wrench" def __init__(self): self.options = SimpleNamespace() self.options.skip = True self.options.args = ["--arg1"] skip_prop1 = SkipOption("fmt", "lint") skip_prop2 = SkipOption("fmt") args_prop1 = ArgsListOption(example="--foo") args_prop2 = ArgsListOption(example="--bar", tool_name="Drill") args_prop3 = ArgsListOption(example="--baz", extra_help="Swing it!")
class BufSubsystem(TemplatedExternalTool): options_scope = "buf-lint" name = "Buf" help = "A linter for Protocol Buffers (https://github.com/bufbuild/buf)." default_version = "v1.0.0" default_known_versions = [ "v1.0.0|linux_arm64 |c4b095268fe0fc8de2ad76c7b4677ccd75f25623d5b1f971082a6b7f43ff1eb0|13378006", "v1.0.0|linux_x86_64|5f0ff97576cde9e43ec86959046169f18ec5bcc08e31d82dcc948d057212f7bf|14511545", "v1.0.0|macos_arm64 |e922c277487d941c4b056cac6c1b4c6e5004e8f3dda65ae2d72d8b10da193297|15147463", "v1.0.0|macos_x86_64|8963e1ab7685aac59b8805cc7d752b06a572b1c747a6342a9e73b94ccdf89ddb|15187858", ] default_url_template = ( "https://github.com/bufbuild/buf/releases/download/{version}/buf-{platform}.tar.gz" ) default_url_platform_mapping = { "macos_arm64": "Darwin-arm64", "macos_x86_64": "Darwin-x86_64", "linux_arm64": "Linux-aarch64", "linux_x86_64": "Linux-x86_64", } skip = SkipOption("lint") args = ArgsListOption(example="--error-format json") def generate_exe(self, plat: Platform) -> str: return "./buf/bin/buf"
class Hadolint(TemplatedExternalTool): options_scope = "hadolint" name = "Hadolint" help = "A linter for Dockerfiles." default_version = "v2.10.0" default_known_versions = [ "v2.10.0|macos_x86_64|59f0523069a857ae918b8ac0774230013f7bcc00c1ea28119c2311353120867a|2514960", "v2.10.0|macos_arm64 |59f0523069a857ae918b8ac0774230013f7bcc00c1ea28119c2311353120867a|2514960", # same as mac x86 "v2.10.0|linux_x86_64|8ee6ff537341681f9e91bae2d5da451b15c575691e33980893732d866d3cefc4|2301804", "v2.10.0|linux_arm64 |b53d5ab10707a585c9e72375d51b7357522300b5329cfa3f91e482687176e128|27954520", ] default_url_template = ( "https://github.com/hadolint/hadolint/releases/download/{version}/hadolint-{platform}" ) default_url_platform_mapping = { "macos_arm64": "Darwin-x86_64", "macos_x86_64": "Darwin-x86_64", "linux_arm64": "Linux-arm64", "linux_x86_64": "Linux-x86_64", } skip = SkipOption("lint") args = ArgsListOption(example="--format json") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: softwrap(f""" Path to an YAML config file understood by Hadolint (https://github.com/hadolint/hadolint#configure). Setting this option will disable `[{cls.options_scope}].config_discovery`. Use this option if the config is located in a non-standard location. """), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: softwrap(f""" If true, Pants will include all relevant config files during runs (`.hadolint.yaml` and `.hadolint.yml`). Use `[{cls.options_scope}].config` instead if your config is in a non-standard location. """), ) def config_request(self) -> ConfigFilesRequest: # Refer to https://github.com/hadolint/hadolint#configure 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=[".hadolint.yaml", ".hadolint.yml"], )
class Black(PythonToolBase): options_scope = "black" name = "Black" help = "The Black Python code formatter (https://black.readthedocs.io/)." default_version = "black==22.1.0" default_main = ConsoleScript("black") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.black", "black.lock") default_lockfile_path = "src/python/pants/backend/python/lint/black/black.lock" default_lockfile_url = git_url(default_lockfile_path) default_extra_requirements = ['typing-extensions>=3.10.0.0; python_version < "3.10"'] skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--target-version=py37 --quiet") export = ExportToolOption() config = FileOption( "--config", default=None, advanced=True, help=lambda cls: softwrap( f""" Path to a TOML config file understood by Black (https://github.com/psf/black#configuration-format). Setting this option will disable `[{cls.options_scope}].config_discovery`. Use this option if the config is located in a non-standard location. """ ), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: softwrap( f""" If true, Pants will include any relevant pyproject.toml config files during runs. Use `[{cls.options_scope}].config` instead if your config is in a non-standard location. """ ), ) def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#where-black-looks-for-the-file # for how Black discovers config. candidates = {os.path.join(d, "pyproject.toml"): b"[tool.black]" for d in ("", *dirs)} return ConfigFilesRequest( specified=self.config, specified_option_name=f"[{self.options_scope}].config", discovery=self.config_discovery, check_content=candidates, )
class Shellcheck(TemplatedExternalTool): options_scope = "shellcheck" name = "Shellcheck" help = "A linter for shell scripts." default_version = "v0.8.0" default_known_versions = [ "v0.8.0|macos_arm64 |e065d4afb2620cc8c1d420a9b3e6243c84ff1a693c1ff0e38f279c8f31e86634|4049756", "v0.8.0|macos_x86_64|e065d4afb2620cc8c1d420a9b3e6243c84ff1a693c1ff0e38f279c8f31e86634|4049756", "v0.8.0|linux_arm64 |9f47bbff5624babfa712eb9d64ece14c6c46327122d0c54983f627ae3a30a4ac|2996468", "v0.8.0|linux_x86_64|ab6ee1b178f014d1b86d1e24da20d1139656c8b0ed34d2867fbb834dad02bf0a|1403852", ] default_url_template = ( "https://github.com/koalaman/shellcheck/releases/download/{version}/shellcheck-" "{version}.{platform}.tar.xz") default_url_platform_mapping = { "macos_arm64": "darwin.x86_64", "macos_x86_64": "darwin.x86_64", "linux_arm64": "linux.aarch64", "linux_x86_64": "linux.x86_64", } skip = SkipOption("lint") args = ArgsListOption(example="-e SC20529") config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=softwrap(""" If true, Pants will include all relevant `.shellcheckrc` and `shellcheckrc` files during runs. See https://www.mankier.com/1/shellcheck#RC_Files for where these can be located. """), ) def generate_exe(self, _: Platform) -> str: return f"./shellcheck-{self.version}/shellcheck" def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to https://www.mankier.com/1/shellcheck#RC_Files for how config files are # discovered. candidates = [] for d in ("", *dirs): candidates.append(os.path.join(d, ".shellcheckrc")) candidates.append(os.path.join(d, "shellcheckrc")) return ConfigFilesRequest(discovery=self.config_discovery, check_existence=candidates)
class Shfmt(TemplatedExternalTool): options_scope = "shfmt" name = "shfmt" help = "An autoformatter for shell scripts (https://github.com/mvdan/sh)." default_version = "v3.2.4" default_known_versions = [ "v3.2.4|macos_arm64 |e70fc42e69debe3e400347d4f918630cdf4bf2537277d672bbc43490387508ec|2998546", "v3.2.4|macos_x86_64|43a0461a1b54070ddc04fbbf1b78f7861ee39a65a61f5466d15a39c4aba4f917|2980208", "v3.2.4|linux_arm64 |6474d9cc08a1c9fe2ef4be7a004951998e3067d46cf55a011ddd5ff7bfab3de6|2752512", "v3.2.4|linux_x86_64|3f5a47f8fec27fae3e06d611559a2063f5d27e4b9501171dde9959b8c60a3538|2797568", ] default_url_template = ( "https://github.com/mvdan/sh/releases/download/{version}/shfmt_{version}_{platform}" ) default_url_platform_mapping = { "macos_arm64": "darwin_arm64", "macos_x86_64": "darwin_amd64", "linux_arm64": "linux_arm64", "linux_x86_64": "linux_amd64", } skip = SkipOption("fmt", "lint") args = ArgsListOption(example="-i 2") config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=softwrap(""" If true, Pants will include all relevant `.editorconfig` files during runs. See https://editorconfig.org. """), ) def generate_exe(self, plat: Platform) -> str: plat_str = self.default_url_platform_mapping[plat.value] return f"./shfmt_{self.version}_{plat_str}" def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to https://editorconfig.org/#file-location for how config files are discovered. candidates = (os.path.join(d, ".editorconfig") for d in ("", *dirs)) return ConfigFilesRequest( discovery=self.config_discovery, check_content={fp: b"[*.sh]" for fp in candidates}, )
class ScalafmtSubsystem(JvmToolBase): options_scope = "scalafmt" name = "scalafmt" help = "scalafmt (https://scalameta.org/scalafmt/)" default_version = "3.2.1" default_artifacts = ("org.scalameta:scalafmt-cli_2.13:{version}",) default_lockfile_resource = ( "pants.backend.scala.lint.scalafmt", "scalafmt.default.lockfile.txt", ) default_lockfile_path = ( "src/python/pants/backend/scala/lint/scalafmt/scalafmt.default.lockfile.txt" ) default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint")
class Autoflake(PythonToolBase): options_scope = "autoflake" name = "Autoflake" help = "The Autoflake Python code formatter (https://github.com/myint/autoflake)." default_version = "autoflake==1.4" default_main = ConsoleScript("autoflake") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.autoflake", "autoflake.lock") default_lockfile_path = "src/python/pants/backend/python/lint/autoflake/autoflake.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--target-version=py37 --quiet")
class Docformatter(PythonToolBase): options_scope = "docformatter" name = "docformatter" help = "The Python docformatter tool (https://github.com/myint/docformatter)." default_version = "docformatter>=1.4,<1.5" default_main = ConsoleScript("docformatter") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.docformatter", "docformatter.lock") default_lockfile_path = "src/python/pants/backend/python/lint/docformatter/docformatter.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--wrap-summaries=100 --pre-summary-newline")
class Bandit(PythonToolBase): options_scope = "bandit" name = "Bandit" help = "A tool for finding security issues in Python code (https://bandit.readthedocs.io)." # When upgrading, check if Bandit has started using PEP 517 (a `pyproject.toml` file). If so, # remove `setuptools` from `default_extra_requirements`. default_version = "bandit>=1.7.0,<1.8" default_extra_requirements = [ "setuptools", # GitPython 3.1.20 was yanked because it breaks Python 3.8+, but Poetry's lockfile # generation still tries to use it. Upgrade this to the newest version once released or # when switching away from Poetry. "GitPython==3.1.18", ] default_main = ConsoleScript("bandit") register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.bandit", "bandit.lock") default_lockfile_path = "src/python/pants/backend/python/lint/bandit/bandit.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("lint") args = ArgsListOption(example="--skip B101,B308 --confidence") config = FileOption( "--config", default=None, advanced=True, help=( "Path to a Bandit YAML config file " "(https://bandit.readthedocs.io/en/latest/config.html)." ), ) @property def config_request(self) -> ConfigFilesRequest: # Refer to https://bandit.readthedocs.io/en/latest/config.html. Note that there are no # default locations for Bandit config files. return ConfigFilesRequest( specified=self.config, specified_option_name=f"{self.options_scope}.config" )
class Prettier(Subsystem): options_scope = "prettier" name = "Prettier" help = softwrap( """ The Prettier utility for formatting JS/TS (and others) code (https://prettier.io/). """ ) default_version = "[email protected]" skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--version") def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: """Prettier will use the closest configuration file to the file currently being formatted, so add all of them In the event of multiple configuration files, Prettier has an order of precedence specified here: https://prettier.io/docs/en/configuration.html.""" config_files = ( *[f"prettier.config{ext}" for ext in [".js", ".cjs"]], *[ f".prettierrc{ext}" for ext in [ "", ".json", ".yml", ".yaml", ".json5", ".js", ".cjs", ".toml", ] ], ) check_existence = [os.path.join(d, file) for file in config_files for d in ("", *dirs)] return ConfigFilesRequest( discovery=True, check_existence=check_existence, )
class PyUpgrade(PythonToolBase): options_scope = "pyupgrade" name = "pyupgrade" help = ( "Upgrade syntax for newer versions of the language (https://github.com/asottile/pyupgrade)." ) default_version = "pyupgrade>=2.31.0,<2.32" default_main = ConsoleScript("pyupgrade") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.pyupgrade", "pyupgrade.lock") default_lockfile_path = "src/python/pants/backend/python/lint/pyupgrade/pyupgrade.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--py39-plus --keep-runtime-typing")
class ClangFormat(PythonToolBase): options_scope = "clang-format" name = "ClangFormat" help = softwrap( """ The clang-format utility for formatting C/C++ (and others) code (https://clang.llvm.org/docs/ClangFormat.html). The clang-format binaries are retrieved from PyPi (https://pypi.org/project/clang-format/). """ ) default_version = "clang-format==14.0.3" default_main = ConsoleScript("clang-format") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--version") register_lockfile = True default_lockfile_resource = ("pants.backend.cc.lint.clangformat", "clangformat.lock") default_lockfile_path = "src/python/pants/backend/cc/lint/clangformat/clangformat.lock" default_lockfile_url = git_url(default_lockfile_path) export = ExportToolOption() def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: """clang-format will use the closest configuration file to the file currently being formatted, so add all of them.""" config_files = ( ".clang-format", "_clang-format", ) check_existence = [os.path.join(d, file) for file in config_files for d in ("", *dirs)] return ConfigFilesRequest( discovery=True, check_existence=check_existence, )
class GoogleJavaFormatSubsystem(JvmToolBase): options_scope = "google-java-format" name = "Google Java Format" help = "Google Java Format (https://github.com/google/google-java-format)" default_version = "1.13.0" default_artifacts = ("com.google.googlejavaformat:google-java-format:{version}",) default_lockfile_resource = ( "pants.backend.java.lint.google_java_format", "google_java_format.default.lockfile.txt", ) default_lockfile_path = "src/python/pants/backend/java/lint/google_java_format/google_java_format.default.lockfile.txt" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") aosp = BoolOption( "--aosp", default=False, help=( "Use AOSP style instead of Google Style (4-space indentation). " '("AOSP" is the Android Open Source Project.)' ), )
class GofmtSubsystem(Subsystem): options_scope = "gofmt" name = "gofmt" help = "Gofmt-specific options." skip = SkipOption("fmt", "lint")
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")
class SubsystemWithName(Subsystem): options_scope = "other-subsystem" name = "Hammer" skip_prop1 = SkipOption("fmt") args_prop1 = ArgsListOption(example="--nail") args_prop2 = ArgsListOption(example="--screw", tool_name="Screwdriver")
class TwineSubsystem(PythonToolBase): options_scope = "twine" name = "Twine" help = "The utility for publishing Python distributions to PyPi and other Python repositories." default_version = "twine>=3.7.1,<3.8" default_main = ConsoleScript("twine") # This explicit dependency resolves a weird behavior in poetry, where it would include a sys # platform constraint on "Windows" when this was included transitively from the twine # requirements. # See: https://github.com/pantsbuild/pants/pull/13594#issuecomment-968154931 default_extra_requirements = ["colorama>=0.4.3"] register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.subsystems", "twine.lock") default_lockfile_path = "src/python/pants/backend/python/subsystems/twine.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("publish") args = ArgsListOption(example="--skip-existing") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: ("Path to a .pypirc config file to use. " "(https://packaging.python.org/specifications/pypirc/)\n\n" f"Setting this option will disable `[{cls.options_scope}].config_discovery`. Use " "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 all relevant config files during runs " "(`.pypirc`).\n\n" f"Use `[{cls.options_scope}].config` instead if your config is in a " "non-standard location."), ) ca_certs_path = StrOption( "--ca-certs-path", advanced=True, default="<inherit>", help= ("Path to a file containing PEM-format CA certificates used for verifying secure " "connections when publishing python distributions.\n\n" 'Uses the value from `[GLOBAL].ca_certs_path` by default. Set to `"<none>"` to ' "not use the default CA certificate."), ) def config_request(self) -> ConfigFilesRequest: # Refer to https://twine.readthedocs.io/en/latest/#configuration 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=[".pypirc"], ) def ca_certs_digest_request( self, default_ca_certs_path: str | None) -> CreateDigest | None: ca_certs_path: str | None = self.ca_certs_path if ca_certs_path == "<inherit>": ca_certs_path = default_ca_certs_path if not ca_certs_path or ca_certs_path == "<none>": return None # The certs file will typically not be in the repo, so we can't digest it via a PathGlobs. # Instead we manually create a FileContent for it. ca_certs_content = Path(ca_certs_path).read_bytes() chrooted_ca_certs_path = os.path.basename(ca_certs_path) return CreateDigest((FileContent(chrooted_ca_certs_path, ca_certs_content), ))
class GoVetSubsystem(Subsystem): options_scope = "go-vet" name = "`go vet`" help = "`go vet`-specific options." skip = SkipOption("lint")
class Hadolint(TemplatedExternalTool): options_scope = "hadolint" name = "Hadolint" help = "A linter for Dockerfiles." default_version = "v2.8.0" # TODO: https://github.com/hadolint/hadolint/issues/411 tracks building and releasing # hadolint for Linux ARM64. default_known_versions = [ "v2.8.0|macos_x86_64|27985f257a216ecab06a16e643e8cb0123e7145b5d526cfcb4ce7a31fe99f357|2428944", "v2.8.0|macos_arm64 |27985f257a216ecab06a16e643e8cb0123e7145b5d526cfcb4ce7a31fe99f357|2428944", # same as mac x86 "v2.8.0|linux_x86_64|9dfc155139a1e1e9b3b28f3de9907736b9dfe7cead1c3a0ae7ff0158f3191674|5895708", ] default_url_template = ( "https://github.com/hadolint/hadolint/releases/download/{version}/hadolint-{platform}" ) default_url_platform_mapping = { "macos_arm64": "Darwin-x86_64", "macos_x86_64": "Darwin-x86_64", "linux_x86_64": "Linux-x86_64", } skip = SkipOption("lint") args = ArgsListOption(example="--format json") config = FileOption( "--config", default=None, advanced=True, help=lambda cls: softwrap( f""" Path to an YAML config file understood by Hadolint (https://github.com/hadolint/hadolint#configure). Setting this option will disable `[{cls.options_scope}].config_discovery`. Use this option if the config is located in a non-standard location. """ ), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: softwrap( f""" If true, Pants will include all relevant config files during runs (`.hadolint.yaml` and `.hadolint.yml`). Use `[{cls.options_scope}].config` instead if your config is in a non-standard location. """ ), ) def config_request(self) -> ConfigFilesRequest: # Refer to https://github.com/hadolint/hadolint#configure 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=[".hadolint.yaml", ".hadolint.yml"], )
class TfFmtSubsystem(Subsystem): options_scope = "terraform-fmt" name = "`terraform fmt`" help = "Terraform fmt options." skip = SkipOption("fmt", "lint")
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 Yapf(PythonToolBase): options_scope = "yapf" name = "yapf" help = "A formatter for Python files (https://github.com/google/yapf)." default_version = "yapf==0.32.0" default_extra_requirements = ["toml"] default_main = ConsoleScript("yapf") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.yapf", "yapf.lock") default_lockfile_path = "src/python/pants/backend/python/lint/yapf/yapf.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") args = ArgsListOption( example="--no-local-style", extra_help=softwrap( """ Certain arguments, specifically `--recursive`, `--in-place`, and `--parallel`, will be ignored because Pants takes care of finding all the relevant files and running the formatting in parallel. """ ), ) export = ExportToolOption() config = FileOption( "--config", default=None, advanced=True, help=lambda cls: softwrap( f""" Path to style file understood by yapf (https://github.com/google/yapf#formatting-style/). Setting this option will disable `[{cls.options_scope}].config_discovery`. Use this option if the config is located in a non-standard location. """ ), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: softwrap( f""" If true, Pants will include any relevant config files during runs (`.style.yapf`, `pyproject.toml`, and `setup.cfg`). Use `[{cls.options_scope}].config` instead if your config is in a non-standard location. """ ), ) def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to https://github.com/google/yapf#formatting-style. check_existence = [] check_content = {} for d in ("", *dirs): check_existence.append(os.path.join(d, ".yapfignore")) check_content.update( { os.path.join(d, "pyproject.toml"): b"[tool.yapf", os.path.join(d, "setup.cfg"): b"[yapf]", os.path.join(d, ".style.yapf"): b"[style]", } ) return ConfigFilesRequest( specified=self.config, specified_option_name=f"[{self.options_scope}].config", discovery=self.config_discovery, check_existence=check_existence, check_content=check_content, )
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 Isort(PythonToolBase): options_scope = "isort" name = "isort" help = "The Python import sorter tool (https://pycqa.github.io/isort/)." default_version = "isort[pyproject,colors]>=5.9.3,<6.0" default_main = ConsoleScript("isort") register_interpreter_constraints = True default_interpreter_constraints = ["CPython>=3.7,<4"] register_lockfile = True default_lockfile_resource = ("pants.backend.python.lint.isort", "isort.lock") default_lockfile_path = "src/python/pants/backend/python/lint/isort/isort.lock" default_lockfile_url = git_url(default_lockfile_path) skip = SkipOption("fmt", "lint") args = ArgsListOption(example="--case-sensitive --trailing-comma") export = ExportToolOption() config = FileListOption( "--config", # TODO: Figure out how to deprecate this being a list in favor of a single string. # Thanks to config autodiscovery, this option should only be used because you want # Pants to explicitly set `--settings`, which only works w/ 1 config file. # isort 4 users should instead use autodiscovery to support multiple config files. # Deprecating this could be tricky, but should be possible thanks to the implicit # add syntax. # # When deprecating, also deprecate the user manually setting `--settings` with # `[isort].args`. advanced=True, help=lambda cls: softwrap(f""" Path to config file understood by isort (https://pycqa.github.io/isort/docs/configuration/config_files/). Setting this option will disable `[{cls.options_scope}].config_discovery`. Use this option if the config is located in a non-standard location. If using isort 5+ and you specify only 1 config file, Pants will configure isort's argv to point to your config file. """), ) config_discovery = BoolOption( "--config-discovery", default=True, advanced=True, help=lambda cls: softwrap(f""" If true, Pants will include any relevant config files during runs (`.isort.cfg`, `pyproject.toml`, `setup.cfg`, `tox.ini` and `.editorconfig`). Use `[{cls.options_scope}].config` instead if your config is in a non-standard location. """), ) def config_request(self, dirs: Iterable[str]) -> ConfigFilesRequest: # Refer to https://pycqa.github.io/isort/docs/configuration/config_files/. check_existence = [] check_content = {} for d in ("", *dirs): check_existence.append(os.path.join(d, ".isort.cfg")) check_content.update({ os.path.join(d, "pyproject.toml"): b"[tool.isort]", os.path.join(d, "setup.cfg"): b"[isort]", os.path.join(d, "tox.ini"): b"[isort]", os.path.join(d, ".editorconfig"): b"[*.py]", }) return ConfigFilesRequest( specified=self.config, specified_option_name=f"[{self.options_scope}].config", discovery=self.config_discovery, check_existence=check_existence, check_content=check_content, )
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)