From 9d6630e24530ce981378378f9a138e3dc52ad96f Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Wed, 22 Feb 2023 10:35:44 +0100 Subject: spack build-env: error when deps are not installed (#35533) Currently we attempt to setup the build environment even when dependencies are not installed, which typically results in error while searching for libraries or executables in a dependency's prefix. With this change, we get a more user friendly error: ``` $ spack build-env perl ==> Error: Not all dependencies of perl are installed, cannot setup build environment: - qpj6dw5 perl@5.36.0%apple-clang@14.0.0+cpanm+open+shared+threads build_system=generic arch=darwin-ventura-m1 - jq2plbe ^berkeley-db@18.1.40%apple-clang@14.0.0+cxx~docs+stl build_system=autotools patches=26090f4,b231fcc arch=darwin-ventura-m1 ... $ echo $? 1 ``` --- lib/spack/spack/cmd/build_env.py | 2 +- lib/spack/spack/cmd/common/env_utility.py | 60 +++++++++++++++++++++++++++++++ lib/spack/spack/cmd/test_env.py | 2 +- lib/spack/spack/test/cmd/build_env.py | 8 +++++ 4 files changed, 70 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/cmd/build_env.py b/lib/spack/spack/cmd/build_env.py index cd19e5aa10..7da9213c5b 100644 --- a/lib/spack/spack/cmd/build_env.py +++ b/lib/spack/spack/cmd/build_env.py @@ -5,7 +5,7 @@ import spack.cmd.common.env_utility as env_utility description = ( - "run a command in a spec's install environment, " "or dump its environment to screen or file" + "run a command in a spec's install environment, or dump its environment to screen or file" ) section = "build" level = "long" diff --git a/lib/spack/spack/cmd/common/env_utility.py b/lib/spack/spack/cmd/common/env_utility.py index f2c4132b3b..095360ee04 100644 --- a/lib/spack/spack/cmd/common/env_utility.py +++ b/lib/spack/spack/cmd/common/env_utility.py @@ -12,7 +12,11 @@ import llnl.util.tty as tty import spack.build_environment as build_environment import spack.cmd import spack.cmd.common.arguments as arguments +import spack.error import spack.paths +import spack.spec +import spack.store +from spack import traverse from spack.util.environment import dump_environment, pickle_environment @@ -38,6 +42,41 @@ def setup_parser(subparser): ) +class AreDepsInstalledVisitor: + def __init__(self, context="build"): + if context not in ("build", "test"): + raise ValueError("context can only be build or test") + + if context == "build": + self.direct_deps = ("build", "link", "run") + else: + self.direct_deps = ("build", "test", "link", "run") + + self.has_uninstalled_deps = False + + def accept(self, item): + # The root may be installed or uninstalled. + if item.depth == 0: + return True + + # Early exit after we've seen an uninstalled dep. + if self.has_uninstalled_deps: + return False + + spec = item.edge.spec + if not spec.external and not spec.installed: + self.has_uninstalled_deps = True + return False + + return True + + def neighbors(self, item): + # Direct deps: follow build & test edges. + # Transitive deps: follow link / run. + deptypes = self.direct_deps if item.depth == 0 else ("link", "run") + return item.edge.spec.edges_to_dependencies(deptype=deptypes) + + def emulate_env_utility(cmd_name, context, args): if not args.spec: tty.die("spack %s requires a spec." % cmd_name) @@ -65,6 +104,27 @@ def emulate_env_utility(cmd_name, context, args): spec = spack.cmd.matching_spec_from_env(spec) + # Require that dependencies are installed. + visitor = AreDepsInstalledVisitor(context=context) + + # Mass install check needs read transaction. + with spack.store.db.read_transaction(): + traverse.traverse_breadth_first_with_visitor([spec], traverse.CoverNodesVisitor(visitor)) + + if visitor.has_uninstalled_deps: + raise spack.error.SpackError( + f"Not all dependencies of {spec.name} are installed. " + f"Cannot setup {context} environment:", + spec.tree( + status_fn=spack.spec.Spec.install_status, + hashlen=7, + hashes=True, + # This shows more than necessary, but we cannot dynamically change deptypes + # in Spec.tree(...). + deptypes="all" if context == "build" else ("build", "test", "link", "run"), + ), + ) + build_environment.setup_package(spec.package, args.dirty, context) if args.dump: diff --git a/lib/spack/spack/cmd/test_env.py b/lib/spack/spack/cmd/test_env.py index 0a7bee6376..049df9d5c0 100644 --- a/lib/spack/spack/cmd/test_env.py +++ b/lib/spack/spack/cmd/test_env.py @@ -5,7 +5,7 @@ import spack.cmd.common.env_utility as env_utility description = ( - "run a command in a spec's test environment, " "or dump its environment to screen or file" + "run a command in a spec's test environment, or dump its environment to screen or file" ) section = "admin" level = "long" diff --git a/lib/spack/spack/test/cmd/build_env.py b/lib/spack/spack/test/cmd/build_env.py index 2efc59fb8e..0e10a629ed 100644 --- a/lib/spack/spack/test/cmd/build_env.py +++ b/lib/spack/spack/test/cmd/build_env.py @@ -6,6 +6,7 @@ import pickle import pytest +import spack.error from spack.main import SpackCommand build_env = SpackCommand("build-env") @@ -48,3 +49,10 @@ def test_pickle(tmpdir): environment = pickle.load(open(_out_file, "rb")) assert type(environment) == dict assert "PATH" in environment + + +def test_failure_when_uninstalled_deps(config, mock_packages): + with pytest.raises( + spack.error.SpackError, match="Not all dependencies of dttop are installed" + ): + build_env("dttop") -- cgit v1.2.3-60-g2f50