Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/notes/2.32.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ The plugin API's `Get()` and `MultiGet()` constructs, deprecated in 2.30, are no

### Goals

#### Check

For those language ecosystems that use separate type checkers from compilers (So Python, but not JVM languages or Golang for example), the `check` goal now includes a `--force` flag that works like the `test` goals's `--force`. Use it for benchmarking or debugging rare problems. Supported languages now also include a cache status hint similar to the `test` output. So using the Pants repository itself as an example, output might look like:


```
✓ javac succeeded.
✓ mypy (pbs-script, ['CPython==3.11.*']) succeeded in 3.93s (cached locally).
✓ mypy (python-default, ['CPython==3.11.*']) succeeded in 17.87s (cached locally).
✓ scalac succeeded.
✓ typescript succeeded.
```

#### Publish

`PublishFieldSet` now has a `check_skip_request` hook to enable preemptive skips of packaging requests for targets where the publishing will be skipped (such as when a `skip_push=True` field exists).
Expand Down
11 changes: 7 additions & 4 deletions src/python/pants/backend/helm/check/kubeconform/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
from pants.backend.helm.check.kubeconform.subsystem import KubeconformSubsystem
from pants.backend.helm.subsystems.helm import HelmSubsystem
from pants.backend.helm.util_rules.renderer import RenderedHelmFiles
from pants.core.goals.check import CheckRequest, CheckResult
from pants.core.goals.check import CheckRequest, CheckResult, CheckSubsystem
from pants.core.util_rules.env_vars import environment_vars_subset
from pants.core.util_rules.external_tool import DownloadedExternalTool, download_external_tool
from pants.engine.env_vars import EnvironmentVarsRequest
from pants.engine.fs import CreateDigest, FileEntry
from pants.engine.intrinsics import create_digest, execute_process
from pants.engine.platform import Platform
from pants.engine.process import Process, ProcessCacheScope
from pants.engine.process import Process
from pants.engine.rules import collect_rules, concurrently, implicitly, rule
from pants.util.frozendict import FrozenDict
from pants.util.logging import LogLevel
Expand Down Expand Up @@ -67,7 +67,10 @@ async def setup_kube_conform(

@rule
async def run_kubeconform(
request: RunKubeconformRequest, setup: KubeconformSetup, kubeconform: KubeconformSubsystem
request: RunKubeconformRequest,
setup: KubeconformSetup,
kubeconform: KubeconformSubsystem,
check_subsystem: CheckSubsystem,
) -> CheckResult:
tool_relpath = "__kubeconform"
chart_relpath = "__chart"
Expand Down Expand Up @@ -126,7 +129,7 @@ async def run_kubeconform(
append_only_caches=setup.append_only_caches,
description=f"Validating Kubernetes manifests for {request.field_set.address}",
level=LogLevel.DEBUG,
cache_scope=ProcessCacheScope.SUCCESSFUL,
cache_scope=check_subsystem.default_process_cache_scope,
),
**implicitly(),
)
Expand Down
10 changes: 9 additions & 1 deletion src/python/pants/backend/python/typecheck/mypy/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@
prepare_python_sources,
)
from pants.base.build_root import BuildRoot
from pants.core.goals.check import REPORT_DIR, CheckRequest, CheckResult, CheckResults
from pants.core.goals.check import (
REPORT_DIR,
CheckRequest,
CheckResult,
CheckResults,
CheckSubsystem,
)
from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
from pants.core.util_rules.system_binaries import (
CpBinary,
Expand Down Expand Up @@ -142,6 +148,7 @@ async def mypy_typecheck_partition(
first_party_plugins: MyPyFirstPartyPlugins,
build_root: BuildRoot,
mypy: MyPy,
check_subsystem: CheckSubsystem,
python_setup: PythonSetup,
mkdir: MkdirBinary,
mktemp: MktempBinary,
Expand Down Expand Up @@ -366,6 +373,7 @@ async def mypy_typecheck_partition(
output_directories=(REPORT_DIR,),
description=f"Run MyPy on {pluralize(len(python_files), 'file')}.",
level=LogLevel.DEBUG,
cache_scope=check_subsystem.default_process_cache_scope,
append_only_caches={"mypy_cache": named_cache_dir},
),
**implicitly(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,3 +783,16 @@ class incredibly_long_type_name_to_force_wrapping_if_mypy_wrapped_error_messages
# at least one escape sequence that sets text color (red)
assert "\033[31m" in result[0].stdout
assert result[0].report == EMPTY_DIGEST


def test_force(rule_runner: PythonRuleRunner) -> None:
rule_runner.write_files(
{
f"{PACKAGE}/f.py": GOOD_FILE,
f"{PACKAGE}/BUILD": "python_sources()",
}
)
tgt = rule_runner.get_target(Address(PACKAGE, relative_file_path="f.py"))
result = run_mypy(rule_runner, [tgt], extra_args=["--check-force"])
assert len(result) == 1
assert result[0].exit_code == 0
5 changes: 4 additions & 1 deletion src/python/pants/backend/python/typecheck/pyright/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
PythonSourceFilesRequest,
prepare_python_sources,
)
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults, CheckSubsystem
from pants.core.util_rules import config_files
from pants.core.util_rules.config_files import ConfigFiles, find_config_file
from pants.core.util_rules.source_files import SourceFilesRequest, determine_source_files
Expand Down Expand Up @@ -167,6 +167,7 @@ async def _patch_config_file(
async def pyright_typecheck_partition(
partition: PyrightPartition,
pyright: Pyright,
check_subsystem: CheckSubsystem,
pex_environment: PexEnvironment,
nodejs: NodeJS,
sh_binary: ShBinary,
Expand Down Expand Up @@ -300,6 +301,8 @@ async def pyright_typecheck_partition(
process, argv=(sh_binary.path, "-c", shell_script), input_digest=input_digest
)

process = dataclasses.replace(process, cache_scope=check_subsystem.default_process_cache_scope)

result = await execute_process(process, **implicitly())
if result.exit_code == 249 and file_with_spaces:
logger.error(
Expand Down
4 changes: 3 additions & 1 deletion src/python/pants/backend/python/typecheck/pytype/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from pants.backend.python.util_rules.pex_environment import PexEnvironment
from pants.backend.python.util_rules.pex_from_targets import RequirementsPexRequest
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults, CheckSubsystem
from pants.core.goals.resolves import ExportableTool
from pants.core.util_rules import config_files
from pants.core.util_rules.config_files import find_config_file
Expand Down Expand Up @@ -90,6 +90,7 @@ class PytypePartitions(Collection[PytypePartition]):
async def pytype_typecheck_partition(
partition: PytypePartition,
pytype: Pytype,
check_subsystem: CheckSubsystem,
pex_environment: PexEnvironment,
) -> CheckResult:
roots_sources, requirements_pex, pytype_pex, config_files = await concurrently(
Expand Down Expand Up @@ -145,6 +146,7 @@ async def pytype_typecheck_partition(
concurrency_available=len(roots_sources.files),
description=f"Run Pytype on {pluralize(len(roots_sources.files), 'file')}.",
level=LogLevel.DEBUG,
cache_scope=check_subsystem.default_process_cache_scope,
)
)
)
Expand Down
12 changes: 9 additions & 3 deletions src/python/pants/backend/typescript/goals/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from pants.backend.typescript.tsconfig import AllTSConfigs, TSConfig, construct_effective_ts_configs
from pants.base.build_root import BuildRoot
from pants.build_graph.address import Address
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults, CheckSubsystem
from pants.core.target_types import FileSourceField
from pants.core.util_rules.system_binaries import CpBinary, FindBinary, MkdirBinary, TouchBinary
from pants.engine.fs import (
Expand Down Expand Up @@ -421,6 +421,7 @@ async def _prepare_tsc_build_process(
async def _typecheck_single_project(
project: NodeJSProject,
subsystem: TypeScriptSubsystem,
check_subsystem: CheckSubsystem,
global_options: GlobalOptions,
all_targets: AllTargets,
all_projects: AllNodeJSProjects,
Expand Down Expand Up @@ -464,6 +465,9 @@ async def _typecheck_single_project(
project_targets,
build_root,
)

process = replace(process, cache_scope=check_subsystem.default_process_cache_scope)

result = await execute_process(process, **implicitly())

return CheckResult.from_fallible_process_result(
Expand All @@ -477,11 +481,12 @@ async def _typecheck_single_project(
async def typecheck_typescript(
request: TypeScriptCheckRequest,
subsystem: TypeScriptSubsystem,
check_subsystem: CheckSubsystem,
global_options: GlobalOptions,
build_root: BuildRoot,
) -> CheckResults:
if subsystem.skip:
return CheckResults([], checker_name=request.tool_name)
return CheckResults([], checker_name=request.tool_name, output_per_partition=False)

field_sets = request.field_sets
(
Expand Down Expand Up @@ -513,6 +518,7 @@ async def typecheck_typescript(
_typecheck_single_project(
project,
subsystem,
check_subsystem,
global_options,
all_targets,
all_projects,
Expand All @@ -523,7 +529,7 @@ async def typecheck_typescript(
for project in projects
)

return CheckResults(project_results, checker_name=request.tool_name)
return CheckResults(project_results, checker_name=request.tool_name, output_per_partition=False)


def rules():
Expand Down
78 changes: 69 additions & 9 deletions src/python/pants/core/goals/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
from pants.engine.fs import EMPTY_DIGEST, Digest, Workspace
from pants.engine.goal import Goal, GoalSubsystem
from pants.engine.internals.selectors import concurrently
from pants.engine.process import FallibleProcessResult
from pants.engine.internals.session import RunId
from pants.engine.process import FallibleProcessResult, ProcessCacheScope, ProcessResultMetadata
from pants.engine.rules import QueryRule, collect_rules, goal_rule, implicitly, rule
from pants.engine.target import FieldSet, FilteredTargets
from pants.engine.unions import UnionMembership, union
from pants.option.option_types import BoolOption
from pants.util.logging import LogLevel
from pants.util.memo import memoized_property
from pants.util.meta import classproperty
Expand All @@ -45,6 +47,7 @@ class CheckResult:
stderr: str
partition_description: str | None = None
report: Digest = EMPTY_DIGEST
result_metadata: ProcessResultMetadata | None = None

@staticmethod
def from_fallible_process_result(
Expand All @@ -60,6 +63,7 @@ def from_fallible_process_result(
stderr=output_simplifier.simplify(process_result.stderr),
partition_description=partition_description,
report=report,
result_metadata=process_result.metadata,
)

def metadata(self) -> dict[str, Any]:
Expand All @@ -77,10 +81,18 @@ class CheckResults(EngineAwareReturnType):

results: tuple[CheckResult, ...]
checker_name: str
output_per_partition: bool

def __init__(self, results: Iterable[CheckResult], *, checker_name: str) -> None:
def __init__(
self,
results: Iterable[CheckResult],
*,
checker_name: str,
output_per_partition: bool = True,
) -> None:
object.__setattr__(self, "results", tuple(results))
object.__setattr__(self, "checker_name", checker_name)
object.__setattr__(self, "output_per_partition", output_per_partition)

@property
def skipped(self) -> bool:
Expand Down Expand Up @@ -177,13 +189,57 @@ def activated(cls, union_membership: UnionMembership) -> bool:
return CheckRequest in union_membership

only = OnlyOption("checker", "mypy", "javac")
force = BoolOption(
default=False,
help="Force checks to run, even if they could be satisfied from cache.",
)

@property
def default_process_cache_scope(self) -> ProcessCacheScope:
return ProcessCacheScope.PER_SESSION if self.force else ProcessCacheScope.SUCCESSFUL


class Check(Goal):
subsystem_cls = CheckSubsystem
environment_behavior = Goal.EnvironmentBehavior.USES_ENVIRONMENTS


_SOURCE_MAP = {
ProcessResultMetadata.Source.MEMOIZED: "memoized",
ProcessResultMetadata.Source.RAN: "ran",
ProcessResultMetadata.Source.HIT_LOCALLY: "cached locally",
ProcessResultMetadata.Source.HIT_REMOTELY: "cached remotely",
}


def _format_check_result(
checker_name: str,
result: CheckResult,
run_id: RunId,
console: Console,
) -> str:
"""Format a single check result for console output."""
sigil = console.sigil_succeeded() if result.exit_code == 0 else console.sigil_failed()
status = "succeeded" if result.exit_code == 0 else "failed"

desc = checker_name
if result.partition_description:
desc = f"{checker_name} ({result.partition_description})"

elapsed = ""
if result.result_metadata and result.result_metadata.total_elapsed_ms:
elapsed = f" in {result.result_metadata.total_elapsed_ms / 1000:.2f}s"

# Cache source (only show if not RAN)
source_desc = ""
if result.result_metadata:
source = result.result_metadata.source(run_id)
if source != ProcessResultMetadata.Source.RAN:
source_desc = f" ({_SOURCE_MAP.get(source, source.value)})"

return f"{sigil} {desc} {status}{elapsed}{source_desc}."


@goal_rule
async def check_goal(
console: Console,
Expand All @@ -192,6 +248,7 @@ async def check_goal(
dist_dir: DistDir,
union_membership: UnionMembership,
check_subsystem: CheckSubsystem,
run_id: RunId,
) -> Check:
request_types = cast("Iterable[type[CheckRequest]]", union_membership[CheckRequest])
specified_ids = determine_specified_tool_ids("check", check_subsystem.only, request_types)
Expand Down Expand Up @@ -245,14 +302,17 @@ async def check_goal(
for results in sorted(all_results, key=lambda results: results.checker_name):
if results.skipped:
continue
elif results.exit_code == 0:
sigil = console.sigil_succeeded()
status = "succeeded"
else:
sigil = console.sigil_failed()
status = "failed"
if results.exit_code != 0:
exit_code = results.exit_code
console.print_stderr(f"{sigil} {results.checker_name} {status}.")
if results.output_per_partition:
for result in results.results:
console.print_stderr(
_format_check_result(results.checker_name, result, run_id, console)
)
else:
sigil = console.sigil_succeeded() if results.exit_code == 0 else console.sigil_failed()
status = "succeeded" if results.exit_code == 0 else "failed"
console.print_stderr(f"{sigil} {results.checker_name} {status}.")

return Check(exit_code)

Expand Down
2 changes: 2 additions & 0 deletions src/python/pants/core/goals/check_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from pants.engine.addresses import Address
from pants.engine.environment import EnvironmentName
from pants.engine.fs import EMPTY_DIGEST, EMPTY_FILE_DIGEST, Workspace
from pants.engine.internals.session import RunId
from pants.engine.platform import Platform
from pants.engine.process import (
FallibleProcessResult,
Expand Down Expand Up @@ -173,6 +174,7 @@ def run_typecheck_rule(
DistDir(relpath=Path("dist")),
union_membership,
check_subsystem,
RunId(0),
],
mock_calls={
"pants.core.environments.rules.resolve_environment_name": lambda a: EnvironmentName(
Expand Down
Loading