Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace include_special_cased_deps flag with should_traverse_deps_predicate #19272

Merged
merged 20 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c95aa60
Add `ShouldTraverseDepsPredicate`
cognifloyd Jun 8, 2023
c2c6f26
Handle SpecialCasedDependencies in `ShouldTraverseDepsPredicate`
cognifloyd Jun 8, 2023
ef7a09f
Replace `include_special_cased_deps=True` flag with `should_traverse_…
cognifloyd Jun 8, 2023
e30672c
Drop `include_special_cased_deps` flag
cognifloyd Jun 8, 2023
941c3c2
fmt
cognifloyd Jun 8, 2023
c1c9252
Add `ExplicitlyProvidedDependenciesRequest`
cognifloyd Jun 8, 2023
c1ed5a2
Add DepsTraversalPredicates class as a mini-namespace for predicates
cognifloyd Jun 8, 2023
39e5bde
Merge branch 'main' into cognifloyd/deps-predicate
cognifloyd Jun 8, 2023
434a8fb
Correct type hints for DepsTraversalPredicates methods
cognifloyd Jun 8, 2023
945b50b
comment typo
cognifloyd Jun 8, 2023
869b91f
Merge branch 'main' into cognifloyd/deps-predicate
cognifloyd Jun 8, 2023
f0a808c
Merge branch 'main' into cognifloyd/deps-predicate
cognifloyd Jun 8, 2023
a77b046
Use TypeAlias to move ShouldTraverseDepsPredicate closer to implement…
cognifloyd Jun 8, 2023
412e994
Merge branch 'main' into cognifloyd/deps-predicate
cognifloyd Jun 12, 2023
0b91c76
Turn ShouldTraverseDepsPredicate into a dataclass
cognifloyd Jun 13, 2023
57660a6
cleanup test
cognifloyd Jun 13, 2023
d0364f5
Add test for frozen ShouldTraverseDepsPredicate subclasses
cognifloyd Jun 13, 2023
b856127
Merge branch 'main' into cognifloyd/deps-predicate
cognifloyd Jun 13, 2023
7ce2bce
Improve comment on _callable
cognifloyd Jun 13, 2023
6a28cd6
reword comment about subclassing ShouldTraverseDepsPredicate
cognifloyd Jun 13, 2023
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
8 changes: 6 additions & 2 deletions src/python/pants/backend/project_info/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
TransitiveTargets,
TransitiveTargetsRequest,
UnexpandedTargets,
always_traverse,
cognifloyd marked this conversation as resolved.
Show resolved Hide resolved
)
from pants.option.option_types import BoolOption

Expand Down Expand Up @@ -43,7 +44,8 @@ async def dependencies(
) -> Dependencies:
if dependencies_subsystem.transitive:
transitive_targets = await Get(
TransitiveTargets, TransitiveTargetsRequest(addresses, include_special_cased_deps=True)
TransitiveTargets,
TransitiveTargetsRequest(addresses, should_traverse_deps_predicate=always_traverse),
)
targets = Targets(transitive_targets.dependencies)
else:
Expand All @@ -55,7 +57,9 @@ async def dependencies(
dependencies_per_target_root = await MultiGet(
Get(
Targets,
DependenciesRequest(tgt.get(DependenciesField), include_special_cased_deps=True),
DependenciesRequest(
tgt.get(DependenciesField), should_traverse_deps_predicate=always_traverse
),
)
for tgt in target_roots
)
Expand Down
14 changes: 12 additions & 2 deletions src/python/pants/backend/project_info/dependents.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
from pants.engine.console import Console
from pants.engine.goal import Goal, GoalSubsystem, LineOriented
from pants.engine.rules import Get, MultiGet, collect_rules, goal_rule, rule
from pants.engine.target import AllUnexpandedTargets, Dependencies, DependenciesRequest
from pants.engine.target import (
AllUnexpandedTargets,
Dependencies,
DependenciesRequest,
always_traverse,
)
from pants.option.option_types import BoolOption
from pants.util.frozendict import FrozenDict
from pants.util.logging import LogLevel
Expand All @@ -25,7 +30,12 @@ class AddressToDependents:
@rule(desc="Map all targets to their dependents", level=LogLevel.DEBUG)
async def map_addresses_to_dependents(all_targets: AllUnexpandedTargets) -> AddressToDependents:
dependencies_per_target = await MultiGet(
Get(Addresses, DependenciesRequest(tgt.get(Dependencies), include_special_cased_deps=True))
Get(
Addresses,
DependenciesRequest(
tgt.get(Dependencies), should_traverse_deps_predicate=always_traverse
),
)
for tgt in all_targets
)

Expand Down
4 changes: 3 additions & 1 deletion src/python/pants/backend/project_info/filedeps.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
TransitiveTargets,
TransitiveTargetsRequest,
UnexpandedTargets,
always_traverse,
)
from pants.option.option_types import BoolOption
from pants.util.strutil import softwrap
Expand Down Expand Up @@ -72,7 +73,8 @@ async def file_deps(
targets: Iterable[Target]
if filedeps_subsystem.transitive:
transitive_targets = await Get(
TransitiveTargets, TransitiveTargetsRequest(addresses, include_special_cased_deps=True)
TransitiveTargets,
TransitiveTargetsRequest(addresses, should_traverse_deps_predicate=always_traverse),
)
targets = transitive_targets.closure
else:
Expand Down
8 changes: 6 additions & 2 deletions src/python/pants/backend/project_info/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Targets,
TransitiveTargets,
TransitiveTargetsRequest,
always_traverse,
)
from pants.option.option_types import StrOption

Expand Down Expand Up @@ -114,13 +115,16 @@ async def paths(console: Console, paths_subsystem: PathsSubsystem) -> PathsGoal:
destination = to_tgts.expect_single()

transitive_targets = await Get(
TransitiveTargets, TransitiveTargetsRequest([root.address], include_special_cased_deps=True)
TransitiveTargets,
TransitiveTargetsRequest([root.address], should_traverse_deps_predicate=always_traverse),
)

adjacent_targets_per_target = await MultiGet(
Get(
Targets,
DependenciesRequest(tgt.get(Dependencies), include_special_cased_deps=True),
DependenciesRequest(
tgt.get(Dependencies), should_traverse_deps_predicate=always_traverse
),
)
for tgt in transitive_targets.closure
)
Expand Down
5 changes: 4 additions & 1 deletion src/python/pants/backend/project_info/peek.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Target,
Targets,
UnexpandedTargets,
always_traverse,
)
from pants.option.option_types import BoolOption
from pants.util.strutil import softwrap
Expand Down Expand Up @@ -179,7 +180,9 @@ async def get_target_data(
dependencies_per_target = await MultiGet(
Get(
Targets,
DependenciesRequest(tgt.get(Dependencies), include_special_cased_deps=True),
DependenciesRequest(
tgt.get(Dependencies), should_traverse_deps_predicate=always_traverse
),
)
for tgt in sorted_targets
)
Expand Down
8 changes: 7 additions & 1 deletion src/python/pants/backend/visibility/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DependenciesRuleApplication,
DependenciesRuleApplicationRequest,
FieldSet,
always_traverse,
)
from pants.util.logging import LogLevel

Expand All @@ -39,7 +40,12 @@ async def check_visibility_rule_violations(
request: EnforceVisibilityRules.Batch, platform: Platform, run_id: RunId
) -> LintResult:
all_dependencies = await MultiGet(
Get(Addresses, DependenciesRequest(field_set.dependencies, include_special_cased_deps=True))
Get(
Addresses,
DependenciesRequest(
field_set.dependencies, should_traverse_deps_predicate=always_traverse
),
)
for field_set in request.elements
)
all_dependencies_rule_action = await MultiGet(
Expand Down
84 changes: 55 additions & 29 deletions src/python/pants/engine/internals/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
Dependencies,
DependenciesRequest,
ExplicitlyProvidedDependencies,
ExplicitlyProvidedDependenciesRequest,
Field,
FieldDefaultFactoryRequest,
FieldDefaultFactoryResult,
Expand Down Expand Up @@ -700,7 +701,7 @@ async def transitive_dependency_mapping(request: _DependencyMappingRequest) -> _
Targets,
DependenciesRequest(
tgt.get(Dependencies),
include_special_cased_deps=request.tt_request.include_special_cased_deps,
should_traverse_deps_predicate=request.tt_request.should_traverse_deps_predicate,
),
)
for tgt in queued
Expand All @@ -711,7 +712,7 @@ async def transitive_dependency_mapping(request: _DependencyMappingRequest) -> _
UnexpandedTargets,
DependenciesRequest(
tgt.get(Dependencies),
include_special_cased_deps=request.tt_request.include_special_cased_deps,
should_traverse_deps_predicate=request.tt_request.should_traverse_deps_predicate,
),
)
for tgt in queued
Expand Down Expand Up @@ -787,7 +788,8 @@ async def coarsened_targets(
_DependencyMapping,
_DependencyMappingRequest(
TransitiveTargetsRequest(
request.roots, include_special_cased_deps=request.include_special_cased_deps
request.roots,
should_traverse_deps_predicate=request.should_traverse_deps_predicate,
),
expanded_targets=request.expanded_targets,
),
Expand Down Expand Up @@ -1205,8 +1207,24 @@ def __init__(


@rule
async def determine_explicitly_provided_dependencies(
async def convert_dependencies_request_to_explicitly_provided_dependencies_request(
request: DependenciesRequest,
) -> ExplicitlyProvidedDependenciesRequest:
"""This rule discards any deps predicate from DependenciesRequest.

Calculating ExplicitlyProvidedDependencies does not use any deps traversal predicates as it is
meant to list all explicit deps from the given field. By stripping the predicate from the
request, we ensure that the cache key for ExplicitlyProvidedDependencies calculation does not
include the predicate increasing the cache-hit rate.
"""
# TODO: Maybe require Get(ExplicitlyProvidedDependencies, ExplicitlyProvidedDependenciesRequest)
# and deprecate Get(ExplicitlyProvidedDependencies, DependenciesRequest) via this rule.
return ExplicitlyProvidedDependenciesRequest(request.field)
Comment on lines +1210 to +1222
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #19155, I just had a comment on the determine_explicitly_provided_dependencies rule that said: NB: This rule ignores request.should_traverse_deps_predicate.

@thejcannon recommended I use an actual type instead of just a comment in #19155 (comment)

We might consider a different input type that doesn't have the ignored attribute?

So, I added this ExplicitlyProvidedDependenciesRequest with a rule to minimize API churn in this PR.

Do we want to deprecate Get(ExplicitlyProvidedDependencies, DependenciesRequest)? If/when we do that, we can probably put a deprecation warning in this rule.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a good idea, although feel free to carry this over into a future PR so we can merge this one.

Also up to you here 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can deprecate once we have updated all uses within the pants repo. So, it fits best in a future PR.



@rule
async def determine_explicitly_provided_dependencies(
request: ExplicitlyProvidedDependenciesRequest,
union_membership: UnionMembership,
registered_target_types: RegisteredTargetTypes,
subproject_roots: SubprojectRoots,
Expand Down Expand Up @@ -1300,6 +1318,13 @@ async def resolve_dependencies(
WrappedTargetRequest(request.field.address, description_of_origin="<infallible>"),
)
tgt = wrapped_tgt.target

# This predicate allows the dep graph to ignore dependencies of selected targets
# including any explicit deps and any inferred deps.
# For example, to avoid traversing the deps of package targets.
if not request.should_traverse_deps_predicate(tgt, request.field):
return Addresses([])

try:
explicitly_provided = await Get(
ExplicitlyProvidedDependencies, DependenciesRequest, request
Expand Down Expand Up @@ -1378,33 +1403,34 @@ async def resolve_dependencies(
# If the target has `SpecialCasedDependencies`, such as the `archive` target having
# `files` and `packages` fields, then we possibly include those too. We don't want to always
# include those dependencies because they should often be excluded from the result due to
# being handled elsewhere in the calling code.
special_cased: tuple[Address, ...] = ()
if request.include_special_cased_deps:
# Unlike normal, we don't use `tgt.get()` because there may be >1 subclass of
# SpecialCasedDependencies.
special_cased_fields = tuple(
field
for field in tgt.field_values.values()
if isinstance(field, SpecialCasedDependencies)
)
# We can't use the normal `Get(Addresses, UnparsedAddressInputs)` due to a graph cycle.
special_cased = await MultiGet(
Get(
Address,
AddressInput,
AddressInput.parse(
addr,
relative_to=tgt.address.spec_path,
subproject_roots=subproject_roots,
description_of_origin=(
f"the `{special_cased_field.alias}` field from the target {tgt.address}"
),
# being handled elsewhere in the calling code. So, we only include fields based on
# the should_traverse_deps_predicate.

# Unlike normal, we don't use `tgt.get()` because there may be >1 subclass of
# SpecialCasedDependencies.
special_cased_fields = tuple(
field
for field in tgt.field_values.values()
if isinstance(field, SpecialCasedDependencies)
and request.should_traverse_deps_predicate(tgt, field)
)
# We can't use the normal `Get(Addresses, UnparsedAddressInputs)` due to a graph cycle.
special_cased = await MultiGet(
Get(
Address,
AddressInput,
AddressInput.parse(
addr,
relative_to=tgt.address.spec_path,
subproject_roots=subproject_roots,
description_of_origin=(
f"the `{special_cased_field.alias}` field from the target {tgt.address}"
),
)
for special_cased_field in special_cased_fields
for addr in special_cased_field.to_unparsed_address_inputs().values
),
)
for special_cased_field in special_cased_fields
for addr in special_cased_field.to_unparsed_address_inputs().values
)

excluded = explicitly_provided_ignores.union(
*itertools.chain(deps.exclude for deps in inferred)
Expand Down
72 changes: 70 additions & 2 deletions src/python/pants/engine/internals/graph_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
Targets,
TransitiveTargets,
TransitiveTargetsRequest,
always_traverse,
)
from pants.engine.unions import UnionMembership, UnionRule
from pants.source.filespec import Filespec
Expand Down Expand Up @@ -314,7 +315,8 @@ def get_target(name: str) -> Target:
assert direct_deps == Targets()

direct_deps = transitive_targets_rule_runner.request(
Targets, [DependenciesRequest(root[Dependencies], include_special_cased_deps=True)]
Targets,
[DependenciesRequest(root[Dependencies], should_traverse_deps_predicate=always_traverse)],
)
assert direct_deps == Targets([d1, d2, d3])

Expand All @@ -327,7 +329,11 @@ def get_target(name: str) -> Target:

transitive_targets = transitive_targets_rule_runner.request(
TransitiveTargets,
[TransitiveTargetsRequest([root.address, d2.address], include_special_cased_deps=True)],
[
TransitiveTargetsRequest(
[root.address, d2.address], should_traverse_deps_predicate=always_traverse
)
],
)
assert transitive_targets.roots == (root, d2)
assert transitive_targets.dependencies == FrozenOrderedSet([d1, d2, d3, t2, t1])
Expand Down Expand Up @@ -367,6 +373,68 @@ def test_transitive_targets_tolerates_generated_target_cycles(
]


def test_transitive_targets_with_should_traverse_deps_predicate(
transitive_targets_rule_runner: RuleRunner,
) -> None:
transitive_targets_rule_runner.write_files(
{
"BUILD": dedent(
"""\
target(name='t1')
target(name='t2', dependencies=[':t1'])
target(name='t3')
target(name='d1', dependencies=[':t1'])
target(name='d2', dependencies=[':t2'])
target(name='d3')
target(name='skipped', dependencies=[':t3'])
target(name='d4', tags=['skip_deps'], dependencies=[':skipped'])
target(name='root', dependencies=[':d1', ':d2', ':d3', ':d4'])
"""
),
}
)

def get_target(name: str) -> Target:
return transitive_targets_rule_runner.get_target(Address("", target_name=name))

t1 = get_target("t1")
t2 = get_target("t2")
t3 = get_target("t3")
d1 = get_target("d1")
d2 = get_target("d2")
d3 = get_target("d3")
skipped = get_target("skipped")
d4 = get_target("d4")
root = get_target("root")

direct_deps = transitive_targets_rule_runner.request(
Targets, [DependenciesRequest(root[Dependencies])]
)
assert direct_deps == Targets([d1, d2, d3, d4])

transitive_targets = transitive_targets_rule_runner.request(
TransitiveTargets,
[
TransitiveTargetsRequest(
[root.address, d2.address],
should_traverse_deps_predicate=lambda tgt, fld: "skip_deps"
not in (tgt[Tags].value or []),
)
],
)
assert transitive_targets.roots == (root, d2)
# NB: `//:d2` is both a target root and a dependency of `//:root`.
assert transitive_targets.dependencies == FrozenOrderedSet([d1, d2, d3, d4, t2, t1])
assert transitive_targets.closure == FrozenOrderedSet([root, d2, d1, d3, d4, t2, t1])
# `//:d4` depends on `//:skipped` which depends on `//:t3`.
# Nothing else depends on `//:skipped` or `//:t3`, so they should not
# be present in the list of transitive deps thanks to `should_traverse_deps_predicate`.
assert skipped not in transitive_targets.dependencies
assert t3 not in transitive_targets.dependencies
assert skipped not in transitive_targets.closure
assert t3 not in transitive_targets.closure


def test_coarsened_targets(transitive_targets_rule_runner: RuleRunner) -> None:
"""CoarsenedTargets should "coarsen" a cycle into a single CoarsenedTarget instance."""
transitive_targets_rule_runner.write_files(
Expand Down
Loading