Skip to content
Permalink
Browse files

Add filtering subsystem to permit skipping targets by tags (pantsbuil…

…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 a86639e1cb9c9da5bf92810a71232ae0b0ac5845
@@ -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)]
@@ -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
@@ -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,
@@ -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):
@@ -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):
@@ -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',
]
)
@@ -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.
You can’t perform that action at this time.