Repository URL to install this package:
|
Version:
1.26.0.dev0+gite506aa5f ▾
|
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).
import os
from abc import abstractmethod
from typing import List
from pants.backend.jvm.subsystems.scalafix import Scalafix
from pants.backend.jvm.tasks.classpath_util import ClasspathUtil
from pants.backend.jvm.tasks.rewrite_base import RewriteBase
from pants.base.deprecated import resolve_conflicting_options
from pants.base.exceptions import TaskError
from pants.build_graph.build_graph import BuildGraph
from pants.build_graph.target_scopes import Scopes
from pants.java.jar.jar_dependency import JarDependency
from pants.option.custom_types import file_option
from pants.task.fmt_task_mixin import FmtTaskMixin
from pants.task.lint_task_mixin import LintTaskMixin
from pants.util.memo import memoized_property
class ScalafixTask(RewriteBase):
"""Executes the scalafix tool."""
_SCALAFIX_MAIN = "scalafix.cli.Cli"
def _resolve_conflicting_options(self, *, old_option: str, new_option: str):
return resolve_conflicting_options(
old_option=old_option,
new_option=new_option,
old_scope="fmt-scalafix",
new_scope="scalafix",
old_container=self.get_options(),
new_container=Scalafix.global_instance().options,
)
def _resolve_conflicting_skip(self, *, old_scope: str):
# Skip mypy because this is a temporary hack, and mypy doesn't follow the inheritance chain
# properly.
return self.resolve_conflicting_skip_options( # type: ignore
old_scope=old_scope, new_scope="scalafix", subsystem=Scalafix.global_instance(),
)
@classmethod
def subsystem_dependencies(cls):
return super().subsystem_dependencies() + (Scalafix,)
@classmethod
def register_options(cls, register):
super().register_options(register)
register(
"--configuration",
type=file_option,
default=None,
fingerprint=True,
removal_version="1.27.0.dev0",
removal_hint="Use `--scalafix-config` instead.",
help="The config file to use (in HOCON format).",
)
register(
"--rules",
default="ProcedureSyntax",
type=str,
fingerprint=True,
help="The `rules` arg to scalafix: generally a name like `ProcedureSyntax`.",
)
register(
"--semantic",
type=bool,
default=False,
fingerprint=True,
help="True to enable `semantic` scalafix rules by requesting compilation and "
"providing the target classpath to scalafix. To enable this option, you "
"will need to install the `semanticdb-scalac` compiler plugin. See "
"https://www.pantsbuild.org/scalac_plugins.html for more information.",
)
cls.register_jvm_tool(
register,
"scalafix",
classpath=[
JarDependency(org="ch.epfl.scala", name="scalafix-cli_2.12.8", rev="0.9.4"),
],
)
cls.register_jvm_tool(register, "scalafix-tool-classpath", classpath=[])
@classmethod
def target_types(cls):
return ["scala_library", "junit_tests"]
@classmethod
def source_extension(cls):
return ".scala"
@classmethod
def prepare(cls, options, round_manager):
super().prepare(options, round_manager)
# Only request a classpath and zinc_args if semantic checks are enabled.
if options.semantic:
round_manager.require_data("zinc_args")
round_manager.require_data("runtime_classpath")
@memoized_property
def _scalac_args(self):
if self.get_options().semantic:
targets = self.context.targets()
targets_to_zinc_args = self.context.products.get_data("zinc_args")
for t in targets:
zinc_args = targets_to_zinc_args[t]
args = []
for arg in zinc_args:
arg = arg.strip()
if arg.startswith("-S"):
args.append(arg[2:])
# All targets will get the same scalac args
if args:
return args
return []
@staticmethod
def _compute_classpath(runtime_classpath, targets):
closure = BuildGraph.closure(
targets, bfs=True, include_scopes=Scopes.JVM_RUNTIME_SCOPES, respect_intransitive=True
)
classpath_for_targets = ClasspathUtil.classpath(closure, runtime_classpath)
return classpath_for_targets
def invoke_tool(self, absolute_root, target_sources):
args = []
tool_classpath = self.tool_classpath("scalafix-tool-classpath")
if tool_classpath:
args.append(f"--tool-classpath={os.pathsep.join(tool_classpath)}")
if self.get_options().semantic:
# If semantic checks are enabled, we need the full classpath for these targets.
runtime_classpath = self.context.products.get_data("runtime_classpath")
classpath = ScalafixTask._compute_classpath(
runtime_classpath, {target for target, _ in target_sources}
)
args.append(f"--sourceroot={absolute_root}")
args.append(f"--classpath={os.pathsep.join(classpath)}")
config = self._resolve_conflicting_options(old_option="configuration", new_option="config")
if config:
args.append(f"--config={config}")
if self.get_options().rules:
args.append(f"--rules={self.get_options().rules}")
if self.get_options().level == "debug":
args.append("--verbose")
# This is how you pass a list of strings to a single arg key
for a in self._scalac_args:
args.append("--scalac-options")
args.append(a)
args.extend(self.additional_args or [])
args.extend(source for _, source in target_sources)
# Execute.
return self.runjava(
classpath=self.tool_classpath("scalafix"),
main=self._SCALAFIX_MAIN,
jvm_options=self.get_options().jvm_options,
args=args,
workunit_name="scalafix",
)
@property
@abstractmethod
def additional_args(self):
"""Additional arguments to the Scalafix command."""
class ScalaFixFix(FmtTaskMixin, ScalafixTask):
"""Applies fixes generated by scalafix."""
sideeffecting = True
additional_args: List[str] = []
@property
def skip_execution(self):
return super()._resolve_conflicting_skip(old_scope="fmt-scalafix")
def process_result(self, result):
if result != 0:
raise TaskError(f"{self._SCALAFIX_MAIN} ... failed to fix ({result}) targets.")
class ScalaFixCheck(LintTaskMixin, ScalafixTask):
"""Checks whether any fixes were generated by scalafix."""
sideeffecting = False
additional_args = ["--test"]
@property
def skip_execution(self):
return super()._resolve_conflicting_skip(old_scope="lint-scalafix")
def process_result(self, result):
if result != 0:
raise TaskError(f"Targets failed scalafix checks.")