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

More declarative target generators for plugins #14377

Merged
merged 7 commits into from
Feb 10, 2022
Merged
Show file tree
Hide file tree
Changes from 6 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
60 changes: 10 additions & 50 deletions src/python/pants/backend/codegen/avro/target_types.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.engine.fs import PathGlobs, Paths
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.rules import collect_rules, rule
from pants.engine.target import (
COMMON_TARGET_FIELDS,
AllTargets,
Dependencies,
GeneratedTargets,
GenerateTargetsRequest,
MultipleSourcesField,
OverridesField,
SingleSourceField,
SourcesPaths,
SourcesPathsRequest,
Target,
TargetFilesGenerator,
Targets,
generate_file_based_overrides_field_help_message,
generate_file_level_targets,
)
from pants.engine.unions import UnionMembership, UnionRule
from pants.option.global_options import FilesNotFoundBehavior
from pants.util.docutil import doc_url
from pants.util.logging import LogLevel

Expand Down Expand Up @@ -81,55 +74,22 @@ class AvroSourcesOverridesField(OverridesField):
)


class AvroSourcesGeneratorTarget(Target):
class AvroSourcesGeneratorTarget(TargetFilesGenerator):
alias = "avro_sources"
core_fields = (
*COMMON_TARGET_FIELDS,
AvroDependenciesField,
AvroSourcesGeneratingSourcesField,
AvroSourcesOverridesField,
)
help = "Generate a `avro_source` target for each file in the `sources` field."


class GenerateTargetsFromAvroSources(GenerateTargetsRequest):
generate_from = AvroSourcesGeneratorTarget


@rule
async def generate_targets_from_avro_sources(
request: GenerateTargetsFromAvroSources,
files_not_found_behavior: FilesNotFoundBehavior,
union_membership: UnionMembership,
) -> GeneratedTargets:
sources_paths = await Get(
SourcesPaths, SourcesPathsRequest(request.generator[AvroSourcesGeneratingSourcesField])
)

all_overrides = {}
overrides_field = request.generator[OverridesField]
if overrides_field.value:
_all_override_paths = await MultiGet(
Get(Paths, PathGlobs, path_globs)
for path_globs in overrides_field.to_path_globs(files_not_found_behavior)
)
all_overrides = overrides_field.flatten_paths(
dict(zip(_all_override_paths, overrides_field.value.values()))
)

return generate_file_level_targets(
AvroSourceTarget,
request.generator,
sources_paths.files,
union_membership,
# Note: Avro files cannot import from other Avro files, so do not add dependencies.
add_dependencies_on_all_siblings=False,
Comment on lines -125 to -126
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bummer we lose this comment. I think it's good to force plugin authors to reason about this, even though it's more boilerplate. What about requiring that you implement the Settings? Getting this option wrong is a big deal, it makes file-level targets irrelevant. And we messed it up in the past with files and resources.

(Fine if followup).

Copy link
Member Author

@stuhood stuhood Feb 10, 2022

Choose a reason for hiding this comment

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

I think that "dependencies on all siblings" should be rare to set, and unlike the inverse it's challenging to detect that you've set it accidentally (because dependencies are automatically more coarse-grained than they should be, and so things will succeed). The inverse (not setting it when you wanted to) is much, much easier to detect.

For example: scala has accidentally been setting this via add_dependencies_on_all_siblings, without us realizing (or remembering, at least): see #14382. In the interests of making dependency inference a stronger default, and removing the boilerplate required to do the right thing, I don't think that we should require it.

overrides=all_overrides,
generated_target_cls = AvroSourceTarget
copied_fields = (
*COMMON_TARGET_FIELDS,
AvroDependenciesField,
)
moved_fields = ()
help = "Generate a `avro_source` target for each file in the `sources` field."


def rules():
return (
*collect_rules(),
UnionRule(GenerateTargetsRequest, GenerateTargetsFromAvroSources),
)
return collect_rules()
30 changes: 12 additions & 18 deletions src/python/pants/backend/codegen/avro/target_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,18 @@
from textwrap import dedent

from pants.backend.codegen.avro import target_types
from pants.backend.codegen.avro.target_types import (
AvroSourcesGeneratorTarget,
AvroSourceTarget,
GenerateTargetsFromAvroSources,
)
from pants.backend.codegen.avro.target_types import AvroSourcesGeneratorTarget, AvroSourceTarget
from pants.engine.addresses import Address
from pants.engine.target import GeneratedTargets, SingleSourceField, Tags
from pants.engine.internals.graph import _TargetParametrizations
from pants.engine.target import SingleSourceField, Tags
from pants.testutil.rule_runner import QueryRule, RuleRunner


def test_generate_source_targets() -> None:
rule_runner = RuleRunner(
rules=[
*target_types.rules(),
QueryRule(GeneratedTargets, [GenerateTargetsFromAvroSources]),
QueryRule(_TargetParametrizations, [Address]),
],
target_types=[AvroSourcesGeneratorTarget],
)
Expand All @@ -42,21 +39,18 @@ def test_generate_source_targets() -> None:
}
)

generator = rule_runner.get_target(Address("src/avro", target_name="lib"))

def gen_tgt(rel_fp: str, tags: list[str] | None = None) -> AvroSourceTarget:
return AvroSourceTarget(
{SingleSourceField.alias: rel_fp, Tags.alias: tags},
Address("src/avro", target_name="lib", relative_file_path=rel_fp),
residence_dir=os.path.dirname(os.path.join("src/avro", rel_fp)),
)

generated = rule_runner.request(GeneratedTargets, [GenerateTargetsFromAvroSources(generator)])
assert generated == GeneratedTargets(
generator,
{
gen_tgt("f1.avsc", tags=["overridden"]),
gen_tgt("f2.avpr"),
gen_tgt("subdir/f.avsc"),
},
)
generated = rule_runner.request(
_TargetParametrizations, [Address("src/avro", target_name="lib")]
).parametrizations
assert set(generated.values()) == {
gen_tgt("f1.avsc", tags=["overridden"]),
gen_tgt("f2.avpr"),
gen_tgt("subdir/f.avsc"),
}
80 changes: 30 additions & 50 deletions src/python/pants/backend/codegen/protobuf/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,23 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.backend.codegen.protobuf.protoc import Protoc
from pants.engine.fs import PathGlobs, Paths
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.rules import collect_rules, rule
from pants.engine.target import (
COMMON_TARGET_FIELDS,
AllTargets,
BoolField,
Dependencies,
GeneratedTargets,
GenerateTargetsRequest,
MultipleSourcesField,
OverridesField,
SingleSourceField,
SourcesPaths,
SourcesPathsRequest,
Target,
TargetFilesGenerator,
TargetFilesGeneratorSettings,
TargetFilesGeneratorSettingsRequest,
Targets,
generate_file_based_overrides_field_help_message,
generate_file_level_targets,
)
from pants.engine.unions import UnionMembership, UnionRule
from pants.option.global_options import FilesNotFoundBehavior
from pants.engine.unions import UnionRule
from pants.util.docutil import doc_url
from pants.util.logging import LogLevel

Expand Down Expand Up @@ -76,6 +72,20 @@ class ProtobufSourceTarget(Target):
# -----------------------------------------------------------------------------------------------


class GeneratorSettingsRequest(TargetFilesGeneratorSettingsRequest):
pass


@rule
def generator_settings(
_: GeneratorSettingsRequest,
protoc: Protoc,
) -> TargetFilesGeneratorSettings:
return TargetFilesGeneratorSettings(
add_dependencies_on_all_siblings=not protoc.dependency_inference
)


class ProtobufSourcesGeneratingSourcesField(MultipleSourcesField):
default = ("*.proto",)
expected_file_extensions = (".proto",)
Expand All @@ -94,56 +104,26 @@ class ProtobufSourcesOverridesField(OverridesField):
)


class ProtobufSourcesGeneratorTarget(Target):
class ProtobufSourcesGeneratorTarget(TargetFilesGenerator):
alias = "protobuf_sources"
core_fields = (
*COMMON_TARGET_FIELDS,
ProtobufDependenciesField,
ProtobufSourcesGeneratingSourcesField,
ProtobufGrpcToggleField,
ProtobufSourcesOverridesField,
)
help = "Generate a `protobuf_source` target for each file in the `sources` field."


class GenerateTargetsFromProtobufSources(GenerateTargetsRequest):
generate_from = ProtobufSourcesGeneratorTarget


@rule
async def generate_targets_from_protobuf_sources(
request: GenerateTargetsFromProtobufSources,
files_not_found_behavior: FilesNotFoundBehavior,
protoc: Protoc,
union_membership: UnionMembership,
) -> GeneratedTargets:
sources_paths = await Get(
SourcesPaths, SourcesPathsRequest(request.generator[ProtobufSourcesGeneratingSourcesField])
)

all_overrides = {}
overrides_field = request.generator[OverridesField]
if overrides_field.value:
_all_override_paths = await MultiGet(
Get(Paths, PathGlobs, path_globs)
for path_globs in overrides_field.to_path_globs(files_not_found_behavior)
)
all_overrides = overrides_field.flatten_paths(
dict(zip(_all_override_paths, overrides_field.value.values()))
)

return generate_file_level_targets(
ProtobufSourceTarget,
request.generator,
sources_paths.files,
union_membership,
add_dependencies_on_all_siblings=not protoc.dependency_inference,
overrides=all_overrides,
generated_target_cls = ProtobufSourceTarget
copied_fields = (
*COMMON_TARGET_FIELDS,
ProtobufDependenciesField,
)
moved_fields = (ProtobufGrpcToggleField,)
settings_request_cls = GeneratorSettingsRequest
help = "Generate a `protobuf_source` target for each file in the `sources` field."


def rules():
return (
return [
*collect_rules(),
UnionRule(GenerateTargetsRequest, GenerateTargetsFromProtobufSources),
)
UnionRule(TargetFilesGeneratorSettingsRequest, GeneratorSettingsRequest),
]
25 changes: 10 additions & 15 deletions src/python/pants/backend/codegen/protobuf/target_types_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@

from pants.backend.codegen.protobuf import target_types
from pants.backend.codegen.protobuf.target_types import (
GenerateTargetsFromProtobufSources,
ProtobufSourcesGeneratorTarget,
ProtobufSourceTarget,
)
from pants.engine.addresses import Address
from pants.engine.target import GeneratedTargets, SingleSourceField, Tags
from pants.engine.internals.graph import _TargetParametrizations
from pants.engine.target import SingleSourceField, Tags
from pants.testutil.rule_runner import QueryRule, RuleRunner


def test_generate_source_targets() -> None:
rule_runner = RuleRunner(
rules=[
*target_types.rules(),
QueryRule(GeneratedTargets, [GenerateTargetsFromProtobufSources]),
QueryRule(_TargetParametrizations, [Address]),
],
target_types=[ProtobufSourcesGeneratorTarget],
)
Expand All @@ -42,8 +42,6 @@ def test_generate_source_targets() -> None:
}
)

generator = rule_runner.get_target(Address("src/proto", target_name="lib"))

def gen_tgt(rel_fp: str, tags: list[str] | None = None) -> ProtobufSourceTarget:
return ProtobufSourceTarget(
{SingleSourceField.alias: rel_fp, Tags.alias: tags},
Expand All @@ -52,13 +50,10 @@ def gen_tgt(rel_fp: str, tags: list[str] | None = None) -> ProtobufSourceTarget:
)

generated = rule_runner.request(
GeneratedTargets, [GenerateTargetsFromProtobufSources(generator)]
)
assert generated == GeneratedTargets(
generator,
{
gen_tgt("f1.proto", tags=["overridden"]),
gen_tgt("f2.proto"),
gen_tgt("subdir/f.proto"),
},
)
_TargetParametrizations, [Address("src/proto", target_name="lib")]
).parametrizations
assert set(generated.values()) == {
gen_tgt("f1.proto", tags=["overridden"]),
gen_tgt("f2.proto"),
gen_tgt("subdir/f.proto"),
}
Loading