Added a function that concretizes specs together (#11158)
* Added a function that concretizes specs together * Specs concretized together are copied instead of being referenced This makes the specs different objects and removes any reference to the fake root package that is needed currently for concretization. * Factored creating a repository for concretization into its own function * Added a test on overlapping dependencies
This commit is contained in:
		
				
					committed by
					
						
						Greg Becker
					
				
			
			
				
	
			
			
			
						parent
						
							0425670942
						
					
				
				
					commit
					5ffb270714
				
			@@ -15,6 +15,12 @@
 | 
			
		||||
      concretization  policies.
 | 
			
		||||
"""
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
import tempfile
 | 
			
		||||
import llnl.util.filesystem as fs
 | 
			
		||||
import llnl.util.tty as tty
 | 
			
		||||
 | 
			
		||||
from itertools import chain
 | 
			
		||||
from functools_backport import reverse_order
 | 
			
		||||
from contextlib import contextmanager
 | 
			
		||||
@@ -28,6 +34,7 @@
 | 
			
		||||
import spack.compilers
 | 
			
		||||
import spack.architecture
 | 
			
		||||
import spack.error
 | 
			
		||||
import spack.tengine
 | 
			
		||||
from spack.config import config
 | 
			
		||||
from spack.version import ver, Version, VersionList, VersionRange
 | 
			
		||||
from spack.package_prefs import PackagePrefs, spec_externals, is_spec_buildable
 | 
			
		||||
@@ -465,6 +472,57 @@ def _compiler_concretization_failure(compiler_spec, arch):
 | 
			
		||||
        raise UnavailableCompilerVersionError(compiler_spec, arch)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def concretize_specs_together(*abstract_specs):
 | 
			
		||||
    """Given a number of specs as input, tries to concretize them together.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        *abstract_specs: abstract specs to be concretized, given either
 | 
			
		||||
            as Specs or strings
 | 
			
		||||
 | 
			
		||||
    Returns:
 | 
			
		||||
        List of concretized specs
 | 
			
		||||
    """
 | 
			
		||||
    def make_concretization_repository(abstract_specs):
 | 
			
		||||
        """Returns the path to a temporary repository created to contain
 | 
			
		||||
        a fake package that depends on all of the abstract specs.
 | 
			
		||||
        """
 | 
			
		||||
        tmpdir = tempfile.mkdtemp()
 | 
			
		||||
        repo_path, _ = spack.repo.create_repo(tmpdir)
 | 
			
		||||
 | 
			
		||||
        debug_msg = '[CONCRETIZATION]: Creating helper repository in {0}'
 | 
			
		||||
        tty.debug(debug_msg.format(repo_path))
 | 
			
		||||
 | 
			
		||||
        pkg_dir = os.path.join(repo_path, 'packages', 'concretizationroot')
 | 
			
		||||
        fs.mkdirp(pkg_dir)
 | 
			
		||||
        environment = spack.tengine.make_environment()
 | 
			
		||||
        template = environment.get_template('misc/coconcretization.pyt')
 | 
			
		||||
 | 
			
		||||
        # Split recursive specs, as it seems the concretizer has issue
 | 
			
		||||
        # respecting conditions on dependents expressed like
 | 
			
		||||
        # depends_on('foo ^bar@1.0'), see issue #11160
 | 
			
		||||
        split_specs = [dep for spec in abstract_specs
 | 
			
		||||
                       for dep in spec.traverse(root=True)]
 | 
			
		||||
 | 
			
		||||
        with open(os.path.join(pkg_dir, 'package.py'), 'w') as f:
 | 
			
		||||
            f.write(template.render(specs=[str(s) for s in split_specs]))
 | 
			
		||||
 | 
			
		||||
        return spack.repo.Repo(repo_path)
 | 
			
		||||
 | 
			
		||||
    abstract_specs = [spack.spec.Spec(s) for s in abstract_specs]
 | 
			
		||||
    concretization_repository = make_concretization_repository(abstract_specs)
 | 
			
		||||
 | 
			
		||||
    with spack.repo.additional_repository(concretization_repository):
 | 
			
		||||
        # Spec from a helper package that depends on all the abstract_specs
 | 
			
		||||
        concretization_root = spack.spec.Spec('concretizationroot')
 | 
			
		||||
        concretization_root.concretize()
 | 
			
		||||
        # Retrieve the direct dependencies
 | 
			
		||||
        concrete_specs = [
 | 
			
		||||
            concretization_root[spec.name].copy() for spec in abstract_specs
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    return concrete_specs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoCompilersForArchError(spack.error.SpackError):
 | 
			
		||||
    def __init__(self, arch, available_os_targets):
 | 
			
		||||
        err_msg = ("No compilers found"
 | 
			
		||||
@@ -474,8 +532,9 @@ def __init__(self, arch, available_os_targets):
 | 
			
		||||
                   (arch.os, arch.target))
 | 
			
		||||
 | 
			
		||||
        available_os_target_strs = list()
 | 
			
		||||
        for os, t in available_os_targets:
 | 
			
		||||
            os_target_str = "%s-%s" % (os, t) if t else os
 | 
			
		||||
        for operating_system, t in available_os_targets:
 | 
			
		||||
            os_target_str = "%s-%s" % (operating_system, t) if t \
 | 
			
		||||
                else operating_system
 | 
			
		||||
            available_os_target_strs.append(os_target_str)
 | 
			
		||||
        err_msg += (
 | 
			
		||||
            "\nCompilers are defined for the following"
 | 
			
		||||
 
 | 
			
		||||
@@ -1223,6 +1223,18 @@ def swap(repo_path):
 | 
			
		||||
    path = saved
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@contextlib.contextmanager
 | 
			
		||||
def additional_repository(repository):
 | 
			
		||||
    """Adds temporarily a repository to the default one.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        repository: repository to be added
 | 
			
		||||
    """
 | 
			
		||||
    path.put_first(repository)
 | 
			
		||||
    yield
 | 
			
		||||
    path.remove(repository)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RepoError(spack.error.SpackError):
 | 
			
		||||
    """Superclass for repository-related errors."""
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
import llnl.util.lang
 | 
			
		||||
 | 
			
		||||
import spack.architecture
 | 
			
		||||
import spack.concretize
 | 
			
		||||
import spack.repo
 | 
			
		||||
 | 
			
		||||
from spack.concretize import find_spec
 | 
			
		||||
@@ -525,3 +526,41 @@ def test_regression_issue_7941(self):
 | 
			
		||||
        t.concretize()
 | 
			
		||||
 | 
			
		||||
        assert s.dag_hash() == t.dag_hash()
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.parametrize('abstract_specs', [
 | 
			
		||||
        # Establish a baseline - concretize a single spec
 | 
			
		||||
        ('mpileaks',),
 | 
			
		||||
        # When concretized together with older version of callpath
 | 
			
		||||
        # and dyninst it uses those older versions
 | 
			
		||||
        ('mpileaks', 'callpath@0.9', 'dyninst@8.1.1'),
 | 
			
		||||
        # Handle recursive syntax within specs
 | 
			
		||||
        ('mpileaks', 'callpath@0.9 ^dyninst@8.1.1', 'dyninst'),
 | 
			
		||||
        # Test specs that have overlapping dependencies but are not
 | 
			
		||||
        # one a dependency of the other
 | 
			
		||||
        ('mpileaks', 'direct-mpich')
 | 
			
		||||
    ])
 | 
			
		||||
    def test_simultaneous_concretization_of_specs(self, abstract_specs):
 | 
			
		||||
 | 
			
		||||
        abstract_specs = [Spec(x) for x in abstract_specs]
 | 
			
		||||
        concrete_specs = spack.concretize.concretize_specs_together(
 | 
			
		||||
            *abstract_specs
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # Check there's only one configuration of each package in the DAG
 | 
			
		||||
        names = set(
 | 
			
		||||
            dep.name for spec in concrete_specs for dep in spec.traverse()
 | 
			
		||||
        )
 | 
			
		||||
        for name in names:
 | 
			
		||||
            name_specs = set(
 | 
			
		||||
                spec[name] for spec in concrete_specs if name in spec
 | 
			
		||||
            )
 | 
			
		||||
            assert len(name_specs) == 1
 | 
			
		||||
 | 
			
		||||
        # Check that there's at least one Spec that satisfies the
 | 
			
		||||
        # initial abstract request
 | 
			
		||||
        for aspec in abstract_specs:
 | 
			
		||||
            assert any(cspec.satisfies(aspec) for cspec in concrete_specs)
 | 
			
		||||
 | 
			
		||||
        # Make sure the concrete spec are top-level specs with no dependents
 | 
			
		||||
        for spec in concrete_specs:
 | 
			
		||||
            assert not spec.dependents()
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user