bugfix: concrete dependencies are now copied properly.

- Dependencies in concrete specs did not previously have their cache
  fields (_concrete, _normal, etc.) preserved.

- _dup and _dup_deps weren't passing each other enough information to
  preserve concreteness properly, so only the root was properly
  preserved.

- cached concreteness is now preserved properly for the entire DAG, not
  just the root.

- added method docs.
This commit is contained in:
Todd Gamblin 2017-09-11 11:41:21 -07:00
parent 14cd73ed3c
commit 8c42aed9d5

View file

@ -2542,7 +2542,7 @@ def virtual_dependencies(self):
"""Return list of any virtual deps in this spec.""" """Return list of any virtual deps in this spec."""
return [spec for spec in self.traverse() if spec.virtual] return [spec for spec in self.traverse() if spec.virtual]
def _dup(self, other, deps=True, cleardeps=True): def _dup(self, other, deps=True, cleardeps=True, caches=None):
"""Copy the spec other into self. This is an overwriting """Copy the spec other into self. This is an overwriting
copy. It does not copy any dependents (parents), but by default copy. It does not copy any dependents (parents), but by default
copies dependencies. copies dependencies.
@ -2557,6 +2557,10 @@ def _dup(self, other, deps=True, cleardeps=True):
cleardeps (bool): if True clears the dependencies of ``self``, cleardeps (bool): if True clears the dependencies of ``self``,
before possibly copying the dependencies of ``other`` onto before possibly copying the dependencies of ``other`` onto
``self`` ``self``
caches (bool or None): preserve cached fields such as
``_normal``, ``_concrete``, and ``_cmp_key_cache``. By
default this is ``False`` if DAG structure would be
changed by the copy, ``True`` if it's an exact copy.
Returns: Returns:
True if ``self`` changed because of the copy operation, True if ``self`` changed because of the copy operation,
@ -2594,21 +2598,20 @@ def _dup(self, other, deps=True, cleardeps=True):
self.external_module = other.external_module self.external_module = other.external_module
self.namespace = other.namespace self.namespace = other.namespace
# If we copy dependencies, preserve DAG structure in the new spec # Cached fields are results of expensive operations.
if deps:
deptypes = alldeps # by default copy all deptypes
# if caller restricted deptypes to be copied, adjust that here.
if isinstance(deps, (tuple, list)):
deptypes = deps
self._dup_deps(other, deptypes)
# These fields are all cached results of expensive operations.
# If we preserved the original structure, we can copy them # If we preserved the original structure, we can copy them
# safely. If not, they need to be recomputed. # safely. If not, they need to be recomputed.
# TODO: dependency hashes can be copied more aggressively. if caches is None:
if deps is True or deps == alldeps: caches = (deps is True or deps == alldeps)
# If we copy dependencies, preserve DAG structure in the new spec
if deps:
# If caller restricted deptypes to be copied, adjust that here.
# By default, just copy all deptypes
deptypes = deps if isinstance(deps, (tuple, list)) else alldeps
self._dup_deps(other, deptypes, caches)
if caches:
self._hash = other._hash self._hash = other._hash
self._cmp_key_cache = other._cmp_key_cache self._cmp_key_cache = other._cmp_key_cache
self._normal = other._normal self._normal = other._normal
@ -2621,7 +2624,7 @@ def _dup(self, other, deps=True, cleardeps=True):
return changed return changed
def _dup_deps(self, other, deptypes): def _dup_deps(self, other, deptypes, caches):
new_specs = {self.name: self} new_specs = {self.name: self}
for dspec in other.traverse_edges(cover='edges', root=False): for dspec in other.traverse_edges(cover='edges', root=False):
if (dspec.deptypes and if (dspec.deptypes and
@ -2629,29 +2632,45 @@ def _dup_deps(self, other, deptypes):
continue continue
if dspec.parent.name not in new_specs: if dspec.parent.name not in new_specs:
new_specs[dspec.parent.name] = dspec.parent.copy(deps=False) new_specs[dspec.parent.name] = dspec.parent.copy(
deps=False, caches=caches)
if dspec.spec.name not in new_specs: if dspec.spec.name not in new_specs:
new_specs[dspec.spec.name] = dspec.spec.copy(deps=False) new_specs[dspec.spec.name] = dspec.spec.copy(
deps=False, caches=caches)
new_specs[dspec.parent.name]._add_dependency( new_specs[dspec.parent.name]._add_dependency(
new_specs[dspec.spec.name], dspec.deptypes) new_specs[dspec.spec.name], dspec.deptypes)
def copy(self, deps=True): def copy(self, deps=True, **kwargs):
"""Return a copy of this spec. """Make a copy of this spec.
By default, returns a deep copy. To control how dependencies are Args:
copied, supply: deps (bool or tuple): Defaults to True. If boolean, controls
whether dependencies are copied (copied if True). If a
tuple is provided, *only* dependencies of types matching
those in the tuple are copied.
kwargs: additional arguments for internal use (passed to ``_dup``).
deps=True: deep copy Returns:
A copy of this spec.
deps=False: shallow copy (no dependencies) Examples:
Deep copy with dependnecies::
deps=('link', 'build'): spec.copy()
only build and link dependencies. Similar for other deptypes. spec.copy(deps=True)
Shallow copy (no dependencies)::
spec.copy(deps=False)
Only build and run dependencies::
deps=('build', 'run'):
""" """
clone = Spec.__new__(Spec) clone = Spec.__new__(Spec)
clone._dup(self, deps=deps) clone._dup(self, deps=deps, **kwargs)
return clone return clone
@property @property