Speed up install of environments with dev packages (#27167)

* only check file modification times once per dev package
This commit is contained in:
Thomas Madlener 2021-11-29 18:34:23 +01:00 committed by GitHub
parent 4858a26400
commit ecb588740a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -286,6 +286,51 @@ def _eval_conditional(string):
return eval(string, valid_variables) return eval(string, valid_variables)
def _is_dev_spec_and_has_changed(spec):
"""Check if the passed spec is a dev build and whether it has changed since the
last installation"""
# First check if this is a dev build and in the process already try to get
# the dev_path
dev_path_var = spec.variants.get('dev_path', None)
if not dev_path_var:
return False
# Now we can check whether the code changed since the last installation
if not spec.package.installed:
# Not installed -> nothing to compare against
return False
_, record = spack.store.db.query_by_spec_hash(spec.dag_hash())
mtime = fs.last_modification_time_recursive(dev_path_var.value)
return mtime > record.installation_time
def _spec_needs_overwrite(spec, changed_dev_specs):
"""Check whether the current spec needs to be overwritten because either it has
changed itself or one of its dependencies have changed"""
# if it's not installed, we don't need to overwrite it
if not spec.package.installed:
return False
# If the spec itself has changed this is a trivial decision
if spec in changed_dev_specs:
return True
# if spec and all deps aren't dev builds, we don't need to overwrite it
if not any(spec.satisfies(c)
for c in ('dev_path=*', '^dev_path=*')):
return False
# If any dep needs overwrite, or any dep is missing and is a dev build then
# overwrite this package
if any(
((not dep.package.installed) and dep.satisfies('dev_path=*')) or
_spec_needs_overwrite(dep, changed_dev_specs)
for dep in spec.traverse(root=False)
):
return True
class ViewDescriptor(object): class ViewDescriptor(object):
def __init__(self, base_path, root, projections={}, select=[], exclude=[], def __init__(self, base_path, root, projections={}, select=[], exclude=[],
link=default_view_link, link_type='symlink'): link=default_view_link, link_type='symlink'):
@ -1392,52 +1437,19 @@ def _add_concrete_spec(self, spec, concrete, new=True):
self.concretized_order.append(h) self.concretized_order.append(h)
self.specs_by_hash[h] = concrete self.specs_by_hash[h] = concrete
def _spec_needs_overwrite(self, spec):
# Overwrite the install if it's a dev build (non-transitive)
# and the code has been changed since the last install
# or one of the dependencies has been reinstalled since
# the last install
# if it's not installed, we don't need to overwrite it
if not spec.package.installed:
return False
# if spec and all deps aren't dev builds, we don't need to overwrite it
if not any(spec.satisfies(c)
for c in ('dev_path=*', '^dev_path=*')):
return False
# if any dep needs overwrite, or any dep is missing and is a dev build
# then overwrite this package
if any(
self._spec_needs_overwrite(dep) or
((not dep.package.installed) and dep.satisfies('dev_path=*'))
for dep in spec.traverse(root=False)
):
return True
# if it's not a direct dev build and its dependencies haven't
# changed, it hasn't changed.
# We don't merely check satisfaction (spec.satisfies('dev_path=*')
# because we need the value of the variant in the next block of code
dev_path_var = spec.variants.get('dev_path', None)
if not dev_path_var:
return False
# if it is a direct dev build, check whether the code changed
# we already know it is installed
_, record = spack.store.db.query_by_spec_hash(spec.dag_hash())
mtime = fs.last_modification_time_recursive(dev_path_var.value)
return mtime > record.installation_time
def _get_overwrite_specs(self): def _get_overwrite_specs(self):
ret = [] # Collect all specs in the environment first before checking which ones
# to rebuild to avoid checking the same specs multiple times
specs_to_check = set()
for dag_hash in self.concretized_order: for dag_hash in self.concretized_order:
spec = self.specs_by_hash[dag_hash] root_spec = self.specs_by_hash[dag_hash]
ret.extend([d.dag_hash() for d in spec.traverse(root=True) specs_to_check.update(root_spec.traverse(root=True))
if self._spec_needs_overwrite(d)])
return ret changed_dev_specs = set(s for s in specs_to_check if
_is_dev_spec_and_has_changed(s))
return [s.dag_hash() for s in specs_to_check if
_spec_needs_overwrite(s, changed_dev_specs)]
def _install_log_links(self, spec): def _install_log_links(self, spec):
if not spec.external: if not spec.external:
@ -1508,8 +1520,12 @@ def install_specs(self, specs=None, **install_args):
tty.debug('Processing {0} uninstalled specs'.format( tty.debug('Processing {0} uninstalled specs'.format(
len(specs_to_install))) len(specs_to_install)))
specs_to_overwrite = self._get_overwrite_specs()
tty.debug('{0} specs need to be overwritten'.format(
len(specs_to_overwrite)))
install_args['overwrite'] = install_args.get( install_args['overwrite'] = install_args.get(
'overwrite', []) + self._get_overwrite_specs() 'overwrite', []) + specs_to_overwrite
installs = [] installs = []
for spec in specs_to_install: for spec in specs_to_install: