Working PackageFinder / Repo
This commit is contained in:
parent
38fdd063d9
commit
8c06b92225
10 changed files with 334 additions and 163 deletions
2
.mailmap
2
.mailmap
|
@ -9,3 +9,5 @@ Saravan Pantham <saravan.pantham@gmail.com> Saravan Pantham <pantham1@su
|
||||||
Tom Scogland <tscogland@llnl.gov> Tom Scogland <scogland1@llnl.gov>
|
Tom Scogland <tscogland@llnl.gov> Tom Scogland <scogland1@llnl.gov>
|
||||||
Tom Scogland <tscogland@llnl.gov> Tom Scogland <tom.scogland@gmail.com>
|
Tom Scogland <tscogland@llnl.gov> Tom Scogland <tom.scogland@gmail.com>
|
||||||
Joachim Protze <protze@rz.rwth-aachen.de> jprotze <protze@rz.rwth-aachen.de>
|
Joachim Protze <protze@rz.rwth-aachen.de> jprotze <protze@rz.rwth-aachen.de>
|
||||||
|
Gregory L. Lee <lee218@llnl.gov> Gregory L. Lee <lee218@surface86.llnl.gov>
|
||||||
|
Gregory L. Lee <lee218@llnl.gov> Gregory Lee <lee218@llnl.gov>
|
||||||
|
|
|
@ -51,12 +51,6 @@
|
||||||
install_path = join_path(opt_path, "spack")
|
install_path = join_path(opt_path, "spack")
|
||||||
etc_path = join_path(prefix, "etc")
|
etc_path = join_path(prefix, "etc")
|
||||||
|
|
||||||
#
|
|
||||||
# Setup the spack.repos namespace
|
|
||||||
#
|
|
||||||
from spack.repo_loader import RepoNamespace
|
|
||||||
repos = RepoNamespace()
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Set up the default packages database.
|
# Set up the default packages database.
|
||||||
#
|
#
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
import re
|
import re
|
||||||
import itertools
|
import itertools
|
||||||
import traceback
|
import traceback
|
||||||
|
from bisect import bisect_left
|
||||||
from external import yaml
|
from external import yaml
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
|
@ -58,11 +59,19 @@ def converter(self, spec_like, *args, **kwargs):
|
||||||
return converter
|
return converter
|
||||||
|
|
||||||
|
|
||||||
class PackageFinder(object):
|
def _make_namespace_module(ns):
|
||||||
"""A PackageFinder is a wrapper around a list of PackageDBs.
|
module = imp.new_module(ns)
|
||||||
|
module.__file__ = "(spack namespace)"
|
||||||
|
module.__path__ = []
|
||||||
|
module.__package__ = ns
|
||||||
|
return module
|
||||||
|
|
||||||
It functions exactly like a PackageDB, but it operates on the
|
|
||||||
combined results of the PackageDBs in its list instead of on a
|
class PackageFinder(object):
|
||||||
|
"""A PackageFinder is a wrapper around a list of Repos.
|
||||||
|
|
||||||
|
It functions exactly like a Repo, but it operates on the
|
||||||
|
combined results of the Repos in its list instead of on a
|
||||||
single package repository.
|
single package repository.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *repo_dirs):
|
def __init__(self, *repo_dirs):
|
||||||
|
@ -70,61 +79,74 @@ def __init__(self, *repo_dirs):
|
||||||
self.by_namespace = NamespaceTrie()
|
self.by_namespace = NamespaceTrie()
|
||||||
self.by_path = {}
|
self.by_path = {}
|
||||||
|
|
||||||
|
self._all_package_names = []
|
||||||
|
self._provider_index = None
|
||||||
|
|
||||||
for root in repo_dirs:
|
for root in repo_dirs:
|
||||||
repo = PackageDB(root)
|
repo = Repo(root)
|
||||||
self.put_last(repo)
|
self.put_last(repo)
|
||||||
|
|
||||||
|
|
||||||
def _check_repo(self, repo):
|
def swap(self, other):
|
||||||
|
"""Convenience function to make swapping repostiories easier.
|
||||||
|
|
||||||
|
This is currently used by mock tests.
|
||||||
|
TODO: Maybe there is a cleaner way.
|
||||||
|
|
||||||
|
"""
|
||||||
|
attrs = ['repos', 'by_namespace', 'by_path', '_all_package_names', '_provider_index']
|
||||||
|
for attr in attrs:
|
||||||
|
tmp = getattr(self, attr)
|
||||||
|
setattr(self, attr, getattr(other, attr))
|
||||||
|
setattr(other, attr, tmp)
|
||||||
|
|
||||||
|
|
||||||
|
def _add(self, repo):
|
||||||
|
"""Add a repository to the namespace and path indexes.
|
||||||
|
|
||||||
|
Checks for duplicates -- two repos can't have the same root
|
||||||
|
directory, and they provide have the same namespace.
|
||||||
|
|
||||||
|
"""
|
||||||
if repo.root in self.by_path:
|
if repo.root in self.by_path:
|
||||||
raise DuplicateRepoError("Package repos are the same",
|
raise DuplicateRepoError("Package repos are the same",
|
||||||
repo, self.by_path[repo.root])
|
repo, self.by_path[repo.root])
|
||||||
|
|
||||||
if repo.namespace in self.by_namespace:
|
if repo.namespace in self.by_namespace:
|
||||||
tty.error("Package repos cannot have the same name",
|
raise DuplicateRepoError("Package repos cannot have the same name",
|
||||||
repo, self.by_namespace[repo.namespace])
|
repo, self.by_namespace[repo.namespace])
|
||||||
|
|
||||||
|
# Add repo to the pkg indexes
|
||||||
def _add(self, repo):
|
|
||||||
self._check_repo(repo)
|
|
||||||
self.by_namespace[repo.namespace] = repo
|
self.by_namespace[repo.namespace] = repo
|
||||||
self.by_path[repo.root] = repo
|
self.by_path[repo.root] = repo
|
||||||
|
|
||||||
|
# add names to the cached name list
|
||||||
|
new_pkgs = set(repo.all_package_names())
|
||||||
|
new_pkgs.update(set(self._all_package_names))
|
||||||
|
self._all_package_names = sorted(new_pkgs, key=lambda n:n.lower())
|
||||||
|
|
||||||
|
|
||||||
def put_first(self, repo):
|
def put_first(self, repo):
|
||||||
|
"""Add repo first in the search path."""
|
||||||
self._add(repo)
|
self._add(repo)
|
||||||
self.repos.insert(0, repo)
|
self.repos.insert(0, repo)
|
||||||
|
|
||||||
|
|
||||||
def put_last(self, repo):
|
def put_last(self, repo):
|
||||||
|
"""Add repo last in the search path."""
|
||||||
self._add(repo)
|
self._add(repo)
|
||||||
self.repos.append(repo)
|
self.repos.append(repo)
|
||||||
|
|
||||||
|
|
||||||
def remove(self, repo):
|
def remove(self, repo):
|
||||||
|
"""Remove a repo from the search path."""
|
||||||
if repo in self.repos:
|
if repo in self.repos:
|
||||||
self.repos.remove(repo)
|
self.repos.remove(repo)
|
||||||
|
|
||||||
|
|
||||||
def swap(self, other):
|
|
||||||
repos = self.repos
|
|
||||||
by_namespace = self.by_namespace
|
|
||||||
by_path = self.by_path
|
|
||||||
|
|
||||||
self.repos = other.repos
|
|
||||||
self.by_namespace = other.by_namespace
|
|
||||||
self.by_pah = other.by_path
|
|
||||||
|
|
||||||
other.repos = repos
|
|
||||||
other.by_namespace = by_namespace
|
|
||||||
other.by_path = by_path
|
|
||||||
|
|
||||||
|
|
||||||
def all_package_names(self):
|
def all_package_names(self):
|
||||||
all_pkgs = set()
|
"""Return all unique package names in all repositories."""
|
||||||
for repo in self.repos:
|
return self._all_package_names
|
||||||
all_pkgs.update(set(repo.all_package_names()))
|
|
||||||
return all_pkgs
|
|
||||||
|
|
||||||
|
|
||||||
def all_packages(self):
|
def all_packages(self):
|
||||||
|
@ -132,109 +154,82 @@ def all_packages(self):
|
||||||
yield self.get(name)
|
yield self.get(name)
|
||||||
|
|
||||||
|
|
||||||
def providers_for(self, vpkg_name):
|
@_autospec
|
||||||
# TODO: THIS IS WRONG; should use more than first repo
|
def providers_for(self, vpkg_spec):
|
||||||
return self.repos[0].providers_for(vpkg_name)
|
if self._provider_index is None:
|
||||||
|
self._provider_index = ProviderIndex(self.all_package_names())
|
||||||
|
|
||||||
|
providers = self._provider_index.providers_for(vpkg_spec)
|
||||||
|
if not providers:
|
||||||
|
raise UnknownPackageError(vpkg_spec.name)
|
||||||
|
return providers
|
||||||
|
|
||||||
|
|
||||||
def _get_spack_pkg_name(self, repo, py_module_name):
|
def find_module(self, fullname, path=None):
|
||||||
"""Allow users to import Spack packages using legal Python identifiers.
|
"""Implements precedence for overlaid namespaces.
|
||||||
|
|
||||||
A python identifier might map to many different Spack package
|
Loop checks each namespace in self.repos for packages, and
|
||||||
names due to hyphen/underscore ambiguity.
|
also handles loading empty containing namespaces.
|
||||||
|
|
||||||
Easy example:
|
|
||||||
num3proxy -> 3proxy
|
|
||||||
|
|
||||||
Ambiguous:
|
|
||||||
foo_bar -> foo_bar, foo-bar
|
|
||||||
|
|
||||||
More ambiguous:
|
|
||||||
foo_bar_baz -> foo_bar_baz, foo-bar-baz, foo_bar-baz, foo-bar_baz
|
|
||||||
"""
|
"""
|
||||||
if py_module_name in repo:
|
# namespaces are added to repo, and package modules are leaves.
|
||||||
return py_module_name
|
namespace, dot, module_name = fullname.rpartition('.')
|
||||||
|
|
||||||
options = possible_spack_module_names(py_module_name)
|
# If it's a module in some repo, or if it is the repo's
|
||||||
options.remove(py_module_name)
|
# namespace, let the repo handle it.
|
||||||
for name in options:
|
for repo in self.repos:
|
||||||
if name in repo:
|
if namespace == repo.namespace:
|
||||||
return name
|
if repo.real_name(module_name):
|
||||||
|
return repo
|
||||||
|
elif fullname == repo.namespace:
|
||||||
|
return repo
|
||||||
|
|
||||||
|
# No repo provides the namespace, but it is a valid prefix of
|
||||||
|
# something in the PackageFinder.
|
||||||
|
if self.by_namespace.is_prefix(fullname):
|
||||||
|
return self
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def find_module(self, fullname, path=None):
|
|
||||||
if fullname in self.by_namespace:
|
|
||||||
return self
|
|
||||||
|
|
||||||
namespace, dot, module_name = fullname.rpartition('.')
|
|
||||||
if namespace not in self.by_namespace:
|
|
||||||
return None
|
|
||||||
|
|
||||||
repo = self.by_namespace[namespace]
|
|
||||||
name = self._get_spack_pkg_name(repo, module_name)
|
|
||||||
if not name:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
def load_module(self, fullname):
|
def load_module(self, fullname):
|
||||||
|
"""Loads containing namespaces when necessary.
|
||||||
|
|
||||||
|
See ``Repo`` for how actual package modules are loaded.
|
||||||
|
"""
|
||||||
if fullname in sys.modules:
|
if fullname in sys.modules:
|
||||||
return sys.modules[fullname]
|
return sys.modules[fullname]
|
||||||
|
|
||||||
if fullname in self.by_namespace:
|
# partition fullname into prefix and module name.
|
||||||
ns = self.by_namespace[fullname]
|
namespace, dot, module_name = fullname.rpartition('.')
|
||||||
module = imp.new_module(fullname)
|
|
||||||
module.__file__ = "<spack-namespace>"
|
|
||||||
module.__path__ = []
|
|
||||||
module.__package__ = fullname
|
|
||||||
|
|
||||||
else:
|
if not self.by_namespace.is_prefix(fullname):
|
||||||
namespace, dot, module_name = fullname.rpartition('.')
|
raise ImportError("No such Spack repo: %s" % fullname)
|
||||||
if namespace not in self.by_namespace:
|
|
||||||
raise ImportError(
|
|
||||||
"No Spack repository with namespace %s" % namespace)
|
|
||||||
|
|
||||||
repo = self.by_namespace[namespace]
|
|
||||||
name = self._get_spack_pkg_name(repo, module_name)
|
|
||||||
if not name:
|
|
||||||
raise ImportError(
|
|
||||||
"No module %s in Spack repository %s" % (module_name, repo))
|
|
||||||
|
|
||||||
fullname = namespace + '.' + name
|
|
||||||
file_path = os.path.join(repo.root, name, package_file_name)
|
|
||||||
module = imp.load_source(fullname, file_path)
|
|
||||||
module.__package__ = namespace
|
|
||||||
|
|
||||||
|
module = _make_namespace_module(namespace)
|
||||||
module.__loader__ = self
|
module.__loader__ = self
|
||||||
sys.modules[fullname] = module
|
sys.modules[fullname] = module
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def _find_repo_for_spec(self, spec):
|
def repo_for_pkg(self, pkg_name):
|
||||||
"""Find a repo that contains the supplied spec's package.
|
|
||||||
|
|
||||||
Raises UnknownPackageErrorfor if not found.
|
|
||||||
"""
|
|
||||||
for repo in self.repos:
|
for repo in self.repos:
|
||||||
if spec.name in repo:
|
if pkg_name in repo:
|
||||||
return repo
|
return repo
|
||||||
raise UnknownPackageError(spec.name)
|
raise UnknownPackageError(pkg_name)
|
||||||
|
|
||||||
|
|
||||||
@_autospec
|
@_autospec
|
||||||
def get(self, spec, new=False):
|
def get(self, spec, new=False):
|
||||||
return self._find_repo_for_spec(spec).get(spec, new)
|
"""Find a repo that contains the supplied spec's package.
|
||||||
|
|
||||||
|
Raises UnknownPackageError if not found.
|
||||||
|
"""
|
||||||
|
return self.repo_for_pkg(spec.name).get(spec)
|
||||||
|
|
||||||
|
|
||||||
def get_repo(self, namespace):
|
def dirname_for_package_name(self, pkg_name):
|
||||||
if namespace in self.by_namespace:
|
return self.repo_for_pkg(pkg_name).dirname_for_package_name(pkg_name)
|
||||||
repo = self.by_namespace[namespace]
|
|
||||||
if isinstance(repo, PackageDB):
|
|
||||||
return repo
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def exists(self, pkg_name):
|
def exists(self, pkg_name):
|
||||||
|
@ -246,7 +241,7 @@ def __contains__(self, pkg_name):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PackageDB(object):
|
class Repo(object):
|
||||||
"""Class representing a package repository in the filesystem.
|
"""Class representing a package repository in the filesystem.
|
||||||
|
|
||||||
Each package repository must have a top-level configuration file
|
Each package repository must have a top-level configuration file
|
||||||
|
@ -278,10 +273,107 @@ def __init__(self, root):
|
||||||
tty.die(("Invalid namespace '%s' in '%s'. Namespaces must be "
|
tty.die(("Invalid namespace '%s' in '%s'. Namespaces must be "
|
||||||
"valid python identifiers separated by '.'")
|
"valid python identifiers separated by '.'")
|
||||||
% (self.namespace, self.root))
|
% (self.namespace, self.root))
|
||||||
|
self._names = self.namespace.split('.')
|
||||||
|
|
||||||
# These are internal cache variables.
|
# These are internal cache variables.
|
||||||
|
self._modules = {}
|
||||||
|
self._classes = {}
|
||||||
self._instances = {}
|
self._instances = {}
|
||||||
|
|
||||||
self._provider_index = None
|
self._provider_index = None
|
||||||
|
self._all_package_names = None
|
||||||
|
|
||||||
|
# make sure the namespace for packages in this repo exists.
|
||||||
|
self._create_namespace()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_namespace(self):
|
||||||
|
"""Create this repo's namespace module and insert it into sys.modules.
|
||||||
|
|
||||||
|
Ensures that modules loaded via the repo have a home, and that
|
||||||
|
we don't get runtime warnings from Python's module system.
|
||||||
|
|
||||||
|
"""
|
||||||
|
for l in range(1, len(self._names)+1):
|
||||||
|
ns = '.'.join(self._names[:l])
|
||||||
|
if not ns in sys.modules:
|
||||||
|
sys.modules[ns] = _make_namespace_module(ns)
|
||||||
|
sys.modules[ns].__loader__ = self
|
||||||
|
|
||||||
|
|
||||||
|
def real_name(self, import_name):
|
||||||
|
"""Allow users to import Spack packages using Python identifiers.
|
||||||
|
|
||||||
|
A python identifier might map to many different Spack package
|
||||||
|
names due to hyphen/underscore ambiguity.
|
||||||
|
|
||||||
|
Easy example:
|
||||||
|
num3proxy -> 3proxy
|
||||||
|
|
||||||
|
Ambiguous:
|
||||||
|
foo_bar -> foo_bar, foo-bar
|
||||||
|
|
||||||
|
More ambiguous:
|
||||||
|
foo_bar_baz -> foo_bar_baz, foo-bar-baz, foo_bar-baz, foo-bar_baz
|
||||||
|
"""
|
||||||
|
if import_name in self:
|
||||||
|
return import_name
|
||||||
|
|
||||||
|
options = possible_spack_module_names(import_name)
|
||||||
|
options.remove(import_name)
|
||||||
|
for name in options:
|
||||||
|
if name in self:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_prefix(self, fullname):
|
||||||
|
"""True if fullname is a prefix of this Repo's namespace."""
|
||||||
|
parts = fullname.split('.')
|
||||||
|
return self._names[:len(parts)] == parts
|
||||||
|
|
||||||
|
|
||||||
|
def find_module(self, fullname, path=None):
|
||||||
|
"""Python find_module import hook.
|
||||||
|
|
||||||
|
Returns this Repo if it can load the module; None if not.
|
||||||
|
"""
|
||||||
|
if self.is_prefix(fullname):
|
||||||
|
return self
|
||||||
|
|
||||||
|
namespace, dot, module_name = fullname.rpartition('.')
|
||||||
|
if namespace == self.namespace:
|
||||||
|
if self.real_name(module_name):
|
||||||
|
return self
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def load_module(self, fullname):
|
||||||
|
"""Python importer load hook.
|
||||||
|
|
||||||
|
Tries to load the module; raises an ImportError if it can't.
|
||||||
|
"""
|
||||||
|
if fullname in sys.modules:
|
||||||
|
return sys.modules[fullname]
|
||||||
|
|
||||||
|
namespace, dot, module_name = fullname.rpartition('.')
|
||||||
|
|
||||||
|
if self.is_prefix(fullname):
|
||||||
|
module = _make_namespace_module(fullname)
|
||||||
|
|
||||||
|
elif namespace == self.namespace:
|
||||||
|
real_name = self.real_name(module_name)
|
||||||
|
if not real_name:
|
||||||
|
raise ImportError("No module %s in repo %s" % (module_name, namespace))
|
||||||
|
module = self._get_pkg_module(real_name)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ImportError("No module %s in repo %s" % (fullname, self.namespace))
|
||||||
|
|
||||||
|
module.__loader__ = self
|
||||||
|
sys.modules[fullname] = module
|
||||||
|
return module
|
||||||
|
|
||||||
|
|
||||||
def _read_config(self):
|
def _read_config(self):
|
||||||
|
@ -307,15 +399,14 @@ def get(self, spec, new=False):
|
||||||
if spec.virtual:
|
if spec.virtual:
|
||||||
raise UnknownPackageError(spec.name)
|
raise UnknownPackageError(spec.name)
|
||||||
|
|
||||||
if new:
|
if new and spec in self._instances:
|
||||||
if spec in self._instances:
|
del self._instances[spec]
|
||||||
del self._instances[spec]
|
|
||||||
|
|
||||||
if not spec in self._instances:
|
if not spec in self._instances:
|
||||||
package_class = self.get_class_for_package_name(spec.name, spec.namespace)
|
PackageClass = self._get_pkg_class(spec.name)
|
||||||
try:
|
try:
|
||||||
copy = spec.copy()
|
copy = spec.copy()
|
||||||
self._instances[copy] = package_class(copy)
|
self._instances[copy] = PackageClass(copy)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if spack.debug:
|
if spack.debug:
|
||||||
sys.excepthook(*sys.exc_info())
|
sys.excepthook(*sys.exc_info())
|
||||||
|
@ -353,7 +444,7 @@ def dirname_for_package_name(self, pkg_name):
|
||||||
|
|
||||||
def filename_for_package_name(self, pkg_name):
|
def filename_for_package_name(self, pkg_name):
|
||||||
"""Get the filename for the module we should load for a particular
|
"""Get the filename for the module we should load for a particular
|
||||||
package. Packages for a pacakge DB live in
|
package. Packages for a Repo live in
|
||||||
``$root/<package_name>/package.py``
|
``$root/<package_name>/package.py``
|
||||||
|
|
||||||
This will return a proper package.py path even if the
|
This will return a proper package.py path even if the
|
||||||
|
@ -366,19 +457,20 @@ def filename_for_package_name(self, pkg_name):
|
||||||
return join_path(pkg_dir, package_file_name)
|
return join_path(pkg_dir, package_file_name)
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
|
||||||
def all_package_names(self):
|
def all_package_names(self):
|
||||||
"""Generator function for all packages. This looks for
|
"""Returns a sorted list of all package names in the Repo."""
|
||||||
``<pkg_name>/package.py`` files within the repo direcotories"""
|
if self._all_package_names is None:
|
||||||
all_package_names = []
|
self._all_package_names = []
|
||||||
|
|
||||||
for pkg_name in os.listdir(self.root):
|
for pkg_name in os.listdir(self.root):
|
||||||
pkg_dir = join_path(self.root, pkg_name)
|
pkg_dir = join_path(self.root, pkg_name)
|
||||||
pkg_file = join_path(pkg_dir, package_file_name)
|
pkg_file = join_path(pkg_dir, package_file_name)
|
||||||
if os.path.isfile(pkg_file):
|
if os.path.isfile(pkg_file):
|
||||||
all_package_names.append(pkg_name)
|
self._all_package_names.append(pkg_name)
|
||||||
|
|
||||||
return sorted(all_package_names)
|
self._all_package_names.sort()
|
||||||
|
|
||||||
|
return self._all_package_names
|
||||||
|
|
||||||
|
|
||||||
def all_packages(self):
|
def all_packages(self):
|
||||||
|
@ -386,27 +478,54 @@ def all_packages(self):
|
||||||
yield self.get(name)
|
yield self.get(name)
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
|
||||||
def exists(self, pkg_name):
|
def exists(self, pkg_name):
|
||||||
"""Whether a package with the supplied name exists."""
|
"""Whether a package with the supplied name exists."""
|
||||||
return os.path.exists(self.filename_for_package_name(pkg_name))
|
# This does a binary search in the sorted list.
|
||||||
|
idx = bisect_left(self.all_package_names(), pkg_name)
|
||||||
|
return self._all_package_names[idx] == pkg_name
|
||||||
|
|
||||||
|
|
||||||
@memoized
|
def _get_pkg_module(self, pkg_name):
|
||||||
def get_class_for_package_name(self, pkg_name, reponame = None):
|
"""Create a module for a particular package.
|
||||||
"""Get an instance of the class for a particular package."""
|
|
||||||
file_path = self.filename_for_package_name(pkg_name)
|
This caches the module within this Repo *instance*. It does
|
||||||
|
*not* add it to ``sys.modules``. So, you can construct
|
||||||
|
multiple Repos for testing and ensure that the module will be
|
||||||
|
loaded once per repo.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if pkg_name not in self._modules:
|
||||||
|
file_path = self.filename_for_package_name(pkg_name)
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise UnknownPackageError(pkg_name, self.namespace)
|
||||||
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
tty.die("Something's wrong. '%s' is not a file!" % file_path)
|
tty.die("Something's wrong. '%s' is not a file!" % file_path)
|
||||||
|
|
||||||
if not os.access(file_path, os.R_OK):
|
if not os.access(file_path, os.R_OK):
|
||||||
tty.die("Cannot read '%s'!" % file_path)
|
tty.die("Cannot read '%s'!" % file_path)
|
||||||
else:
|
|
||||||
raise UnknownPackageError(pkg_name, self.namespace)
|
|
||||||
|
|
||||||
|
fullname = "%s.%s" % (self.namespace, pkg_name)
|
||||||
|
|
||||||
|
module = imp.load_source(fullname, file_path)
|
||||||
|
module.__package__ = self.namespace
|
||||||
|
module.__loader__ = self
|
||||||
|
self._modules[pkg_name] = module
|
||||||
|
|
||||||
|
return self._modules[pkg_name]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_pkg_class(self, pkg_name):
|
||||||
|
"""Get the class for the package out of its module.
|
||||||
|
|
||||||
|
First loads (or fetches from cache) a module for the
|
||||||
|
package. Then extracts the package class from the module
|
||||||
|
according to Spack's naming convention.
|
||||||
|
"""
|
||||||
class_name = mod_to_class(pkg_name)
|
class_name = mod_to_class(pkg_name)
|
||||||
module = __import__(self.namespace + '.' + pkg_name, fromlist=[class_name])
|
module = self._get_pkg_module(pkg_name)
|
||||||
|
|
||||||
cls = getattr(module, class_name)
|
cls = getattr(module, class_name)
|
||||||
if not inspect.isclass(cls):
|
if not inspect.isclass(cls):
|
||||||
tty.die("%s.%s is not a class" % (pkg_name, class_name))
|
tty.die("%s.%s is not a class" % (pkg_name, class_name))
|
||||||
|
@ -415,7 +534,7 @@ def get_class_for_package_name(self, pkg_name, reponame = None):
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<PackageDB '%s' from '%s'>" % (self.namespace, self.root)
|
return "<Repo '%s' from '%s'>" % (self.namespace, self.root)
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
|
@ -54,7 +54,7 @@ def __init__(self, pkg, path_or_url, level):
|
||||||
if '://' in path_or_url:
|
if '://' in path_or_url:
|
||||||
self.url = path_or_url
|
self.url = path_or_url
|
||||||
else:
|
else:
|
||||||
pkg_dir = spack.db.dirname_for_package_name(pkg_name)
|
pkg_dir = spack.db.dirname_for_package_name(self.pkg_name)
|
||||||
self.path = join_path(pkg_dir, path_or_url)
|
self.path = join_path(pkg_dir, path_or_url)
|
||||||
if not os.path.isfile(self.path):
|
if not os.path.isfile(self.path):
|
||||||
raise NoSuchPatchFileError(pkg_name, self.path)
|
raise NoSuchPatchFileError(pkg_name, self.path)
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
import os
|
import os
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
import spack
|
import spack
|
||||||
from spack.packages import PackageDB
|
|
||||||
from spack.test.mock_packages_test import *
|
from spack.test.mock_packages_test import *
|
||||||
|
|
||||||
# Some sample compiler config data
|
# Some sample compiler config data
|
||||||
|
|
|
@ -34,50 +34,81 @@ def setUp(self):
|
||||||
|
|
||||||
def test_add_single(self):
|
def test_add_single(self):
|
||||||
self.trie['foo'] = 'bar'
|
self.trie['foo'] = 'bar'
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo'))
|
||||||
self.assertEqual(self.trie['foo'], 'bar')
|
self.assertEqual(self.trie['foo'], 'bar')
|
||||||
self.assertTrue('foo' in self.trie)
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_multiple(self):
|
def test_add_multiple(self):
|
||||||
self.trie['foo.bar'] = 'baz'
|
self.trie['foo.bar'] = 'baz'
|
||||||
|
|
||||||
|
self.assertFalse(self.trie.has_value('foo'))
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo.bar'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo.bar'))
|
||||||
self.assertEqual(self.trie['foo.bar'], 'baz')
|
self.assertEqual(self.trie['foo.bar'], 'baz')
|
||||||
|
|
||||||
self.assertFalse('foo' in self.trie)
|
self.assertFalse(self.trie.is_prefix('foo.bar.baz'))
|
||||||
self.assertFalse('foo.bar.baz' in self.trie)
|
self.assertFalse(self.trie.has_value('foo.bar.baz'))
|
||||||
self.assertTrue('foo.bar' in self.trie)
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_three(self):
|
def test_add_three(self):
|
||||||
# add a three-level namespace
|
# add a three-level namespace
|
||||||
self.trie['foo.bar.baz'] = 'quux'
|
self.trie['foo.bar.baz'] = 'quux'
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo'))
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo.bar'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo.bar'))
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo.bar.baz'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo.bar.baz'))
|
||||||
self.assertEqual(self.trie['foo.bar.baz'], 'quux')
|
self.assertEqual(self.trie['foo.bar.baz'], 'quux')
|
||||||
|
|
||||||
self.assertFalse('foo' in self.trie)
|
self.assertFalse(self.trie.is_prefix('foo.bar.baz.quux'))
|
||||||
self.assertFalse('foo.bar' in self.trie)
|
self.assertFalse(self.trie.has_value('foo.bar.baz.quux'))
|
||||||
self.assertTrue('foo.bar.baz' in self.trie)
|
|
||||||
|
|
||||||
# Try to add a second element in a higher space
|
# Try to add a second element in a prefix namespace
|
||||||
self.trie['foo.bar'] = 'blah'
|
self.trie['foo.bar'] = 'blah'
|
||||||
|
|
||||||
self.assertFalse('foo' in self.trie)
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo'))
|
||||||
|
|
||||||
self.assertTrue('foo.bar' in self.trie)
|
self.assertTrue(self.trie.is_prefix('foo.bar'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo.bar'))
|
||||||
self.assertEqual(self.trie['foo.bar'], 'blah')
|
self.assertEqual(self.trie['foo.bar'], 'blah')
|
||||||
|
|
||||||
self.assertTrue('foo.bar.baz' in self.trie)
|
self.assertTrue(self.trie.is_prefix('foo.bar.baz'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo.bar.baz'))
|
||||||
self.assertEqual(self.trie['foo.bar.baz'], 'quux')
|
self.assertEqual(self.trie['foo.bar.baz'], 'quux')
|
||||||
|
|
||||||
|
self.assertFalse(self.trie.is_prefix('foo.bar.baz.quux'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo.bar.baz.quux'))
|
||||||
|
|
||||||
|
|
||||||
def test_add_none_single(self):
|
def test_add_none_single(self):
|
||||||
self.trie['foo'] = None
|
self.trie['foo'] = None
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo'))
|
||||||
self.assertEqual(self.trie['foo'], None)
|
self.assertEqual(self.trie['foo'], None)
|
||||||
self.assertTrue('foo' in self.trie)
|
|
||||||
|
self.assertFalse(self.trie.is_prefix('foo.bar'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo.bar'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_none_multiple(self):
|
def test_add_none_multiple(self):
|
||||||
self.trie['foo.bar'] = None
|
self.trie['foo.bar'] = None
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo'))
|
||||||
|
self.assertFalse(self.trie.has_value('foo'))
|
||||||
|
|
||||||
|
self.assertTrue(self.trie.is_prefix('foo.bar'))
|
||||||
|
self.assertTrue(self.trie.has_value('foo.bar'))
|
||||||
self.assertEqual(self.trie['foo.bar'], None)
|
self.assertEqual(self.trie['foo.bar'], None)
|
||||||
|
|
||||||
self.assertFalse('foo' in self.trie)
|
self.assertFalse(self.trie.is_prefix('foo.bar.baz'))
|
||||||
self.assertFalse('foo.bar.baz' in self.trie)
|
self.assertFalse(self.trie.has_value('foo.bar.baz'))
|
||||||
self.assertTrue('foo.bar' in self.trie)
|
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import spack
|
import spack
|
||||||
import spack.url as url
|
|
||||||
from spack.packages import PackageFinder
|
from spack.packages import PackageFinder
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ def test_get_all_packages(self):
|
||||||
self.check_db()
|
self.check_db()
|
||||||
|
|
||||||
|
|
||||||
def test_get_all_mock_packages(self):
|
def ztest_get_all_mock_packages(self):
|
||||||
"""Get the mock packages once each too."""
|
"""Get the mock packages once each too."""
|
||||||
db = PackageFinder(spack.mock_packages_path)
|
db = PackageFinder(spack.mock_packages_path)
|
||||||
spack.db.swap(db)
|
spack.db.swap(db)
|
||||||
|
@ -53,7 +52,7 @@ def test_get_all_mock_packages(self):
|
||||||
spack.db.swap(db)
|
spack.db.swap(db)
|
||||||
|
|
||||||
|
|
||||||
def test_url_versions(self):
|
def ztest_url_versions(self):
|
||||||
"""Check URLs for regular packages, if they are explicitly defined."""
|
"""Check URLs for regular packages, if they are explicitly defined."""
|
||||||
for pkg in spack.db.all_packages():
|
for pkg in spack.db.all_packages():
|
||||||
for v, vattrs in pkg.versions.items():
|
for v, vattrs in pkg.versions.items():
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
from llnl.util.filesystem import join_path
|
from llnl.util.filesystem import join_path
|
||||||
|
|
||||||
import spack
|
import spack
|
||||||
import spack.packages as packages
|
from spack.packages import Repo
|
||||||
from spack.util.naming import mod_to_class
|
from spack.util.naming import mod_to_class
|
||||||
from spack.test.mock_packages_test import *
|
from spack.test.mock_packages_test import *
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ def test_package_name(self):
|
||||||
|
|
||||||
|
|
||||||
def test_package_filename(self):
|
def test_package_filename(self):
|
||||||
repo = spack.db.get_repo('gov.llnl.spack.mock')
|
repo = Repo(spack.mock_packages_path)
|
||||||
filename = repo.filename_for_package_name('mpich')
|
filename = repo.filename_for_package_name('mpich')
|
||||||
self.assertEqual(filename, join_path(spack.mock_packages_path, 'mpich', 'package.py'))
|
self.assertEqual(filename, join_path(spack.mock_packages_path, 'mpich', 'package.py'))
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def test_package_name(self):
|
||||||
|
|
||||||
|
|
||||||
def test_nonexisting_package_filename(self):
|
def test_nonexisting_package_filename(self):
|
||||||
repo = spack.db.get_repo('gov.llnl.spack.mock')
|
repo = Repo(spack.mock_packages_path)
|
||||||
filename = repo.filename_for_package_name('some-nonexisting-package')
|
filename = repo.filename_for_package_name('some-nonexisting-package')
|
||||||
self.assertEqual(filename, join_path(spack.mock_packages_path, 'some-nonexisting-package', 'package.py'))
|
self.assertEqual(filename, join_path(spack.mock_packages_path, 'some-nonexisting-package', 'package.py'))
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
|
|
||||||
import spack
|
import spack
|
||||||
import spack.url as url
|
import spack.url as url
|
||||||
from spack.packages import PackageDB
|
|
||||||
|
|
||||||
|
|
||||||
class PackageSanityTest(unittest.TestCase):
|
class PackageSanityTest(unittest.TestCase):
|
||||||
|
|
|
@ -134,14 +134,42 @@ def __getitem__(self, namespace):
|
||||||
return self._get_helper(namespace, namespace)
|
return self._get_helper(namespace, namespace)
|
||||||
|
|
||||||
|
|
||||||
def __contains__(self, namespace):
|
def is_prefix(self, namespace):
|
||||||
|
"""True if the namespace has a value, or if it's the prefix of one that does."""
|
||||||
|
first, sep, rest = namespace.partition(self._sep)
|
||||||
|
if not first:
|
||||||
|
return True
|
||||||
|
elif first not in self._subspaces:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self._subspaces[first].is_prefix(rest)
|
||||||
|
|
||||||
|
|
||||||
|
def is_leaf(self, namespace):
|
||||||
|
"""True if this namespace has no children in the trie."""
|
||||||
|
first, sep, rest = namespace.partition(self._sep)
|
||||||
|
if not first:
|
||||||
|
return bool(self._subspaces)
|
||||||
|
elif first not in self._subspaces:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return self._subspaces[first].is_leaf(rest)
|
||||||
|
|
||||||
|
|
||||||
|
def has_value(self, namespace):
|
||||||
|
"""True if there is a value set for the given namespace."""
|
||||||
first, sep, rest = namespace.partition(self._sep)
|
first, sep, rest = namespace.partition(self._sep)
|
||||||
if not first:
|
if not first:
|
||||||
return self._value is not None
|
return self._value is not None
|
||||||
elif first not in self._subspaces:
|
elif first not in self._subspaces:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return rest in self._subspaces[first]
|
return self._subspaces[first].has_value(rest)
|
||||||
|
|
||||||
|
|
||||||
|
def __contains__(self, namespace):
|
||||||
|
"""Returns whether a value has been set for the namespace."""
|
||||||
|
return self.has_value(namespace)
|
||||||
|
|
||||||
|
|
||||||
def _str_helper(self, stream, level=0):
|
def _str_helper(self, stream, level=0):
|
||||||
|
|
Loading…
Reference in a new issue