Skip to content

Commit

Permalink
Plugin fields should propagate to subclassed target types. (#15876)
Browse files Browse the repository at this point in the history
  • Loading branch information
kaos committed Jun 24, 2022
1 parent e4bf4d7 commit 643d8da
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/python/pants/build_graph/build_configuration.py
Expand Up @@ -243,6 +243,10 @@ def register_target_types(
)
for target_type in target_types:
self._target_type_to_providers[target_type].append(plugin_or_backend)
# Access the Target._plugin_field_cls here to ensure the PluginField class is
# created before the UnionMembership is instantiated, as the class hierarchy is
# walked during union membership setup.
_ = target_type._plugin_field_cls

def allow_unknown_options(self, allow: bool = True) -> None:
"""Allows overriding whether Options parsing will fail for unrecognized Options.
Expand Down
10 changes: 9 additions & 1 deletion src/python/pants/engine/target.py
Expand Up @@ -406,8 +406,16 @@ def _plugin_field_cls(cls) -> type:
# NB: We ensure that each Target subtype has its own `PluginField` class so that
# registering a plugin field doesn't leak across target types.

baseclass = (
object
if cast("Type[Target]", cls) is Target
else next(
base for base in cast("Type[Target]", cls).__bases__ if issubclass(base, Target)
)._plugin_field_cls
)

@union
class PluginField:
class PluginField(baseclass): # type: ignore[misc, valid-type]
pass

return PluginField
Expand Down
22 changes: 22 additions & 0 deletions src/python/pants/engine/target_test.py
Expand Up @@ -296,6 +296,28 @@ class OtherTarget(Target):
assert other_tgt.has_field(CustomField) is False


def test_subclassed_target_inherits_plugin_fields() -> None:
class CustomFortranTarget(FortranTarget):
alias = "custom_fortran"

# Ensure that any `PluginField` is not lost on subclasses.
assert issubclass(CustomFortranTarget._plugin_field_cls, FortranTarget._plugin_field_cls)
assert issubclass(FortranTarget._plugin_field_cls, Target._plugin_field_cls)

class CustomField(BoolField):
alias = "custom_field"
default = False

union_membership = UnionMembership.from_rules(
[FortranTarget.register_plugin_field(CustomField)]
)

custom_tgt = CustomFortranTarget(
{}, Address("", target_name="custom"), union_membership=union_membership
)
assert custom_tgt.has_field(CustomField) is True


def test_override_preexisting_field_via_new_target() -> None:
# To change the behavior of a pre-existing field, you must create a new target as it would not
# be safe to allow plugin authors to change the behavior of core target types.
Expand Down
8 changes: 8 additions & 0 deletions src/python/pants/engine/unions.py
Expand Up @@ -72,6 +72,14 @@ def from_rules(cls, rules: Iterable[UnionRule]) -> UnionMembership:
mapping: DefaultDict[type, OrderedSet[type]] = defaultdict(OrderedSet)
for rule in rules:
mapping[rule.union_base].add(rule.union_member)
# Subclassed union bases should inherit the superclass's union members.
bases = list(mapping.keys())
while len(bases) > 0:
union_base = bases.pop()
for sub_union in union_base.__subclasses__():
if sub_union not in mapping:
bases.append(sub_union)
mapping[sub_union].update(mapping[union_base])
return cls(mapping)

def __init__(self, union_rules: Mapping[type, Iterable[type]]) -> None:
Expand Down

0 comments on commit 643d8da

Please sign in to comment.