diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py index 490538694b..d75cf6b262 100644 --- a/lib/spack/spack/cmd/env.py +++ b/lib/spack/spack/cmd/env.py @@ -672,18 +672,31 @@ def env_depfile(args): # Currently only make is supported. spack.cmd.require_active_env(cmd_name="env depfile") + env = ev.active_environment() + # What things do we build when running make? By default, we build the # root specs. If specific specs are provided as input, we build those. filter_specs = spack.cmd.parse_specs(args.specs) if args.specs else None template = spack.tengine.make_environment().get_template(os.path.join("depfile", "Makefile")) model = depfile.MakefileModel.from_env( - ev.active_environment(), + env, filter_specs=filter_specs, pkg_buildcache=depfile.UseBuildCache.from_string(args.use_buildcache[0]), dep_buildcache=depfile.UseBuildCache.from_string(args.use_buildcache[1]), make_prefix=args.make_prefix, jobserver=args.jobserver, ) + + # Warn in case we're generating a depfile for an empty environment. We don't automatically + # concretize; the user should do that explicitly. Could be changed in the future if requested. + if model.empty: + if not env.user_specs: + tty.warn("no specs in the environment") + elif filter_specs is not None: + tty.warn("no concrete matching specs found in environment") + else: + tty.warn("environment is not concretized. Run `spack concretize` first") + makefile = template.render(model.to_dict()) # Finally write to stdout/file. diff --git a/lib/spack/spack/environment/depfile.py b/lib/spack/spack/environment/depfile.py index f3a28331bd..34e2481fa9 100644 --- a/lib/spack/spack/environment/depfile.py +++ b/lib/spack/spack/environment/depfile.py @@ -232,6 +232,10 @@ def to_dict(self): "pkg_ids": " ".join(self.all_pkg_identifiers), } + @property + def empty(self): + return len(self.roots) == 0 + @staticmethod def from_env( env: ev.Environment, @@ -254,15 +258,10 @@ def from_env( jobserver: when enabled, make will invoke Spack with jobserver support. For dry-run this should be disabled. """ - # If no specs are provided as a filter, build all the specs in the environment. - if filter_specs: - entrypoints = [env.matching_spec(s) for s in filter_specs] - else: - entrypoints = [s for _, s in env.concretized_specs()] - + roots = env.all_matching_specs(*filter_specs) if filter_specs else env.concrete_roots() visitor = DepfileSpecVisitor(pkg_buildcache, dep_buildcache) traverse.traverse_breadth_first_with_visitor( - entrypoints, traverse.CoverNodesVisitor(visitor, key=lambda s: s.dag_hash()) + roots, traverse.CoverNodesVisitor(visitor, key=lambda s: s.dag_hash()) ) - return MakefileModel(env, entrypoints, visitor.adjacency_list, make_prefix, jobserver) + return MakefileModel(env, roots, visitor.adjacency_list, make_prefix, jobserver) diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py index 0c290493ba..3b843be72a 100644 --- a/lib/spack/spack/test/cmd/env.py +++ b/lib/spack/spack/test/cmd/env.py @@ -3382,6 +3382,20 @@ def test_spack_package_ids_variable(tmpdir, mock_packages): assert "post-install: {}".format(s.dag_hash()) in out +def test_depfile_empty_does_not_error(tmp_path): + # For empty environments Spack should create a depfile that does nothing + make = Executable("make") + makefile = str(tmp_path / "Makefile") + + env("create", "test") + with ev.read("test"): + env("depfile", "-o", makefile) + + make("-f", makefile) + + assert make.returncode == 0 + + def test_unify_when_possible_works_around_conflicts(): e = ev.create("coconcretization") e.unify = "when_possible" diff --git a/share/spack/templates/depfile/Makefile b/share/spack/templates/depfile/Makefile index dde42cf7d5..4b76475267 100644 --- a/share/spack/templates/depfile/Makefile +++ b/share/spack/templates/depfile/Makefile @@ -8,7 +8,7 @@ SPACK_INSTALL_FLAGS ?= {{ all_target }}: {{ env_target }} -{{ env_target }}: {{ root_install_targets }} +{{ env_target }}: {{ root_install_targets }} | {{ dirs_target }} @touch $@ {{ dirs_target }}: