Skip to content

Commit

Permalink
Add filtering subsystem to permit skipping targets by tags (pantsbuil…
Browse files Browse the repository at this point in the history
…d#7275)

This subsystem is responsible for handling options meant to exclude targets from specific tasks

The application of the logic itself is contained in the TargetFiltering class - which currently only handles excluding targets with provided tags and can be expanded upon for additional filtering options.
  • Loading branch information
codealchemy authored and jsirois committed Feb 23, 2019
1 parent b34d66f commit a86639e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 4 deletions.
45 changes: 45 additions & 0 deletions src/python/pants/build_graph/target_filter_subsystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# coding=utf-8
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

import logging
from builtins import object, set

from pants.subsystem.subsystem import Subsystem


logger = logging.getLogger(__name__)


class TargetFilter(Subsystem):
"""Filter targets matching configured criteria.
:API: public
"""

options_scope = 'target-filter'

@classmethod
def register_options(cls, register):
super(TargetFilter, cls).register_options(register)

register('--exclude-tags', type=list,
default=[], fingerprint=True,
help='Skip targets with given tag(s).')

def apply(self, targets):
exclude_tags = set(self.get_options().exclude_tags)
return TargetFiltering(targets, exclude_tags).apply_tag_blacklist()


class TargetFiltering(object):
"""Apply filtering logic against targets."""

def __init__(self, targets, exclude_tags):
self.targets = targets
self.exclude_tags = exclude_tags

def apply_tag_blacklist(self):
return [t for t in self.targets if not self.exclude_tags.intersection(t.tags)]
19 changes: 15 additions & 4 deletions src/python/pants/task/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import sys
from abc import abstractmethod
from builtins import filter, map, object, str, zip
from builtins import filter, map, object, set, str, zip
from contextlib import contextmanager
from hashlib import sha1
from itertools import repeat
Expand All @@ -16,6 +16,7 @@

from pants.base.exceptions import TaskError
from pants.base.worker_pool import Work
from pants.build_graph.target_filter_subsystem import TargetFilter
from pants.cache.artifact_cache import UnreadableArtifact, call_insert, call_use_cached_files
from pants.cache.cache_setup import CacheSetup
from pants.invalidation.build_invalidator import (BuildInvalidator, CacheKeyGenerator,
Expand Down Expand Up @@ -96,7 +97,7 @@ def _compute_stable_name(cls):
@classmethod
def subsystem_dependencies(cls):
return (super(TaskBase, cls).subsystem_dependencies() +
(CacheSetup.scoped(cls), BuildInvalidator.Factory, SourceRootConfig))
(CacheSetup.scoped(cls), TargetFilter.scoped(cls), BuildInvalidator.Factory, SourceRootConfig))

@classmethod
def product_types(cls):
Expand Down Expand Up @@ -237,8 +238,18 @@ def get_targets(self, predicate=None):
:API: public
"""
return (self.context.targets(predicate) if self.act_transitively
else list(filter(predicate, self.context.target_roots)))
initial_targets = (self.context.targets(predicate) if self.act_transitively
else list(filter(predicate, self.context.target_roots)))

included_targets = TargetFilter.scoped_instance(self).apply(initial_targets)
excluded_targets = set(initial_targets).difference(included_targets)

if excluded_targets:
self.context.log.info("{} target(s) excluded".format(len(excluded_targets)))
for target in excluded_targets:
self.context.log.debug("{} excluded".format(target.address.spec))

return included_targets

@memoized_property
def workdir(self):
Expand Down
11 changes: 11 additions & 0 deletions tests/python/pants_test/build_graph/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,14 @@ python_tests(
'tests/python/pants_test:test_base',
]
)

python_tests(
name = 'target_filter_subsystem',
sources = ['test_target_filter_subsystem.py'],
dependencies = [
'3rdparty/python:future',
'src/python/pants/build_graph',
'src/python/pants/task',
'tests/python/pants_test:task_test_base',
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# coding=utf-8
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import absolute_import, division, print_function, unicode_literals

from builtins import set

from pants.build_graph.target_filter_subsystem import TargetFilter, TargetFiltering
from pants.task.task import Task
from pants_test.task_test_base import TaskTestBase


class TestTargetFilter(TaskTestBase):

class DummyTask(Task):
options_scope = 'dummy'

def execute(self):
self.context.products.safe_create_data('task_targets', self.get_targets)

@classmethod
def task_type(cls):
return cls.DummyTask

def test_task_execution_with_filter(self):
a = self.make_target('a', tags=['skip-me'])
b = self.make_target('b', dependencies=[a], tags=[])

context = self.context(for_task_types=[self.DummyTask], for_subsystems=[TargetFilter], target_roots=[b], options={
TargetFilter.options_scope: {
'exclude_tags': ['skip-me']
}
})

self.create_task(context).execute()
self.assertEqual([b], context.products.get_data('task_targets'))

def test_filtering_single_tag(self):
a = self.make_target('a', tags=[])
b = self.make_target('b', tags=['skip-me'])
c = self.make_target('c', tags=['tag1', 'skip-me'])

filtered_targets = TargetFiltering([a, b, c], {'skip-me'}).apply_tag_blacklist()
self.assertEqual([a], filtered_targets)

def test_filtering_multiple_tags(self):
a = self.make_target('a', tags=['tag1', 'skip-me'])
b = self.make_target('b', tags=['tag1', 'tag2', 'skip-me'])
c = self.make_target('c', tags=['tag2'])

filtered_targets = TargetFiltering([a, b, c], {'skip-me', 'tag2'}).apply_tag_blacklist()
self.assertEqual([], filtered_targets)

def test_filtering_no_tags(self):
a = self.make_target('a', tags=['tag1'])
b = self.make_target('b', tags=['tag1', 'tag2'])
c = self.make_target('c', tags=['tag2'])

filtered_targets = TargetFiltering([a, b, c], set()).apply_tag_blacklist()
self.assertEqual([a, b, c], filtered_targets)

0 comments on commit a86639e

Please sign in to comment.