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

Fix PluginField not working in production #10469

Merged
merged 7 commits into from Jul 28, 2020
Expand Up @@ -3,12 +3,11 @@

from pants.backend.codegen.protobuf.target_types import ProtobufLibrary
from pants.backend.python.target_types import PythonInterpreterCompatibility
from pants.engine.unions import UnionRule


class ProtobufPythonInterpreterCompatibility(PythonInterpreterCompatibility):
alias = "python_compatibility"


def rules():
return [UnionRule(ProtobufLibrary.PluginField, ProtobufPythonInterpreterCompatibility)]
return [ProtobufLibrary.register_plugin_field(ProtobufPythonInterpreterCompatibility)]
4 changes: 2 additions & 2 deletions src/python/pants/backend/pants_info/list_target_types_test.py
Expand Up @@ -8,7 +8,7 @@
from pants.backend.pants_info.list_target_types import TargetTypesSubsystem, list_target_types
from pants.core.util_rules.pants_bin import PantsBin
from pants.engine.target import BoolField, IntField, RegisteredTargetTypes, StringField, Target
from pants.engine.unions import UnionMembership, UnionRule
from pants.engine.unions import UnionMembership
from pants.testutil.engine.util import MockConsole, create_goal_subsystem, run_rule


Expand Down Expand Up @@ -121,7 +121,7 @@ class CustomField(BoolField):

tests_target_stdout = run_goal(
union_membership=UnionMembership.from_rules(
[UnionRule(FortranTests.PluginField, CustomField)]
[FortranTests.register_plugin_field(CustomField)]
),
details_target=FortranTests.alias,
)
Expand Down
9 changes: 3 additions & 6 deletions src/python/pants/engine/fs.py
Expand Up @@ -15,13 +15,10 @@

@dataclass(frozen=True)
class Digest:
"""A Digest is a content-digest fingerprint, and a length of underlying content.
"""A Digest is a lightweight reference to a set of files known about by the engine.

Typically, this is used as a lightweight reference to a set of files known about by the engine.
You can use `await Get(Snapshot, Digest)` to set the file names referred to, or use
`await Get(DigestContents, Digest)` to see the actual file content.

Occasionally, a Digest is a reference for other types of strings/bytes/content.
You can use `await Get(Snapshot, Digest)` to set the file names referred to, or use `await
Get(DigestContents, Digest)` to see the actual file content.
"""

fingerprint: str
Expand Down
6 changes: 4 additions & 2 deletions src/python/pants/engine/internals/graph.py
Expand Up @@ -94,7 +94,9 @@

@rule
async def resolve_target(
address: Address, registered_target_types: RegisteredTargetTypes
address: Address,
registered_target_types: RegisteredTargetTypes,
union_membership: UnionMembership,
) -> WrappedTarget:
if address.generated_base_target_name:
base_target = await Get(WrappedTarget, Address, address.maybe_convert_to_base_target())
Expand All @@ -110,7 +112,7 @@ async def resolve_target(
raise UnrecognizedTargetTypeException(
target_adaptor.type_alias, registered_target_types, address=address
)
target = target_type(target_adaptor.kwargs, address=address)
target = target_type(target_adaptor.kwargs, address=address, union_membership=union_membership)
return WrappedTarget(target)


Expand Down
36 changes: 23 additions & 13 deletions src/python/pants/engine/target.py
Expand Up @@ -29,11 +29,11 @@
from pants.engine.addresses import Address, assert_single_address
from pants.engine.collection import Collection, DeduplicatedCollection
from pants.engine.fs import Snapshot
from pants.engine.unions import UnionMembership, union
from pants.engine.unions import UnionMembership, UnionRule, union
from pants.source.filespec import Filespec
from pants.util.collections import ensure_list, ensure_str_list
from pants.util.frozendict import FrozenDict
from pants.util.memo import memoized_property
from pants.util.memo import memoized_classproperty, memoized_property
from pants.util.meta import frozen_after_init
from pants.util.ordered_set import FrozenOrderedSet
from pants.util.strutil import pluralize
Expand Down Expand Up @@ -294,16 +294,17 @@ def __init__(
def field_types(self) -> Tuple[Type[Field], ...]:
return (*self.core_fields, *self.plugin_fields)

@union
@final
class PluginField:
"""A sentinel class to allow plugin authors to add additional fields to this target type.
@memoized_classproperty
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.

Plugin authors may add additional fields by simply registering UnionRules between the
`Target.PluginField` and the custom field, e.g. `UnionRule(PythonLibrary.PluginField,
TypeChecked)`. The `Target` will then treat `TypeChecked` as a first-class citizen and
plugins can use that Field like any other Field.
"""
@union
class PluginField:
pass

return PluginField

def __repr__(self) -> str:
fields = ", ".join(str(field) for field in self.field_values.values())
Expand All @@ -322,9 +323,7 @@ def __str__(self) -> str:
@final
@classmethod
def _find_plugin_fields(cls, union_membership: UnionMembership) -> Tuple[Type[Field], ...]:
return cast(
Tuple[Type[Field], ...], tuple(union_membership.union_rules.get(cls.PluginField, ()))
)
return cast(Tuple[Type[Field], ...], tuple(union_membership.get(cls._plugin_field_cls)))

@final
@classmethod
Expand Down Expand Up @@ -469,6 +468,17 @@ def class_has_fields(
fields, registered_fields=cls.class_field_types(union_membership=union_membership)
)

@final
@classmethod
def register_plugin_field(cls, field: Type[Field]) -> UnionRule:
"""Register a new field on the target type.

In the `rules()` register.py entry-point, include
`MyTarget.register_plugin_field(NewField)`. This will register `NewField` as a first-class
citizen. Plugins can use this new field like any other.
"""
return UnionRule(cls._plugin_field_cls, field)


@dataclass(frozen=True)
class WrappedTarget:
Expand Down
13 changes: 11 additions & 2 deletions src/python/pants/engine/target_test.py
Expand Up @@ -39,7 +39,7 @@
generate_subtarget,
generate_subtarget_address,
)
from pants.engine.unions import UnionMembership, UnionRule
from pants.engine.unions import UnionMembership
from pants.testutil.engine.util import MockGet, run_rule
from pants.util.collections import ensure_str_list
from pants.util.frozendict import FrozenDict
Expand Down Expand Up @@ -263,7 +263,7 @@ class CustomField(BoolField):
default = False

union_membership = UnionMembership.from_rules(
[UnionRule(FortranTarget.PluginField, CustomField)]
[FortranTarget.register_plugin_field(CustomField)]
)
tgt_values = {CustomField.alias: True}
tgt = FortranTarget(
Expand All @@ -289,6 +289,15 @@ class CustomField(BoolField):
)
assert default_tgt[CustomField].value is False

# Ensure that the `PluginField` is not being registered on other target types.
class OtherTarget(Target):
alias = "other_target"
core_fields = ()

other_tgt = OtherTarget({}, address=Address.parse(":other"))
assert other_tgt.plugin_fields == ()
assert other_tgt.has_field(CustomField) is False


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
Expand Down