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 ```
This commit is contained in:
		@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,11 @@
 | 
			
		||||
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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user