-
-
Notifications
You must be signed in to change notification settings - Fork 641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make "changed" tasks work with deleted files #4546
Changes from 6 commits
0d3eb35
536be29
90304dc
b1a3571
1cb7047
cec832c
4e1f7ee
9d9581a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
from pants.build_graph.source_mapper import SourceMapper | ||
from pants.engine.legacy.address_mapper import LegacyAddressMapper | ||
from pants.engine.legacy.graph import HydratedTargets | ||
from pants.source.filespec import matches_filespec | ||
from pants.source.wrapped_globs import EagerFilesetWithSpec | ||
|
||
|
||
|
@@ -48,20 +49,27 @@ def _unique_dirs_for_sources(self, sources): | |
def target_addresses_for_source(self, source): | ||
return list(self.iter_target_addresses_for_sources([source])) | ||
|
||
def _iter_owned_files_from_hydrated_target(self, legacy_target): | ||
def _match_source(self, source, fileset): | ||
if os.path.exists(source): | ||
return fileset.matches(source) | ||
else: | ||
return matches_filespec(source, fileset.filespec) | ||
|
||
def _own_source(self, source, legacy_target): | ||
"""Given a `HydratedTarget` instance, yield all files owned by the target.""" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment out of date. I think this method is probably There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
target_kwargs = legacy_target.adaptor.kwargs() | ||
|
||
# Handle targets like `python_binary` which have a singular `source='main.py'` declaration. | ||
target_source = target_kwargs.get('source') | ||
if target_source: | ||
yield os.path.join(legacy_target.adaptor.address.spec_path, target_source) | ||
path_from_build_root = os.path.join(legacy_target.adaptor.address.spec_path, target_source) | ||
if path_from_build_root == source: | ||
return True | ||
|
||
# Handle `sources`-declaring targets. | ||
target_sources = target_kwargs.get('sources', []) | ||
if target_sources: | ||
for f in target_sources.paths_from_buildroot_iter(): | ||
yield f | ||
if target_sources and self._match_source(source, target_sources): | ||
return True | ||
|
||
# Handle `resources`-declaring targets. | ||
# TODO: Remember to get rid of this in 1.5.0.dev0, when the deprecation of `resources` is complete. | ||
|
@@ -74,8 +82,8 @@ def _iter_owned_files_from_hydrated_target(self, legacy_target): | |
# python_library(..., resources=['file.txt', 'file2.txt']) | ||
# | ||
if isinstance(target_resources, EagerFilesetWithSpec): | ||
for f in target_resources.paths_from_buildroot_iter(): | ||
yield f | ||
if self._match_source(source, target_resources): | ||
return True | ||
# 2) Strings of addresses, which are represented in kwargs by a list of strings: | ||
# | ||
# java_library(..., resources=['testprojects/src/resources/...:resource']) | ||
|
@@ -87,12 +95,13 @@ def _iter_owned_files_from_hydrated_target(self, legacy_target): | |
for hydrated_targets in self._engine.product_request(HydratedTargets, resource_dep_subjects): | ||
for hydrated_target in hydrated_targets.dependencies: | ||
resource_sources = hydrated_target.adaptor.kwargs().get('sources') | ||
if resource_sources: | ||
for f in resource_sources.paths_from_buildroot_iter(): | ||
yield f | ||
if resource_sources and self._match_source(source, resource_sources): | ||
return True | ||
else: | ||
raise AssertionError('Could not process target_resources with type {}'.format(type(target_resources))) | ||
|
||
return False | ||
|
||
def iter_target_addresses_for_sources(self, sources): | ||
"""Bulk, iterable form of `target_addresses_for_source`.""" | ||
# Walk up the buildroot looking for targets that would conceivably claim changed sources. | ||
|
@@ -107,8 +116,5 @@ def iter_target_addresses_for_sources(self, sources): | |
if any(LegacyAddressMapper.is_declaring_file(legacy_address, f) for f in sources_set): | ||
yield legacy_address | ||
else: | ||
# Handle claimed files. | ||
target_files_iter = self._iter_owned_files_from_hydrated_target(hydrated_target) | ||
if any(source_file in sources_set for source_file in target_files_iter): | ||
# At least one file in this targets sources match our changed sources - emit its address. | ||
if any(self._own_source(source, hydrated_target) for source in sources_set): | ||
yield legacy_address |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# coding=utf-8 | ||
# Copyright 2017 Pants project contributors (see CONTRIBUTORS.md). | ||
# Licensed under the Apache License, Version 2.0 (see LICENSE). | ||
|
||
from __future__ import (absolute_import, division, generators, nested_scopes, print_function, | ||
unicode_literals, with_statement) | ||
|
||
import re | ||
|
||
|
||
def glob_to_regex(pattern): | ||
"""Given a glob pattern, return an equivalent regex expression. | ||
:param string glob: The glob pattern. "**" matches 0 or more dirs recursively. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should already be provided by the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. glob syntax used in file matching is not equivalent to gitignore syntax. For example, "src" in gitignore will match everything inside "src", but in glob syntax, it should only match "src". |
||
"*" only matches patterns in a single dir. | ||
:returns: A regex string that matches same paths as the input glob does. | ||
""" | ||
out = ['^'] | ||
components = pattern.strip('/').replace('.', '[.]').replace('$','[$]').split('/') | ||
doublestar = False | ||
for component in components: | ||
if len(out) == 1: | ||
if pattern.startswith('/'): | ||
out.append('/') | ||
else: | ||
if not doublestar: | ||
out.append('/') | ||
|
||
if '**' in component: | ||
if component != '**': | ||
raise ValueError('Invalid usage of "**", use "*" instead.') | ||
|
||
if not doublestar: | ||
out.append('(([^/]+/)*)') | ||
doublestar = True | ||
else: | ||
out.append(component.replace('*', '[^/]*')) | ||
doublestar = False | ||
|
||
if doublestar: | ||
out.append('[^/]*') | ||
|
||
out.append('$') | ||
|
||
return ''.join(out) | ||
|
||
|
||
def globs_matches(path, patterns): | ||
return any(re.match(glob_to_regex(pattern), path) for pattern in patterns) | ||
|
||
|
||
def matches_filespec(path, spec): | ||
if spec is None: | ||
return False | ||
if not globs_matches(path, spec.get('globs', [])): | ||
return False | ||
for spec in spec.get('exclude', []): | ||
if matches_filespec(path, spec): | ||
return False | ||
return True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than always checking whether it exists, maybe first check whether it is in the fileset... if it is, then it's definitely owned, and that's cheaper than a syscall. If it's not in the fileset, can use
matches_filespec
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.