Add a global --tag option to filter targets based on their tags.

Review Request #2362 — Created June 13, 2015 and submitted

--tag=foo,bar includes only targets that have at least one of those tags.
--tag=-foo,bar includes only targets that have none of those tags.
--tag=foo,bar --tag=-baz includes only targets that have at least one
  of the tags foo or bar, and do not have the tag baz.

The filtering is on target roots, of course, similar to spec_excludes and
exclude_target_regexp. Dependencies are included in the targets passed to
Task.execute() regardless of their tags.

We already had similar filtering functionality in the Filter task. So I
refactored some of that into a new file under util.

While doing so, I noticed that the help text on Filter's options was wrong:
In --tag=-foo,bar the '-' prefix applies to both foo and bar, and
--tag=-foo,+bar is not sensible. While fixing up the help strings I realized
that it would be more succinct to move the common help text (the part
explaining about the prefix and how multiple filters are specified) into the
goal description.  Then I realized that it would be pretty verbose to put that
in the with_description() text in, so I added a little thing that
uses the task's docstring as the description, if one isn't provided explicitly.

Note that Filter still has its own --tag option, which we can think
about deprecating in a future change.

IMPORTANT: While testing, I noticed that we had a longstanding bug, where
multiple filters of the same type wouldn't work, because in the loop the
filter() function was capturing the same references in its closure on each
iteration. See the test I added in - that test fails with no
other changes at HEAD.

Also note that the test marked as xfail is behaving as intended: according to
the implementation, separate specifications of the same option are ANDed
(while comma-separated values in a single specification are ORed). If we want
the behavior to be different then we can discuss, but for now at least that xfail
was inappropriate and misleading.

CI passes:

  2. src/python/pants/goal/ (Diff revision 1)

    Agreed, but goals needs some work to accomodate this better going forward: goals
                   filemap: Outputs a mapping from source file to owning target.
                    filter: Filter the input targets based on various criteria.
      Each of the filtering options below is a comma-separated list of filtering criteria, with an
      implied logical OR between them, so that a target passes the filter if it matches any of the
      criteria in the list.  A '-' prefix inverts the sense of the entire comma-separated list, so that
      a target passes the filter only if it matches none of the criteria in the list.
      Each of the filtering options may be specified multiple times, with an implied logical AND
      between them.
                       gen: Generate code.
                     goals: List all documented goals.

    Perhaps the 1st sentence for goals output and the full doc for --help.

    I'd prefer an issue or TODO over action in this RB.

    1. Bah, forgot about goals output. Will file an issue and follow up on it immediately.

Review request changed

Status: Closed (submitted)

Change Summary:

Submitted as 6c1ac098535f453af47656cf132a047724c2de5c.

  1. Thanks John! Submitted as 6c1ac098535f453af47656cf132a047724c2de5c.