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:
Harmen Stoppels 2023-02-22 10:35:44 +01:00 committed by GitHub
parent b8d15e816b
commit 9d6630e245
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 2 deletions

View File

@ -5,7 +5,7 @@
import spack.cmd.common.env_utility as env_utility import spack.cmd.common.env_utility as env_utility
description = ( 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" section = "build"
level = "long" level = "long"

View File

@ -12,7 +12,11 @@
import spack.build_environment as build_environment import spack.build_environment as build_environment
import spack.cmd import spack.cmd
import spack.cmd.common.arguments as arguments import spack.cmd.common.arguments as arguments
import spack.error
import spack.paths import spack.paths
import spack.spec
import spack.store
from spack import traverse
from spack.util.environment import dump_environment, pickle_environment 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): def emulate_env_utility(cmd_name, context, args):
if not args.spec: if not args.spec:
tty.die("spack %s requires a spec." % cmd_name) 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) 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) build_environment.setup_package(spec.package, args.dirty, context)
if args.dump: if args.dump:

View File

@ -5,7 +5,7 @@
import spack.cmd.common.env_utility as env_utility import spack.cmd.common.env_utility as env_utility
description = ( 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" section = "admin"
level = "long" level = "long"

View File

@ -6,6 +6,7 @@
import pytest import pytest
import spack.error
from spack.main import SpackCommand from spack.main import SpackCommand
build_env = SpackCommand("build-env") build_env = SpackCommand("build-env")
@ -48,3 +49,10 @@ def test_pickle(tmpdir):
environment = pickle.load(open(_out_file, "rb")) environment = pickle.load(open(_out_file, "rb"))
assert type(environment) == dict assert type(environment) == dict
assert "PATH" in environment 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")