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
	 Harmen Stoppels
					Harmen Stoppels