Skip to content

Commit

Permalink
Add subsystem for appending tags to targets from a single source (#7315)
Browse files Browse the repository at this point in the history
Problem:
It can be cumbersome to apply the same tags to many targets as it requires editing every associated BUILD file. Having a single source of tags would make it more straightforward to configure tags by which to filter targets (#6902), and can be applied to other use cases such as grouping targets with a particular suffix (ex. _integration in pants) under a single tag.

Solution:
Add a subsystem within Target responsible for fetching, parsing, and matching tags configured in a JSON file to targets. The subsystem matches against the target's address (though could be expanded to a regex to address additional use cases).

Closes #7302
  • Loading branch information
codealchemy authored and benjyw committed Mar 7, 2019
1 parent d7fd73a commit afa0e16
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 3 deletions.
29 changes: 26 additions & 3 deletions src/python/pants/build_graph/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from pants.source.payload_fields import SourcesField
from pants.source.wrapped_globs import EagerFilesetWithSpec, FilesetWithSpec
from pants.subsystem.subsystem import Subsystem
from pants.util.memo import memoized_property
from pants.util.memo import memoized_method, memoized_property


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -139,9 +139,32 @@ def check_unknown(self, target, kwargs, payload):
args=''.join('\n {} = {}'.format(key, value) for key, value in unknown_args.items())
))

class TagAssignments(Subsystem):
"""Tags to add to targets in addition to any defined in their BUILD files."""

options_scope = 'target-tag-assignments'

@classmethod
def register_options(cls, register):
register('--tag-targets-mappings', type=dict, default=None, fromfile=True, fingerprint=True,
help='Dict with tag assignments for targets. Ex: { "tag1": ["path/to/target:foo"] }')

@classmethod
def tags_for(cls, target_address):
return cls.global_instance()._invert_tag_targets_mappings().get(target_address, [])

@memoized_method
def _invert_tag_targets_mappings(self):
result = {}
for tag, targets in (self.get_options().tag_targets_mappings or {}).items():
for target in targets:
target_tags = result.setdefault(Address.parse(target).spec, [])
target_tags.append(tag)
return result

@classmethod
def subsystems(cls):
return super(Target, cls).subsystems() + (cls.Arguments,)
return super(Target, cls).subsystems() + (cls.Arguments, cls.TagAssignments)

@classmethod
def get_addressable_type(target_cls):
Expand Down Expand Up @@ -312,7 +335,7 @@ def __init__(self, name, address, build_graph, type_alias=None, payload=None, ta
self.address = address
self._build_graph = build_graph
self._type_alias = type_alias
self._tags = set(tags or [])
self._tags = set(tags or []).union(self.TagAssignments.tags_for(address.spec))
self.description = description

self._cached_fingerprint_map = {}
Expand Down
1 change: 1 addition & 0 deletions tests/python/pants_test/build_graph/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ python_tests(
sources = ['test_build_file_aliases.py'],
dependencies = [
'src/python/pants/build_graph',
'tests/python/pants_test/subsystem:subsystem_utils',
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pants.build_graph.build_file_aliases import BuildFileAliases, TargetMacro
from pants.build_graph.mutable_build_graph import MutableBuildGraph
from pants.build_graph.target import Target
from pants_test.subsystem.subsystem_util import init_subsystem


class BuildFileAliasesTest(unittest.TestCase):
Expand All @@ -19,6 +20,7 @@ class BlueTarget(Target):
pass

def setUp(self):
init_subsystem(Target.TagAssignments)
self.target_macro_factory = TargetMacro.Factory.wrap(
lambda ctx: ctx.create_object(self.BlueTarget,
type_alias='jill',
Expand Down
22 changes: 22 additions & 0 deletions tests/python/pants_test/build_graph/test_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,28 @@ def test_unknown_kwargs(self):
target = self.make_target('foo:bar', Target, foobar='barfoo')
self.assertFalse(hasattr(target, 'foobar'))

def test_tags_applied_from_configured_dict(self):
options = {Target.TagAssignments.options_scope: {
'tag_targets_mappings': {
'special_tag': ['foo:bar', 'path/to/target:foo', 'path/to/target'],
'special_tag2': ['path/to/target:target', '//base:foo'],
'nonextant_target_tag': ['i/dont/exist'],
}
}}

init_subsystem(Target.TagAssignments, options)
target1 = self.make_target('foo:bar', Target, tags=['tag1', 'tag2'])
target2 = self.make_target('path/to/target:foo', Target, tags=['tag1'])
target3 = self.make_target('path/to/target', Target, tags=['tag2'])
target4 = self.make_target('//base:foo', Target, tags=['tag3'])
target5 = self.make_target('baz:qux', Target, tags=['tag3'])

self.assertEqual({'tag1', 'tag2', 'special_tag'}, target1.tags)
self.assertEqual({'tag1', 'special_tag'}, target2.tags)
self.assertEqual({'tag2', 'special_tag', 'special_tag2'}, target3.tags)
self.assertEqual({'tag3', 'special_tag2'}, target4.tags)
self.assertEqual({'tag3'}, target5.tags)

def test_target_id_long(self):
long_path = 'dummy'
for i in range(1,30):
Expand Down
1 change: 1 addition & 0 deletions tests/python/pants_test/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ def setUp(self):

self._build_configuration = self.build_config()
self._inited_target = False
subsystem_util.init_subsystem(Target.TagAssignments)

def buildroot_files(self, relpath=None):
"""Returns the set of all files under the test build root.
Expand Down

0 comments on commit afa0e16

Please sign in to comment.