Skip to content

Commit

Permalink
Add new visibility field to targets to limit target dependencies.
Browse files Browse the repository at this point in the history
Also introduces a new `defaults` target which allows to dynamically provide "scoped" default field values.

Closes pantsbuild#13393.

[ci skip-rust]

[ci skip-build-wheels]
  • Loading branch information
kaos committed Jun 10, 2022
1 parent 960f12f commit 096010b
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 6 deletions.
8 changes: 5 additions & 3 deletions src/python/pants/core/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from pants.core.target_types import (
ArchiveTarget,
DefaultsTarget,
FilesGeneratorTarget,
FileTarget,
GenericTarget,
Expand Down Expand Up @@ -84,12 +85,13 @@ def rules():
def target_types():
return [
ArchiveTarget,
FileTarget,
DefaultsTarget,
FilesGeneratorTarget,
FileTarget,
GenericTarget,
ResourceTarget,
ResourcesGeneratorTarget,
RelocatedFiles,
ResourcesGeneratorTarget,
ResourceTarget,
]


Expand Down
44 changes: 43 additions & 1 deletion src/python/pants/core/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,10 @@ async def _hydrate_asset_source(request: GenerateSourcesRequest) -> GeneratedSou


# -----------------------------------------------------------------------------------------------
# `file` and`files` targets
# `file` and `files` targets
# -----------------------------------------------------------------------------------------------


class FileSourceField(AssetSourceField):
uses_source_roots = False

Expand Down Expand Up @@ -708,6 +710,46 @@ async def package_archive_target(field_set: ArchiveFieldSet) -> BuiltPackage:
return BuiltPackage(archive, (BuiltPackageArtifact(output_filename),))


# -----------------------------------------------------------------------------------------------
# `defaults` target
# -----------------------------------------------------------------------------------------------


class DefaultsTarget(Target):
alias = "defaults"
core_fields = COMMON_TARGET_FIELDS
help = softwrap(
"""
A meta target providing scoped default values for all closest targets from current directory
and below.
"""
)


@dataclass(frozen=True)
class DefaultsRequest:
path: str
target_name: str | None = None

@classmethod
def for_target(cls, target: Target, defaults_name: str | None = None) -> DefaultsRequest:
return cls(target.address.spec_path, defaults_name)


@rule
async def get_defaults(request: DefaultsRequest, all_targets: AllTargets) -> Targets:
"""Fetch all applicable `defaults` targets, sorted on specificity (closest first)."""
defaults = []
for tgt in all_targets:
if not isinstance(tgt, DefaultsTarget):
continue
if request.target_name in (None, tgt.address.target_name) and request.path.startswith(
tgt.address.spec_path
):
defaults.append(tgt)
return Targets(sorted(defaults, key=lambda d: len(d.address.spec_path), reverse=True))


def rules():
return (
*collect_rules(),
Expand Down
74 changes: 74 additions & 0 deletions src/python/pants/engine/internals/visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from pants.core.target_types import DefaultsRequest
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import (
FieldSet,
Targets,
ValidatedDependencies,
ValidateDependenciesRequest,
VisibilityField,
VisibilityViolationError,
WrappedTarget,
WrappedTargetRequest,
)
from pants.engine.unions import UnionRule
from pants.util.strutil import softwrap


class ValidateVisibilityRulesFieldSet(FieldSet):
required_fields = (VisibilityField,)


class ValidateVisibilityRulesRequest(ValidateDependenciesRequest):
field_set_type = ValidateVisibilityRulesFieldSet


@rule
async def validate_visibility_rules(
request: ValidateVisibilityRulesRequest,
) -> ValidatedDependencies:
wrapped_dependency_targets = await MultiGet(
Get(WrappedTarget, WrappedTargetRequest(address, description_of_origin="<infallible>"))
for address in request.dependencies
)
dependency_targets = [wrapped.target for wrapped in wrapped_dependency_targets]
targets_defaults = await MultiGet(
Get(Targets, DefaultsRequest, DefaultsRequest.for_target(tgt, "visibility"))
for tgt in dependency_targets
)
subject = request.field_set.address
for target, defaults in zip(dependency_targets, targets_defaults):
if defaults:
default_visibility = defaults[0].get(VisibilityField).value
default_origin = defaults[0]
else:
default_visibility = (VisibilityField.PUBLIC_VISIBILITY,)
default_origin = target
visibility_field = target.get(VisibilityField)
if visibility_field.value is not None:
origin = target
else:
origin = default_origin
if not visibility_field.visible(origin.address, subject, default=default_visibility):
raise VisibilityViolationError(
softwrap(
f"""
{target.address} is not visible to {subject}.
Visibility from {origin.alias} {origin.address} : \
{", ".join(visibility_field.get_visibility(default_visibility) or ("<none>",))}.
"""
)
)
return ValidatedDependencies()


def rules():
return (
*collect_rules(),
UnionRule(ValidateDependenciesRequest, ValidateVisibilityRulesRequest),
)
33 changes: 32 additions & 1 deletion src/python/pants/engine/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,10 @@ def __init__(
)


class VisibilityViolationError(Exception):
pass


# -----------------------------------------------------------------------------------------------
# Field templates
# -----------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -2658,7 +2662,34 @@ class DescriptionField(StringField):
)


COMMON_TARGET_FIELDS = (Tags, DescriptionField)
class VisibilityField(StringSequenceField):
PUBLIC_VISIBILITY = "public"
PRIVATE_VISIBILITY = "private"

alias = "visibility"
help = softwrap(
"""
TBW.
"""
)

def get_visibility(self, default: tuple[str, ...] | None = None) -> tuple[str, ...]:
return next(value for value in (self.value, default, ()) if value is not None)

def visible(
self, origin: Address, destination: Address, default: tuple[str, ...] | None = None
) -> bool:
for rule in self.get_visibility(default):
if rule == self.PUBLIC_VISIBILITY:
return True
if rule == self.PRIVATE_VISIBILITY:
return destination.spec_path.startswith(origin.spec_path)
if destination.spec_path.startswith(rule):
return True
return False


COMMON_TARGET_FIELDS = (Tags, DescriptionField, VisibilityField)


class OverridesField(AsyncFieldMixin, Field):
Expand Down
3 changes: 2 additions & 1 deletion src/python/pants/init/engine_initializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pants.engine.console import Console
from pants.engine.fs import PathGlobs, Snapshot, Workspace
from pants.engine.goal import Goal
from pants.engine.internals import build_files, graph, options_parsing, specs_rules
from pants.engine.internals import build_files, graph, options_parsing, specs_rules, visibility
from pants.engine.internals.native_engine import PyExecutor, PySessionCancellationLatch
from pants.engine.internals.parser import Parser
from pants.engine.internals.scheduler import Scheduler, SchedulerSession
Expand Down Expand Up @@ -266,6 +266,7 @@ def build_root_singleton() -> BuildRoot:
*changed_rules(),
*streaming_workunit_handler_rules(),
*specs_calculator.rules(),
*visibility.rules(),
*rules,
)
)
Expand Down

0 comments on commit 096010b

Please sign in to comment.