environment.py: drop early exit in install (#42145)

`spack install` early exit behavior was sometimes convenient, except
that it had and has bugs:

1. prior bug: we didn't mark env roots of already installed specs as
   explicit in the db
2. current bug: `spack install --overwrite` is ignored

So this PR simplifies by letting the installer do its thing even if
everything is supposedly installed.
This commit is contained in:
Harmen Stoppels 2024-01-22 20:39:12 +01:00 committed by GitHub
parent 7580ba4861
commit ed9d495915
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 12 additions and 57 deletions

View file

@ -1803,8 +1803,8 @@ def _add_concrete_spec(self, spec, concrete, new=True):
self.concretized_order.append(h)
self.specs_by_hash[h] = concrete
def _get_overwrite_specs(self):
# Find all dev specs that were modified.
def _dev_specs_that_need_overwrite(self):
"""Return the hashes of all specs that need to be reinstalled due to source code change."""
changed_dev_specs = [
s
for s in traverse.traverse_nodes(
@ -1862,52 +1862,21 @@ def install_all(self, **install_args):
"""
self.install_specs(None, **install_args)
def install_specs(self, specs=None, **install_args):
tty.debug("Assessing installation status of environment packages")
# If "spack install" is invoked repeatedly for a large environment
# where all specs are already installed, the operation can take
# a large amount of time due to repeatedly acquiring and releasing
# locks. As a small optimization, drop already installed root specs.
installed_roots, uninstalled_roots = self._partition_roots_by_install_status()
if specs:
specs_to_install = [s for s in specs if s not in installed_roots]
specs_dropped = [s for s in specs if s in installed_roots]
else:
specs_to_install = uninstalled_roots
specs_dropped = installed_roots
def install_specs(self, specs: Optional[List[Spec]] = None, **install_args):
roots = self.concrete_roots()
specs = specs if specs is not None else roots
# We need to repeat the work of the installer thanks to the above optimization:
# Already installed root specs should be marked explicitly installed in the
# database.
if specs_dropped:
with spack.store.STORE.db.write_transaction(): # do all in one transaction
for spec in specs_dropped:
spack.store.STORE.db.update_explicit(spec, True)
# Extend the set of specs to overwrite with modified dev specs and their parents
install_args["overwrite"] = (
install_args.get("overwrite", []) + self._dev_specs_that_need_overwrite()
)
if not specs_to_install:
tty.msg("All of the packages are already installed")
else:
tty.debug("Processing {0} uninstalled specs".format(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("overwrite", []) + specs_to_overwrite
installs = []
for spec in specs_to_install:
pkg_install_args = install_args.copy()
pkg_install_args["explicit"] = spec in self.roots()
installs.append((spec.package, pkg_install_args))
installs = [(spec.package, {**install_args, "explicit": spec in roots}) for spec in specs]
try:
builder = PackageInstaller(installs)
builder.install()
PackageInstaller(installs).install()
finally:
# Ensure links are set appropriately
for spec in specs_to_install:
if spec.installed:
self.new_installs.append(spec)
self.new_installs.extend(s for s in specs if s.installed)
def all_specs_generator(self) -> Iterable[Spec]:
"""Returns a generator for all concrete specs"""

View file

@ -303,20 +303,6 @@ def test_activate_adds_transitive_run_deps_to_path(install_mockery, mock_fetch,
assert env_variables["DEPENDENCY_ENV_VAR"] == "1"
def test_env_install_same_spec_twice(install_mockery, mock_fetch):
env("create", "test")
e = ev.read("test")
with e:
# The first installation outputs the package prefix, updates the view
out = install("--add", "cmake-client")
assert "Updating view at" in out
# The second installation reports all packages already installed
out = install("cmake-client")
assert "already installed" in out
def test_env_definition_symlink(install_mockery, mock_fetch, tmpdir):
filepath = str(tmpdir.join("spack.yaml"))
filepath_mid = str(tmpdir.join("spack_mid.yaml"))