Skip to content

Commit

Permalink
Refactor SpecsFilter to use TargetFilter abstraction (#15597)
Browse files Browse the repository at this point in the history
We were re-inventing the wheel. We already have lots of modeling from `filter_targets.py` for how to apply filters to a target.

Prework for applying `--filter` options from anywhere.

[ci skip-rust]
[ci skip-build-wheels]
  • Loading branch information
Eric-Arellano authored May 23, 2022
1 parent f681d4d commit 664ae63
Show file tree
Hide file tree
Showing 6 changed files with 35 additions and 41 deletions.
7 changes: 2 additions & 5 deletions src/python/pants/backend/project_info/filter_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import re
from enum import Enum
from typing import Callable, Pattern
from typing import Pattern

from pants.base.deprecated import warn_or_error
from pants.engine.console import Console
Expand All @@ -20,7 +20,7 @@
)
from pants.option.option_types import EnumOption, StrListOption
from pants.util.enums import match
from pants.util.filtering import and_filters, create_filters
from pants.util.filtering import TargetFilter, and_filters, create_filters
from pants.util.memo import memoized
from pants.util.strutil import softwrap

Expand Down Expand Up @@ -85,9 +85,6 @@ def compile_regex(regex: str) -> Pattern:
raise re.error(f"Invalid regular expression {repr(regex)}: {e}")


TargetFilter = Callable[[Target], bool]


# Memoized so the deprecation doesn't happen repeatedly.
@memoized
def warn_deprecated_target_type(tgt_type: type[Target]) -> None:
Expand Down
53 changes: 22 additions & 31 deletions src/python/pants/engine/internals/mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@

import re
from dataclasses import dataclass
from typing import Callable, Iterable, Mapping, Pattern
from typing import Iterable, Mapping

from pants.base.exceptions import MappingError
from pants.build_graph.address import Address, BuildFileAddress
from pants.engine.internals.parser import BuildFilePreludeSymbols, Parser
from pants.engine.internals.target_adaptor import TargetAdaptor
from pants.engine.target import Tags, Target
from pants.util.filtering import and_filters, create_filters
from pants.util.filtering import TargetFilter, and_filters, create_filters
from pants.util.memo import memoized_property
from pants.util.meta import frozen_after_init


class DuplicateNameError(MappingError):
Expand Down Expand Up @@ -152,44 +151,36 @@ def __repr__(self) -> str:
)


@frozen_after_init
@dataclass(unsafe_hash=True)
@dataclass(frozen=True)
class SpecsFilter:
"""Filters addresses with the `--tags` and `--exclude-target-regexp` options."""

tags: tuple[str, ...]
exclude_target_regexps: tuple[str, ...]

def __init__(
self,
*,
tags: Iterable[str] | None = None,
exclude_target_regexps: Iterable[str] | None = None,
) -> None:
self.tags = tuple(tags or [])
self.exclude_target_regexps = tuple(exclude_target_regexps or [])
is_specified: bool
tags_filter: TargetFilter
exclude_target_regexps_filter: TargetFilter

@memoized_property
def _exclude_regexps(self) -> tuple[Pattern, ...]:
return tuple(re.compile(pattern) for pattern in self.exclude_target_regexps)
@classmethod
def create(cls, *, tags: Iterable[str], exclude_target_regexps: Iterable[str]) -> SpecsFilter:
exclude_patterns = tuple(re.compile(pattern) for pattern in exclude_target_regexps)

def _is_excluded_by_pattern(self, address: Address) -> bool:
return any(p.search(address.spec) is not None for p in self._exclude_regexps)
def exclude_target_regexps_filter(tgt: Target) -> bool:
return all(p.search(tgt.address.spec) is None for p in exclude_patterns)

@memoized_property
def _tag_filter(self):
def filter_for_tag(tag: str) -> Callable[[Target], bool]:
def filter_target(tgt: Target) -> bool:
def tags_outer_filter(tag: str) -> TargetFilter:
def tags_inner_filter(tgt: Target) -> bool:
return tag in (tgt.get(Tags).value or [])

return filter_target
return tags_inner_filter

tags_filter = and_filters(create_filters(tags, tags_outer_filter))

return and_filters(create_filters(self.tags, filter_for_tag))
return SpecsFilter(
is_specified=bool(tags or exclude_target_regexps),
tags_filter=tags_filter,
exclude_target_regexps_filter=exclude_target_regexps_filter,
)

def matches(self, target: Target) -> bool:
"""Check that the target matches the provided `--tag` and `--exclude-target-regexp`
options."""
return self._tag_filter(target) and not self._is_excluded_by_pattern(target.address)

def __bool__(self) -> bool:
return bool(self.tags or self.exclude_target_regexps)
return self.tags_filter(target) and self.exclude_target_regexps_filter(target)
2 changes: 1 addition & 1 deletion src/python/pants/engine/internals/mapper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def test_address_family_duplicate_names() -> None:


def test_specs_filter() -> None:
specs_filter = SpecsFilter(tags=["-a", "+b"], exclude_target_regexps=["skip-me"])
specs_filter = SpecsFilter.create(tags=["-a", "+b"], exclude_target_regexps=["skip-me"])

class MockTgt(Target):
alias = "tgt"
Expand Down
2 changes: 1 addition & 1 deletion src/python/pants/engine/internals/specs_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def filter_targets(targets: Targets, specs_filter: SpecsFilter) -> FilteredTarge

@rule
def setup_specs_filter(global_options: GlobalOptions) -> SpecsFilter:
return SpecsFilter(
return SpecsFilter.create(
tags=global_options.tag, exclude_target_regexps=global_options.exclude_target_regexp
)

Expand Down
10 changes: 8 additions & 2 deletions src/python/pants/util/filtering.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

import operator
from typing import Callable, Iterable, Sequence, Tuple, TypeVar
from typing import TYPE_CHECKING, Callable, Iterable, Tuple, TypeVar

if TYPE_CHECKING:
from pants.engine.target import Target

_T = TypeVar("_T")
Filter = Callable[[_T], bool]
TargetFilter = Callable[["Target"], bool]


def _extract_modifier(modified_param: str) -> Tuple[Callable[[bool], bool], str]:
Expand Down Expand Up @@ -42,7 +48,7 @@ def filt(x: _T) -> bool:

def create_filters(
predicate_params: Iterable[str], predicate_factory: Callable[[str], Filter]
) -> Sequence[Filter]:
) -> list[Filter]:
"""Create filter functions from a list of string parameters.
:param predicate_params: A list of predicate_param arguments as in `create_filter`.
Expand Down
2 changes: 1 addition & 1 deletion src/python/pants/vcs/changed.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def find_changed_owners(
),
)
result = FrozenOrderedSet(owners) | (dependees - owner_target_generators)
if specs_filter:
if specs_filter.is_specified:
# Finally, we must now filter out the result to only include what matches our tags, as the
# last step of https://github.com/pantsbuild/pants/issues/15544.
#
Expand Down

0 comments on commit 664ae63

Please sign in to comment.