diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py index e81161de90..d276fa3a43 100644 --- a/lib/spack/spack/cmd/solve.py +++ b/lib/spack/spack/cmd/solve.py @@ -116,9 +116,7 @@ def solve(parser, args): # dump the solutions as concretized specs if 'solutions' in dump: - best = min(result.answers) - - opt, _, answer = best + opt, _, _ = min(result.answers) if ("opt" in dump) and (not args.format): tty.msg("Best of %d considered solutions." % result.nmodels) tty.msg("Optimization Criteria:") @@ -132,16 +130,7 @@ def solve(parser, args): color.cprint(fmt % (i + 1, name, val)) print() - # iterate over roots from command line - for input_spec in specs: - key = input_spec.name - if input_spec.virtual: - providers = [spec.name for spec in answer.values() - if spec.package.provides(key)] - key = providers[0] - - spec = answer[key] - + for spec in result.specs: # With -y, just print YAML to output. if args.format == 'yaml': # use write because to_yaml already has a newline. diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py index 900009e283..81af96504a 100644 --- a/lib/spack/spack/concretize.py +++ b/lib/spack/spack/concretize.py @@ -737,8 +737,7 @@ def _concretize_specs_together_new(*abstract_specs, **kwargs): result.print_cores() tty.die("Unsatisfiable spec.") - opt, i, answer = min(result.answers) - return [answer[s.name].copy() for s in abstract_specs] + return [s.copy() for s in result.specs] def _concretize_specs_together_original(*abstract_specs, **kwargs): diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 99e556a764..5bfbf1a6a0 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -197,7 +197,7 @@ def check_packages_exist(specs): class Result(object): """Result of an ASP solve.""" - def __init__(self, asp=None): + def __init__(self, specs, asp=None): self.asp = asp self.satisfiable = None self.optimal = None @@ -211,12 +211,45 @@ def __init__(self, asp=None): # names of optimization criteria self.criteria = [] + # Abstract user requests + self.abstract_specs = specs + + # Concrete specs + self._concrete_specs = None + def print_cores(self): for core in self.cores: tty.msg( "The following constraints are unsatisfiable:", *sorted(str(symbol) for symbol in core)) + @property + def specs(self): + """List of concretized specs satisfying the initial + abstract request. + """ + # The specs were already computed, return them + if self._concrete_specs: + return self._concrete_specs + + # Assert prerequisite + msg = 'cannot compute specs ["satisfiable" is not True ]' + assert self.satisfiable, msg + + self._concrete_specs = [] + best = min(self.answers) + opt, _, answer = best + for input_spec in self.abstract_specs: + key = input_spec.name + if input_spec.virtual: + providers = [spec.name for spec in answer.values() + if spec.package.provides(key)] + key = providers[0] + + self._concrete_specs.append(answer[key]) + + return self._concrete_specs + def _normalize_packages_yaml(packages_yaml): normalized_yaml = copy.copy(packages_yaml) @@ -329,7 +362,7 @@ def solve( timer.phase("ground") # With a grounded program, we can run the solve. - result = Result() + result = Result(specs) models = [] # stable models if things go well cores = [] # unsatisfiable cores if they do not diff --git a/lib/spack/spack/test/cmd/env.py b/lib/spack/spack/test/cmd/env.py index 5aa30a9455..0c7dea4c3e 100644 --- a/lib/spack/spack/test/cmd/env.py +++ b/lib/spack/spack/test/cmd/env.py @@ -2561,3 +2561,15 @@ def test_multiple_modules_post_env_hook(tmpdir, install_mockery, mock_fetch): assert view_prefix not in full_contents assert spec.prefix in full_contents + + +@pytest.mark.regression('24148') +def test_virtual_spec_concretize_together(tmpdir): + # An environment should permit to concretize "mpi" + e = ev.create('virtual_spec') + e.concretization = 'together' + + e.add('mpi') + e.concretize() + + assert any(s.package.provides('mpi') for _, s in e.concretized_specs())