summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2015-11-28 16:26:23 -0800
committerTodd Gamblin <tgamblin@llnl.gov>2015-11-28 16:26:23 -0800
commit73ef06018e2a6542d90f0b65a42e4a0287525b7a (patch)
treee1f8bba6e5f5ea51acd81f3a9a5811738ca5b8f9
parenta338e0efd5464a40d2e206ed00af99e5dcc53a79 (diff)
downloadspack-73ef06018e2a6542d90f0b65a42e4a0287525b7a.tar.gz
spack-73ef06018e2a6542d90f0b65a42e4a0287525b7a.tar.bz2
spack-73ef06018e2a6542d90f0b65a42e4a0287525b7a.tar.xz
spack-73ef06018e2a6542d90f0b65a42e4a0287525b7a.zip
Integrate namespace attribute into spec, spec DAG, spec YAML.
-rw-r--r--lib/spack/spack/config.py22
-rw-r--r--lib/spack/spack/database.py2
-rw-r--r--lib/spack/spack/directory_layout.py9
-rw-r--r--lib/spack/spack/repository.py29
-rw-r--r--lib/spack/spack/spec.py65
-rw-r--r--lib/spack/spack/test/spec_semantics.py33
6 files changed, 125 insertions, 35 deletions
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index 66da91f629..36bf8a7fc3 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -115,7 +115,7 @@ class _ConfigCategory:
self.result_dict = {}
_config_sections[name] = self
-_ConfigCategory('config', 'config.yaml', True, False)
+_ConfigCategory('repos', 'repos.yaml', True, True)
_ConfigCategory('compilers', 'compilers.yaml', True, True)
_ConfigCategory('mirrors', 'mirrors.yaml', True, True)
_ConfigCategory('view', 'views.yaml', True, True)
@@ -212,7 +212,7 @@ def substitute_spack_prefix(path):
return path.replace('$spack', spack.prefix)
-def get_config(category='config'):
+def get_config(category):
"""Get the confguration tree for a category.
Strips off the top-level category entry from the dict
@@ -233,6 +233,10 @@ def get_config(category='config'):
continue
result = result[category.name]
+ # ignore empty sections for easy commenting of single-line configs.
+ if result is None:
+ continue
+
category.files_read_from.insert(0, path)
if category.merge:
category.result_dict = _merge_yaml(category.result_dict, result)
@@ -266,12 +270,18 @@ def get_compilers_config(arch=None):
def get_repos_config():
- config = get_config()
- if 'repos' not in config:
+ repo_list = get_config('repos')
+ if repo_list is None:
return []
- repo_list = config['repos']
- return [substitute_spack_prefix(repo) for repo in repo_list]
+ if not isinstance(repo_list, list):
+ tty.die("Bad repository configuration. 'repos' element does not contain a list.")
+
+ def expand_repo_path(path):
+ path = substitute_spack_prefix(path)
+ path = os.path.expanduser(path)
+ return path
+ return [expand_repo_path(repo) for repo in repo_list]
def get_mirror_config():
diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py
index 8e380083f3..5b3bd7502f 100644
--- a/lib/spack/spack/database.py
+++ b/lib/spack/spack/database.py
@@ -211,6 +211,8 @@ class Database(object):
child = self._read_spec_from_yaml(dep_hash, installs, hash_key)
spec._add_dependency(child)
+ spec._normal = True
+ spec._concrete = True
return spec
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index da8f4187cc..30e2c93950 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -211,9 +211,12 @@ class YamlDirectoryLayout(DirectoryLayout):
with open(path) as f:
spec = Spec.from_yaml(f)
- # Specs read from actual installations are always concrete
- spec._normal = True
- spec._concrete = True
+ # Specs read from actual installs are always concrete, so mark
+ # all parts of the spec.
+ for s in spec.traverse():
+ s._normal = True
+ s._concrete = True
+
return spec
diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py
index a2c0bbe147..1b09d63cdd 100644
--- a/lib/spack/spack/repository.py
+++ b/lib/spack/spack/repository.py
@@ -238,7 +238,16 @@ class RepoPath(object):
Raises UnknownPackageError if not found.
"""
- return self.repo_for_pkg(spec.name).get(spec)
+ # if the spec has a fully qualified namespace, we grab it
+ # directly and ignore overlay precedence.
+ if spec.namespace:
+ fullspace = '%s.%s' % (self.super_namespace, spec.namespace)
+ if not fullspace in self.by_namespace:
+ raise UnknownPackageError(
+ "No configured repository contains package %s." % spec.fullname)
+ return self.by_namespace[fullspace].get(spec)
+ else:
+ return self.repo_for_pkg(spec.name).get(spec)
def dirname_for_package_name(self, pkg_name):
@@ -454,20 +463,24 @@ class Repo(object):
if spec.virtual:
raise UnknownPackageError(spec.name)
- if new and spec in self._instances:
- del self._instances[spec]
+ if spec.namespace and spec.namespace != self.namespace:
+ raise UnknownPackageError("Repository %s does not contain package %s."
+ % (self.namespace, spec.fullname))
- if not spec in self._instances:
+ if new or spec not in self._instances:
PackageClass = self._get_pkg_class(spec.name)
try:
- copy = spec.copy()
- self._instances[copy] = PackageClass(copy)
+ package = PackageClass(spec.copy())
+ self._instances[spec] = package
+ return package
+
except Exception, e:
if spack.debug:
sys.excepthook(*sys.exc_info())
- raise FailedConstructorError(spec.name, *sys.exc_info())
+ raise FailedConstructorError(spec.fullname, *sys.exc_info())
- return self._instances[spec]
+ else:
+ return self._instances[spec]
def purge(self):
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index bb0f194c13..303df6df38 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -465,6 +465,13 @@ class Spec(object):
self.dependencies[spec.name] = spec
spec.dependents[self.name] = self
+ #
+ # Public interface
+ #
+ @property
+ def fullname(self):
+ return '%s.%s' % (self.namespace, self.name) if self.namespace else self.name
+
@property
def root(self):
@@ -518,6 +525,7 @@ class Spec(object):
return True
self._concrete = bool(not self.virtual
+ and self.namespace is not None
and self.versions.concrete
and self.variants.concrete
and self.architecture
@@ -658,6 +666,12 @@ class Spec(object):
'dependencies' : dict((d, self.dependencies[d].dag_hash())
for d in sorted(self.dependencies))
}
+
+ # Older concrete specs do not have a namespace. Omit for
+ # consistent hashing.
+ if not self.concrete or self.namespace:
+ d['namespace'] = self.namespace
+
if self.compiler:
d.update(self.compiler.to_dict())
else:
@@ -682,6 +696,7 @@ class Spec(object):
node = node[name]
spec = Spec(name)
+ spec.namespace = node.get('namespace', None)
spec.versions = VersionList.from_dict(node)
spec.architecture = node['arch']
@@ -834,7 +849,20 @@ class Spec(object):
changed = any(changes)
force=True
- self._concrete = True
+ for s in self.traverse():
+ # After concretizing, assign namespaces to anything left.
+ # Note that this doesn't count as a "change". The repository
+ # configuration is constant throughout a spack run, and
+ # normalize and concretize evaluate Packages using Repo.get(),
+ # which respects precedence. So, a namespace assignment isn't
+ # changing how a package name would have been interpreted and
+ # we can do it as late as possible to allow as much
+ # compatibility across repositories as possible.
+ if s.namespace is None:
+ s.namespace = spack.repo.repo_for_pkg(s.name).namespace
+
+ # Mark everything in the spec as concrete, as well.
+ s._concrete = True
def concretized(self):
@@ -909,7 +937,7 @@ class Spec(object):
the dependency. If no conditions are True (and we don't
depend on it), return None.
"""
- pkg = spack.repo.get(self.name)
+ pkg = spack.repo.get(self.fullname)
conditions = pkg.dependencies[name]
# evaluate when specs to figure out constraints on the dependency.
@@ -1037,7 +1065,7 @@ class Spec(object):
any_change = False
changed = True
- pkg = spack.repo.get(self.name)
+ pkg = spack.repo.get(self.fullname)
while changed:
changed = False
for dep_name in pkg.dependencies:
@@ -1058,18 +1086,17 @@ class Spec(object):
the root, and ONLY the ones that were explicitly provided are there.
Normalization turns a partial flat spec into a DAG, where:
- 1. ALL dependencies of the root package are in the DAG.
- 2. Each node's dependencies dict only contains its direct deps.
+ 1. Known dependencies of the root package are in the DAG.
+ 2. Each node's dependencies dict only contains its known direct deps.
3. There is only ONE unique spec for each package in the DAG.
* This includes virtual packages. If there a non-virtual
package that provides a virtual package that is in the spec,
then we replace the virtual package with the non-virtual one.
- 4. The spec DAG matches package DAG, including default variant values.
-
TODO: normalize should probably implement some form of cycle detection,
to ensure that the spec is actually a DAG.
+
"""
if self._normal and not force:
return False
@@ -1115,7 +1142,7 @@ class Spec(object):
for spec in self.traverse():
# Don't get a package for a virtual name.
if not spec.virtual:
- spack.repo.get(spec.name)
+ spack.repo.get(spec.fullname)
# validate compiler in addition to the package name.
if spec.compiler:
@@ -1138,6 +1165,10 @@ class Spec(object):
if not self.name == other.name:
raise UnsatisfiableSpecNameError(self.name, other.name)
+ if other.namespace is not None:
+ if self.namespace is not None and other.namespace != self.namespace:
+ raise UnsatisfiableSpecNameError(self.fullname, other.fullname)
+
if not self.versions.overlaps(other.versions):
raise UnsatisfiableVersionSpecError(self.versions, other.versions)
@@ -1181,7 +1212,7 @@ class Spec(object):
# TODO: might want more detail than this, e.g. specific deps
# in violation. if this becomes a priority get rid of this
- # check and be more specici about what's wrong.
+ # check and be more specific about what's wrong.
if not other.satisfies_dependencies(self):
raise UnsatisfiableDependencySpecError(other, self)
@@ -1247,7 +1278,7 @@ class Spec(object):
# A concrete provider can satisfy a virtual dependency.
if not self.virtual and other.virtual:
- pkg = spack.repo.get(self.name)
+ pkg = spack.repo.get(self.fullname)
if pkg.provides(other.name):
for provided, when_spec in pkg.provided.items():
if self.satisfies(when_spec, deps=False, strict=strict):
@@ -1259,6 +1290,11 @@ class Spec(object):
if self.name != other.name:
return False
+ # namespaces either match, or other doesn't require one.
+ if other.namespace is not None:
+ if self.namespace is not None and self.namespace != other.namespace:
+ return False
+
if self.versions and other.versions:
if not self.versions.satisfies(other.versions, strict=strict):
return False
@@ -1476,8 +1512,8 @@ class Spec(object):
def _cmp_node(self):
"""Comparison key for just *this node* and not its deps."""
- return (self.name, self.versions, self.variants,
- self.architecture, self.compiler)
+ return (self.name, self.namespace, self.versions,
+ self.variants, self.architecture, self.compiler)
def eq_node(self, other):
@@ -1507,7 +1543,7 @@ class Spec(object):
in the format string. The format strings you can provide are::
$_ Package name
- $. Long package name
+ $. Full package name (with namespace)
$@ Version
$% Compiler
$%@ Compiler & compiler version
@@ -1556,8 +1592,7 @@ class Spec(object):
if c == '_':
out.write(fmt % self.name)
elif c == '.':
- longname = '%s.%s.%s' % (self.namespace, self.name) if self.namespace else self.name
- out.write(fmt % longname)
+ out.write(fmt % self.fullname)
elif c == '@':
if self.versions and self.versions != _any_version:
write(fmt % (c + str(self.versions)), c)
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index 6666dbbb52..87b7b628ed 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -35,7 +35,10 @@ class SpecSematicsTest(MockPackagesTest):
# ================================================================================
def check_satisfies(self, spec, anon_spec, concrete=False):
left = Spec(spec, concrete=concrete)
- right = parse_anonymous_spec(anon_spec, left.name)
+ try:
+ right = Spec(anon_spec) # if it's not anonymous, allow it.
+ except:
+ right = parse_anonymous_spec(anon_spec, left.name)
# Satisfies is one-directional.
self.assertTrue(left.satisfies(right))
@@ -48,7 +51,10 @@ class SpecSematicsTest(MockPackagesTest):
def check_unsatisfiable(self, spec, anon_spec, concrete=False):
left = Spec(spec, concrete=concrete)
- right = parse_anonymous_spec(anon_spec, left.name)
+ try:
+ right = Spec(anon_spec) # if it's not anonymous, allow it.
+ except:
+ right = parse_anonymous_spec(anon_spec, left.name)
self.assertFalse(left.satisfies(right))
self.assertFalse(left.satisfies(anon_spec))
@@ -88,6 +94,28 @@ class SpecSematicsTest(MockPackagesTest):
self.check_satisfies('libdwarf^libelf@0.8.13', '^libelf@0:1')
+ def test_satisfies_namespace(self):
+ self.check_satisfies('builtin.mpich', 'mpich')
+ self.check_satisfies('builtin.mock.mpich', 'mpich')
+
+ # TODO: only works for deps now, but shouldn't we allow this for root spec?
+ # self.check_satisfies('builtin.mock.mpich', 'mpi')
+
+ self.check_satisfies('builtin.mock.mpich', 'builtin.mock.mpich')
+
+ self.check_unsatisfiable('builtin.mock.mpich', 'builtin.mpich')
+
+
+ def test_satisfies_namespaced_dep(self):
+ """Ensure spec from same or unspecified namespace satisfies namespace constraint."""
+ self.check_satisfies('mpileaks ^builtin.mock.mpich', '^mpich')
+
+ self.check_satisfies('mpileaks ^builtin.mock.mpich', '^mpi')
+ self.check_satisfies('mpileaks ^builtin.mock.mpich', '^builtin.mock.mpich')
+
+ self.check_unsatisfiable('mpileaks ^builtin.mock.mpich', '^builtin.mpich')
+
+
def test_satisfies_compiler(self):
self.check_satisfies('foo%gcc', '%gcc')
self.check_satisfies('foo%intel', '%intel')
@@ -327,4 +355,3 @@ class SpecSematicsTest(MockPackagesTest):
self.check_constrain_not_changed('libelf^foo+debug', 'libelf^foo+debug')
self.check_constrain_not_changed('libelf^foo~debug', 'libelf^foo~debug')
self.check_constrain_not_changed('libelf^foo=bgqos_0', 'libelf^foo=bgqos_0')
-