New, more consistent package directory structure.

- Packages now live in <package_name>/package.py

- spack.packages refactored to use a PackageDB object instead of
  monolithic module.

- Implementation of mock_packages_test.py is greatly simplified

- Added test to exercise install/uninstall code because that wasn't
  covered by existing tests and kept breaking.
This commit is contained in:
Todd Gamblin 2014-03-16 14:51:03 -07:00
parent 74ec74d73c
commit b8b334e86c
64 changed files with 554 additions and 486 deletions

View file

@ -74,9 +74,10 @@ args = parser.parse_args()
spack.verbose = args.verbose spack.verbose = args.verbose
spack.debug = args.debug spack.debug = args.debug
if args.mock: if args.mock:
from spack.util.filesystem import new_path from llnl.util.filesystem import join_path
mock_path = new_path(spack.module_path, 'test', 'mock_packages') from spack.packages import PackageDB
spack.packages_path = mock_path mock_path = join_path(spack.module_path, 'test', 'mock_packages')
spack.db = PackageDB(mock_path)
# If the user asked for it, don't check ssl certs. # If the user asked for it, don't check ssl certs.
if args.insecure: if args.insecure:

View file

@ -34,7 +34,6 @@
import spack import spack
import spack.cmd import spack.cmd
import spack.packages as packages
import spack.util.crypto import spack.util.crypto
from spack.stage import Stage, FailedDownloadError from spack.stage import Stage, FailedDownloadError
from spack.version import * from spack.version import *
@ -81,7 +80,7 @@ def get_checksums(versions, urls, **kwargs):
def checksum(parser, args): def checksum(parser, args):
# get the package we're going to generate checksums for # get the package we're going to generate checksums for
pkg = packages.get(args.package) pkg = spack.db.get(args.package)
# If the user asked for specific versions, use those. # If the user asked for specific versions, use those.
versions = [ver(v) for v in args.versions] versions = [ver(v) for v in args.versions]

View file

@ -26,8 +26,8 @@
import llnl.util.tty as tty import llnl.util.tty as tty
import spack
import spack.cmd import spack.cmd
import spack.packages as packages
import spack.stage as stage import spack.stage as stage
description = "Remove staged files for packages" description = "Remove staged files for packages"
@ -49,7 +49,7 @@ def clean(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = packages.get(spec) package = spack.db.get(spec)
if args.dist: if args.dist:
package.do_clean_dist() package.do_clean_dist()
elif args.work: elif args.work:

View file

@ -33,7 +33,6 @@
import spack import spack
import spack.cmd import spack.cmd
import spack.package import spack.package
import spack.packages as packages
import spack.url import spack.url
import spack.util.crypto as crypto import spack.util.crypto as crypto
import spack.cmd.checksum import spack.cmd.checksum
@ -131,7 +130,7 @@ def create(parser, args):
tty.msg("Couldn't guess a name for this package.") tty.msg("Couldn't guess a name for this package.")
while not name: while not name:
new_name = raw_input("Name: ") new_name = raw_input("Name: ")
if packages.valid_name(name): if spack.db.valid_name(name):
name = new_name name = new_name
else: else:
print "Package name can only contain A-Z, a-z, 0-9, '_' and '-'" print "Package name can only contain A-Z, a-z, 0-9, '_' and '-'"
@ -141,11 +140,11 @@ def create(parser, args):
tty.msg("Creating template for package %s" % name) tty.msg("Creating template for package %s" % name)
pkg_path = packages.filename_for_package_name(name) pkg_path = spack.db.filename_for_package_name(name)
if os.path.exists(pkg_path) and not args.force: if os.path.exists(pkg_path) and not args.force:
tty.die("%s already exists." % pkg_path) tty.die("%s already exists." % pkg_path)
class_name = packages.class_name_for_package_name(name) class_name = spack.db.class_name_for_package_name(name)
versions = list(reversed(spack.package.find_versions_of_archive(url))) versions = list(reversed(spack.package.find_versions_of_archive(url)))
archives_to_fetch = 1 archives_to_fetch = 1

View file

@ -29,7 +29,6 @@
import llnl.util.tty as tty import llnl.util.tty as tty
import spack import spack
import spack.packages as packages
description = "Open package files in $EDITOR" description = "Open package files in $EDITOR"
@ -67,7 +66,7 @@ def edit(parser, args):
if not name: if not name:
path = spack.packages_path path = spack.packages_path
else: else:
path = packages.filename_for_package_name(name) path = spack.db.filename_for_package_name(name)
if os.path.exists(path): if os.path.exists(path):
if not os.path.isfile(path): if not os.path.isfile(path):
@ -78,7 +77,7 @@ def edit(parser, args):
tty.die("No package '%s'. Use spack create, or supply -f/--force " tty.die("No package '%s'. Use spack create, or supply -f/--force "
"to edit a new file." % name) "to edit a new file." % name)
else: else:
class_name = packages.class_name_for_package_name(name) class_name = spack.db.class_name_for_package_name(name)
with closing(open(path, "w")) as pkg_file: with closing(open(path, "w")) as pkg_file:
pkg_file.write( pkg_file.write(

View file

@ -24,8 +24,8 @@
############################################################################## ##############################################################################
import argparse import argparse
import spack
import spack.cmd import spack.cmd
import spack.packages as packages
description = "Fetch archives for packages" description = "Fetch archives for packages"
@ -46,5 +46,5 @@ def fetch(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = packages.get(spec) package = spack.db.get(spec)
package.do_fetch() package.do_fetch()

View file

@ -31,7 +31,7 @@
import spack import spack
import spack.spec import spack.spec
import spack.packages as packages import spack
description ="Find installed spack packages" description ="Find installed spack packages"
@ -83,7 +83,7 @@ def hasher():
# Make a dict with specs keyed by architecture and compiler. # Make a dict with specs keyed by architecture and compiler.
index = hasher() index = hasher()
for spec in packages.installed_package_specs(): for spec in spack.db.installed_package_specs():
if query_specs and not any(spec.satisfies(q) for q in query_specs): if query_specs and not any(spec.satisfies(q) for q in query_specs):
continue continue

View file

@ -23,9 +23,8 @@
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
############################################################################## ##############################################################################
import spack import spack
import spack.packages as packages
description = "Write out inter-package dependencies in dot graph format" description = "Write out inter-package dependencies in dot graph format"
def graph(parser, args): def graph(parser, args):
packages.graph_dependencies() spack.db.graph_dependencies()

View file

@ -26,7 +26,6 @@
import textwrap import textwrap
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
import spack import spack
import spack.packages as packages
description = "Get detailed information on a particular package" description = "Get detailed information on a particular package"
@ -35,7 +34,7 @@ def setup_parser(subparser):
def info(parser, args): def info(parser, args):
package = packages.get(args.name) package = spack.db.get(args.name)
print "Package: ", package.name print "Package: ", package.name
print "Homepage: ", package.homepage print "Homepage: ", package.homepage
print "Download: ", package.url print "Download: ", package.url

View file

@ -26,7 +26,6 @@
import argparse import argparse
import spack import spack
import spack.packages as packages
import spack.cmd import spack.cmd
description = "Build and install packages" description = "Build and install packages"
@ -56,6 +55,6 @@ def install(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = packages.get(spec) package = spack.db.get(spec)
package.dirty = args.dirty package.dirty = args.dirty
package.do_install() package.do_install()

View file

@ -22,8 +22,8 @@
# along with this program; if not, write to the Free Software Foundation, # along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
############################################################################## ##############################################################################
import spack.packages as packages
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
import spack
description ="List available spack packages" description ="List available spack packages"
@ -33,4 +33,4 @@ def setup_parser(subparser):
def list(parser, args): def list(parser, args):
# Print all the package names in columns # Print all the package names in columns
colify(packages.all_package_names()) colify(spack.db.all_package_names())

View file

@ -29,9 +29,8 @@
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import mkdirp, join_path from llnl.util.filesystem import mkdirp, join_path
import spack.packages as packages import spack
import spack.cmd import spack.cmd
from spack.stage import Stage from spack.stage import Stage
@ -46,7 +45,7 @@ def setup_parser(subparser):
def mirror(parser, args): def mirror(parser, args):
if not args.packages: if not args.packages:
args.packages = [p for p in packages.all_package_names()] args.packages = [p for p in spack.db.all_package_names()]
if os.path.isfile(args.directory): if os.path.isfile(args.directory):
tty.error("%s already exists and is a file." % args.directory) tty.error("%s already exists and is a file." % args.directory)
@ -59,7 +58,7 @@ def mirror(parser, args):
# Iterate through packages and download all the safe tarballs for each of them # Iterate through packages and download all the safe tarballs for each of them
for pkg_name in args.packages: for pkg_name in args.packages:
pkg = packages.get(pkg_name) pkg = spack.db.get(pkg_name)
# Skip any package that has no checksummed versions. # Skip any package that has no checksummed versions.
if not pkg.versions: if not pkg.versions:

View file

@ -25,7 +25,7 @@
import argparse import argparse
import spack.cmd import spack.cmd
import spack.packages as packages import spack
description="Patch expanded archive sources in preparation for install" description="Patch expanded archive sources in preparation for install"
@ -47,5 +47,5 @@ def patch(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = packages.get(spec) package = spack.db.get(spec)
package.do_patch() package.do_patch()

View file

@ -27,8 +27,8 @@
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
import spack
import spack.cmd import spack.cmd
import spack.packages
description ="List packages that provide a particular virtual package" description ="List packages that provide a particular virtual package"
@ -39,4 +39,4 @@ def setup_parser(subparser):
def providers(parser, args): def providers(parser, args):
for spec in spack.cmd.parse_specs(args.vpkg_spec): for spec in spack.cmd.parse_specs(args.vpkg_spec):
colify(sorted(spack.packages.providers_for(spec)), indent=4) colify(sorted(spack.db.providers_for(spec)), indent=4)

View file

@ -24,9 +24,8 @@
############################################################################## ##############################################################################
import argparse import argparse
import spack
import spack.cmd import spack.cmd
import spack.packages as packages
description="Expand downloaded archive in preparation for install" description="Expand downloaded archive in preparation for install"
@ -47,5 +46,5 @@ def stage(parser, args):
specs = spack.cmd.parse_specs(args.packages, concretize=True) specs = spack.cmd.parse_specs(args.packages, concretize=True)
for spec in specs: for spec in specs:
package = packages.get(spec) package = spack.db.get(spec)
package.do_stage() package.do_stage()

View file

@ -28,7 +28,6 @@
from llnl.util.lang import list_modules from llnl.util.lang import list_modules
import spack import spack
import spack.packages as packages
import spack.test import spack.test
description ="Run unit tests" description ="Run unit tests"

View file

@ -26,8 +26,8 @@
import llnl.util.tty as tty import llnl.util.tty as tty
import spack
import spack.cmd import spack.cmd
import spack.packages as packages
description="Remove an installed package" description="Remove an installed package"
@ -49,7 +49,7 @@ def uninstall(parser, args):
# Fail and ask user to be unambiguous if it doesn't # Fail and ask user to be unambiguous if it doesn't
pkgs = [] pkgs = []
for spec in specs: for spec in specs:
matching_specs = packages.get_installed(spec) matching_specs = spack.db.get_installed(spec)
if len(matching_specs) > 1: if len(matching_specs) > 1:
tty.die("%s matches multiple packages. Which one did you mean?" tty.die("%s matches multiple packages. Which one did you mean?"
% spec, *matching_specs) % spec, *matching_specs)
@ -58,7 +58,7 @@ def uninstall(parser, args):
tty.die("%s does not match any installed packages." % spec) tty.die("%s does not match any installed packages." % spec)
installed_spec = matching_specs[0] installed_spec = matching_specs[0]
pkgs.append(packages.get(installed_spec)) pkgs.append(spack.db.get(installed_spec))
# Sort packages to be uninstalled by the number of installed dependents # Sort packages to be uninstalled by the number of installed dependents
# This ensures we do things in the right order # This ensures we do things in the right order

View file

@ -24,7 +24,7 @@
############################################################################## ##############################################################################
import os import os
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
import spack.packages as packages import spack
description ="List available versions of a package" description ="List available versions of a package"
@ -33,5 +33,5 @@ def setup_parser(subparser):
def versions(parser, args): def versions(parser, args):
pkg = packages.get(args.package) pkg = spack.db.get(args.package)
colify(reversed(pkg.fetch_available_versions())) colify(reversed(pkg.fetch_available_versions()))

View file

@ -35,7 +35,6 @@
""" """
import spack.architecture import spack.architecture
import spack.compilers import spack.compilers
import spack.packages
import spack.spec import spack.spec
from spack.version import * from spack.version import *

View file

@ -30,6 +30,7 @@
from spack.util.executable import * from spack.util.executable import *
from spack.directory_layout import SpecHashDirectoryLayout from spack.directory_layout import SpecHashDirectoryLayout
from spack.concretize import DefaultConcretizer from spack.concretize import DefaultConcretizer
from spack.packages import PackageDB
# This lives in $prefix/lib/spac/spack/__file__ # This lives in $prefix/lib/spac/spack/__file__
prefix = ancestor(__file__, 4) prefix = ancestor(__file__, 4)
@ -41,7 +42,6 @@
lib_path = join_path(prefix, "lib", "spack") lib_path = join_path(prefix, "lib", "spack")
env_path = join_path(lib_path, "env") env_path = join_path(lib_path, "env")
module_path = join_path(lib_path, "spack") module_path = join_path(lib_path, "spack")
packages_path = join_path(module_path, "packages")
compilers_path = join_path(module_path, "compilers") compilers_path = join_path(module_path, "compilers")
test_path = join_path(module_path, "test") test_path = join_path(module_path, "test")
@ -50,6 +50,12 @@
install_path = join_path(prefix, "opt") install_path = join_path(prefix, "opt")
#
# Set up the packages database.
#
db = PackageDB(join_path(module_path, "packages"))
# #
# This controls how spack lays out install prefixes and # This controls how spack lays out install prefixes and
# stage directories. # stage directories.

View file

@ -44,19 +44,19 @@
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.tty.color import cwrite from llnl.util.tty.color import cwrite
from llnl.util.filesystem import touch from llnl.util.filesystem import *
from llnl.util.lang import * from llnl.util.lang import *
from spack import * import spack
import spack.spec import spack.spec
import spack.error import spack.error
import spack.packages as packages
import spack.url as url import spack.url as url
import spack.util.crypto as crypto import spack.util.crypto as crypto
from spack.version import * from spack.version import *
from spack.stage import Stage from spack.stage import Stage
from spack.util.web import get_pages from spack.util.web import get_pages
from spack.util.environment import * from spack.util.environment import *
from spack.util.executable import Executable, which
from spack.util.compression import allowed_archive from spack.util.compression import allowed_archive
"""Allowed URL schemes for spack packages.""" """Allowed URL schemes for spack packages."""
@ -480,7 +480,7 @@ def preorder_traversal(self, visited=None, **kwargs):
yield spec yield spec
continue continue
for pkg in packages.get(name).preorder_traversal(visited, **kwargs): for pkg in spack.db.get(name).preorder_traversal(visited, **kwargs):
yield pkg yield pkg
@ -539,7 +539,7 @@ def installed_dependents(self):
"""Return a list of the specs of all installed packages that depend """Return a list of the specs of all installed packages that depend
on this one.""" on this one."""
dependents = [] dependents = []
for spec in packages.installed_package_specs(): for spec in spack.db.installed_package_specs():
if self.name in spec.dependencies: if self.name in spec.dependencies:
dep_spec = spec.dependencies[self.name] dep_spec = spec.dependencies[self.name]
if self.spec == dep_spec: if self.spec == dep_spec:
@ -594,7 +594,7 @@ def do_fetch(self):
self.stage.fetch() self.stage.fetch()
if self.version in self.versions: if spack.do_checksum and self.version in self.versions:
digest = self.versions[self.version] digest = self.versions[self.version]
checker = crypto.Checker(digest) checker = crypto.Checker(digest)
if checker.check(self.stage.archive_file): if checker.check(self.stage.archive_file):
@ -720,28 +720,28 @@ def setup_install_environment(self):
# Add spack environment at front of path and pass the # Add spack environment at front of path and pass the
# lib location along so the compiler script can find spack # lib location along so the compiler script can find spack
os.environ[SPACK_LIB] = lib_path os.environ[spack.SPACK_LIB] = spack.lib_path
# Fix for case-insensitive file systems. Conflicting links are # Fix for case-insensitive file systems. Conflicting links are
# in directories called "case*" within the env directory. # in directories called "case*" within the env directory.
env_paths = [env_path] env_paths = [spack.env_path]
for file in os.listdir(env_path): for file in os.listdir(spack.env_path):
path = join_path(env_path, file) path = join_path(spack.env_path, file)
if file.startswith("case") and os.path.isdir(path): if file.startswith("case") and os.path.isdir(path):
env_paths.append(path) env_paths.append(path)
path_put_first("PATH", env_paths) path_put_first("PATH", env_paths)
path_set(SPACK_ENV_PATH, env_paths) path_set(spack.SPACK_ENV_PATH, env_paths)
# Pass along prefixes of dependencies here # Pass along prefixes of dependencies here
path_set( path_set(
SPACK_DEPENDENCIES, spack.SPACK_DEPENDENCIES,
[dep.package.prefix for dep in self.spec.dependencies.values()]) [dep.package.prefix for dep in self.spec.dependencies.values()])
# Install location # Install location
os.environ[SPACK_PREFIX] = self.prefix os.environ[spack.SPACK_PREFIX] = self.prefix
# Build root for logging. # Build root for logging.
os.environ[SPACK_BUILD_ROOT] = self.stage.expanded_archive_path os.environ[spack.SPACK_BUILD_ROOT] = self.stage.expanded_archive_path
def do_install_dependencies(self): def do_install_dependencies(self):
@ -887,7 +887,7 @@ def __init__(self, name, parallel):
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
parallel = kwargs.get('parallel', self.parallel) parallel = kwargs.get('parallel', self.parallel)
disable_parallel = env_flag(SPACK_NO_PARALLEL_MAKE) disable_parallel = env_flag(spack.SPACK_NO_PARALLEL_MAKE)
if parallel and not disable_parallel: if parallel and not disable_parallel:
jobs = "-j%d" % multiprocessing.cpu_count() jobs = "-j%d" % multiprocessing.cpu_count()

View file

@ -28,14 +28,22 @@
import string import string
import inspect import inspect
import glob import glob
import imp
import llnl.util.tty as tty import llnl.util.tty as tty
from llnl.util.filesystem import join_path from llnl.util.filesystem import join_path
from llnl.util.lang import list_modules from llnl.util.lang import memoized
import spack import spack
import spack.error import spack.error
import spack.spec import spack.spec
from spack.virtual import ProviderIndex
# Name of module under which packages are imported
_imported_packages_module = 'spack.packages'
# Name of the package file inside a package directory
_package_file_name = 'package.py'
# Valid package names can contain '-' but can't start with it. # Valid package names can contain '-' but can't start with it.
valid_package_re = r'^\w[\w-]*$' valid_package_re = r'^\w[\w-]*$'
@ -43,222 +51,19 @@
# Don't allow consecutive [_-] in package names # Don't allow consecutive [_-] in package names
invalid_package_re = r'[_-][_-]+' invalid_package_re = r'[_-][_-]+'
instances = {}
def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
function to a Spec."""
def converter(arg):
if not isinstance(arg, spack.spec.Spec):
arg = spack.spec.Spec(arg)
return function(arg)
return converter
class ProviderIndex(object):
"""This is a dict of dicts used for finding providers of particular
virtual dependencies. The dict of dicts looks like:
{ vpkg name :
{ full vpkg spec : package providing spec } }
Callers can use this to first find which packages provide a vpkg,
then find a matching full spec. e.g., in this scenario:
{ 'mpi' :
{ mpi@:1.1 : mpich,
mpi@:2.3 : mpich2@1.9: } }
Calling providers_for(spec) will find specs that provide a
matching implementation of MPI.
"""
def __init__(self, specs, **kwargs):
# TODO: come up with another name for this. This "restricts" values to
# the verbatim impu specs (i.e., it doesn't pre-apply package's constraints, and
# keeps things as broad as possible, so it's really the wrong name)
self.restrict = kwargs.setdefault('restrict', False)
self.providers = {}
for spec in specs:
if not isinstance(spec, spack.spec.Spec):
spec = spack.spec.Spec(spec)
if spec.virtual:
continue
self.update(spec)
def update(self, spec):
if type(spec) != spack.spec.Spec:
spec = spack.spec.Spec(spec)
assert(not spec.virtual)
pkg = spec.package
for provided_spec, provider_spec in pkg.provided.iteritems():
if provider_spec.satisfies(spec, deps=False):
provided_name = provided_spec.name
if provided_name not in self.providers:
self.providers[provided_name] = {}
if self.restrict:
self.providers[provided_name][provided_spec] = spec
else:
# Before putting the spec in the map, constrain it so that
# it provides what was asked for.
constrained = spec.copy()
constrained.constrain(provider_spec)
self.providers[provided_name][provided_spec] = constrained
def providers_for(self, *vpkg_specs):
"""Gives specs of all packages that provide virtual packages
with the supplied specs."""
providers = set()
for vspec in vpkg_specs:
# Allow string names to be passed as input, as well as specs
if type(vspec) == str:
vspec = spack.spec.Spec(vspec)
# Add all the providers that satisfy the vpkg spec.
if vspec.name in self.providers:
for provider_spec, spec in self.providers[vspec.name].items():
if provider_spec.satisfies(vspec, deps=False):
providers.add(spec)
# Return providers in order
return sorted(providers)
# TODO: this is pretty darned nasty, and inefficient.
def _cross_provider_maps(self, lmap, rmap):
result = {}
for lspec in lmap:
for rspec in rmap:
try:
constrained = lspec.copy().constrain(rspec)
if lmap[lspec].name != rmap[rspec].name:
continue
result[constrained] = lmap[lspec].copy().constrain(
rmap[rspec], deps=False)
except spack.spec.UnsatisfiableSpecError:
continue
return result
def __contains__(self, name):
"""Whether a particular vpkg name is in the index."""
return name in self.providers
def satisfies(self, other):
"""Check that providers of virtual specs are compatible."""
common = set(self.providers) & set(other.providers)
if not common:
return True
result = {}
for name in common:
crossed = self._cross_provider_maps(self.providers[name],
other.providers[name])
if crossed:
result[name] = crossed
return bool(result)
@_autospec
def get(spec):
if spec.virtual:
raise UnknownPackageError(spec.name)
if not spec in instances:
package_class = get_class_for_package_name(spec.name)
instances[spec.name] = package_class(spec)
return instances[spec.name]
@_autospec
def get_installed(spec):
return [s for s in installed_package_specs() if s.satisfies(spec)]
@_autospec
def providers_for(vpkg_spec):
if not hasattr(providers_for, 'index'):
providers_for.index = ProviderIndex(all_package_names())
providers = providers_for.index.providers_for(vpkg_spec)
if not providers:
raise UnknownPackageError("No such virtual package: %s" % vpkg_spec)
return providers
def valid_package_name(pkg_name): def valid_package_name(pkg_name):
"""Return whether the pkg_name is valid for use in Spack."""
return (re.match(valid_package_re, pkg_name) and return (re.match(valid_package_re, pkg_name) and
not re.search(invalid_package_re, pkg_name)) not re.search(invalid_package_re, pkg_name))
def validate_package_name(pkg_name): def validate_package_name(pkg_name):
"""Raise an exception if pkg_name is not valid."""
if not valid_package_name(pkg_name): if not valid_package_name(pkg_name):
raise InvalidPackageNameError(pkg_name) raise InvalidPackageNameError(pkg_name)
def dirname_for_package_name(pkg_name):
"""Get the directory name for a particular package would use, even if it's a
foo.py package and not a directory with a foo/__init__.py file."""
return join_path(spack.packages_path, pkg_name)
def filename_for_package_name(pkg_name):
"""Get the filename for the module we should load for a particular package.
The package can be either in a standalone .py file, or it can be in
a directory with an __init__.py file.
Package "foo" in standalone .py file:
packages/foo.py
Package "foo" in directory:
packages/foo/__init__.py
The second form is used when there are files (like patches) that need
to be stored along with the package.
If the package doesn't exist yet, this will just return the name
of the standalone .py file.
"""
validate_package_name(pkg_name)
pkg_dir = dirname_for_package_name(pkg_name)
if os.path.isdir(pkg_dir):
init_file = join_path(pkg_dir, '__init__.py')
return init_file
else:
pkg_file = "%s.py" % pkg_dir
return pkg_file
def installed_package_specs():
return spack.install_layout.all_specs()
def all_package_names():
"""Generator function for all packages."""
for module in list_modules(spack.packages_path):
yield module
def all_packages():
for name in all_package_names():
yield get(name)
def class_name_for_package_name(pkg_name): def class_name_for_package_name(pkg_name):
"""Get a name for the class the package file should contain. Note that """Get a name for the class the package file should contain. Note that
conflicts don't matter because the classes are in different modules. conflicts don't matter because the classes are in different modules.
@ -277,40 +82,136 @@ def class_name_for_package_name(pkg_name):
return class_name return class_name
def exists(pkg_name): def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
function to a Spec."""
def converter(self, spec_like):
if not isinstance(spec_like, spack.spec.Spec):
spec_like = spack.spec.Spec(spec_like)
return function(self, spec_like)
return converter
class PackageDB(object):
def __init__(self, root):
"""Construct a new package database from a root directory."""
self.root = root
self.instances = {}
self.provider_index = None
@_autospec
def get(self, spec):
if spec.virtual:
raise UnknownPackageError(spec.name)
if not spec in self.instances:
package_class = self.get_class_for_package_name(spec.name)
self.instances[spec.name] = package_class(spec)
return self.instances[spec.name]
@_autospec
def get_installed(self, spec):
return [s for s in self.installed_package_specs() if s.satisfies(spec)]
@_autospec
def providers_for(self, vpkg_spec):
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("No such virtual package: %s" % vpkg_spec)
return providers
def dirname_for_package_name(self, pkg_name):
"""Get the directory name for a particular package. This is the
directory that contains its package.py file."""
return join_path(self.root, pkg_name)
def filename_for_package_name(self, pkg_name):
"""Get the filename for the module we should load for a particular
package. Packages for a pacakge DB live in
``$root/<package_name>/package.py``
This will return a proper package.py path even if the
package doesn't exist yet, so callers will need to ensure
the package exists before importing.
"""
validate_package_name(pkg_name)
pkg_dir = self.dirname_for_package_name(pkg_name)
return join_path(pkg_dir, _package_file_name)
def installed_package_specs(self):
"""Read installed package names straight from the install directory
layout.
"""
return spack.install_layout.all_specs()
@memoized
def all_package_names(self):
"""Generator function for all packages. This looks for
``<pkg_name>/package.py`` files within the root direcotry"""
all_package_names = []
for pkg_name in os.listdir(self.root):
pkg_dir = join_path(self.root, pkg_name)
pkg_file = join_path(pkg_dir, _package_file_name)
if os.path.isfile(pkg_file):
all_package_names.append(pkg_name)
all_package_names.sort()
return all_package_names
def all_packages(self):
for name in self.all_package_names():
yield get(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(filename_for_package_name(pkg_name)) return os.path.exists(self.filename_for_package_name(pkg_name))
def packages_module(): @memoized
# TODO: replace this with a proper package DB class, instead of this hackiness. def get_class_for_package_name(self, pkg_name):
packages_path = re.sub(spack.module_path + '\/+', 'spack.', spack.packages_path) """Get an instance of the class for a particular package.
packages_module = re.sub(r'/', '.', packages_path)
return packages_module
This method uses Python's ``imp`` package to load python
source from a Spack package's ``package.py`` file. A
normal python import would only load each package once, but
because we do this dynamically, the method needs to be
memoized to ensure there is only ONE package class
instance, per package, per database.
"""
file_path = self.filename_for_package_name(pkg_name)
def get_class_for_package_name(pkg_name): if os.path.exists(file_path):
file_name = filename_for_package_name(pkg_name) if not os.path.isfile(file_path):
tty.die("Something's wrong. '%s' is not a file!" % file_path)
if os.path.exists(file_name): if not os.access(file_path, os.R_OK):
if not os.path.isfile(file_name): tty.die("Cannot read '%s'!" % file_path)
tty.die("Something's wrong. '%s' is not a file!" % file_name)
if not os.access(file_name, os.R_OK):
tty.die("Cannot read '%s'!" % file_name)
else: else:
raise UnknownPackageError(pkg_name) raise UnknownPackageError(pkg_name)
# Figure out pacakges module from spack.packages_path # Figure out pacakges module based on self.root
# This allows us to change the module path. if not re.match(r'%s' % spack.module_path, self.root):
if not re.match(r'%s' % spack.module_path, spack.packages_path):
raise RuntimeError("Packages path is not a submodule of spack.") raise RuntimeError("Packages path is not a submodule of spack.")
class_name = class_name_for_package_name(pkg_name) class_name = class_name_for_package_name(pkg_name)
try: try:
module_name = "%s.%s" % (packages_module(), pkg_name) module_name = _imported_packages_module + '.' + pkg_name
module = __import__(module_name, fromlist=[class_name]) module = imp.load_source(module_name, file_path)
except ImportError, e: except ImportError, e:
tty.die("Error while importing %s.%s:\n%s" % (pkg_name, class_name, e.message)) tty.die("Error while importing %s from %s:\n%s" % (
pkg_name, file_path, e.message))
cls = getattr(module, class_name) cls = getattr(module, class_name)
if not inspect.isclass(cls): if not inspect.isclass(cls):
@ -319,7 +220,7 @@ def get_class_for_package_name(pkg_name):
return cls return cls
def compute_dependents(): def compute_dependents(self):
"""Reads in all package files and sets dependence information on """Reads in all package files and sets dependence information on
Package objects in memory. Package objects in memory.
""" """
@ -337,7 +238,7 @@ def compute_dependents():
dpkg._dependents.append(pkg.name) dpkg._dependents.append(pkg.name)
def graph_dependencies(out=sys.stdout): def graph_dependencies(self, out=sys.stdout):
"""Print out a graph of all the dependencies between package. """Print out a graph of all the dependencies between package.
Graph is in dot format.""" Graph is in dot format."""
out.write('digraph G {\n') out.write('digraph G {\n')

View file

@ -30,7 +30,6 @@
import spack import spack
import spack.stage import spack.stage
import spack.error import spack.error
import spack.packages as packages
from spack.util.executable import which from spack.util.executable import which
@ -55,7 +54,7 @@ def __init__(self, pkg_name, 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 = packages.dirname_for_package_name(pkg_name) pkg_dir = spack.db.dirname_for_package_name(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)

View file

@ -80,7 +80,6 @@ class Mpileaks(Package):
from spack.patch import Patch from spack.patch import Patch
from spack.spec import Spec, parse_anonymous_spec from spack.spec import Spec, parse_anonymous_spec
from spack.packages import packages_module
"""Adds a dependencies local variable in the locals of """Adds a dependencies local variable in the locals of

View file

@ -99,16 +99,16 @@
from llnl.util.lang import * from llnl.util.lang import *
from llnl.util.tty.color import * from llnl.util.tty.color import *
import spack
import spack.parse import spack.parse
import spack.error import spack.error
import spack.compilers import spack.compilers
import spack.compilers.gcc import spack.compilers.gcc
import spack.packages as packages
from spack.version import * from spack.version import *
from spack.util.string import * from spack.util.string import *
from spack.util.prefix import Prefix from spack.util.prefix import Prefix
from spack.virtual import ProviderIndex
# Convenient names for color formats so that other things can use them # Convenient names for color formats so that other things can use them
compiler_color = '@g' compiler_color = '@g'
@ -379,7 +379,7 @@ def root(self):
@property @property
def package(self): def package(self):
return packages.get(self) return spack.db.get(self)
@property @property
@ -391,7 +391,7 @@ def virtual(self):
Possible idea: just use conventin and make virtual deps all Possible idea: just use conventin and make virtual deps all
caps, e.g., MPI vs mpi. caps, e.g., MPI vs mpi.
""" """
return not packages.exists(self.name) return not spack.db.exists(self.name)
@property @property
@ -532,7 +532,7 @@ def _expand_virtual_packages(self):
return return
for spec in virtuals: for spec in virtuals:
providers = packages.providers_for(spec) providers = spack.db.providers_for(spec)
concrete = spack.concretizer.choose_provider(spec, providers) concrete = spack.concretizer.choose_provider(spec, providers)
concrete = concrete.copy() concrete = concrete.copy()
spec._replace_with(concrete) spec._replace_with(concrete)
@ -624,7 +624,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
# Combine constraints from package dependencies with # Combine constraints from package dependencies with
# constraints on the spec's dependencies. # constraints on the spec's dependencies.
pkg = packages.get(self.name) pkg = spack.db.get(self.name)
for name, pkg_dep in self.package.dependencies.items(): for name, pkg_dep in self.package.dependencies.items():
# If it's a virtual dependency, try to find a provider # If it's a virtual dependency, try to find a provider
if pkg_dep.virtual: if pkg_dep.virtual:
@ -653,7 +653,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
else: else:
# if it's a real dependency, check whether it provides something # if it's a real dependency, check whether it provides something
# already required in the spec. # already required in the spec.
index = packages.ProviderIndex([pkg_dep], restrict=True) index = ProviderIndex([pkg_dep], restrict=True)
for vspec in (v for v in spec_deps.values() if v.virtual): for vspec in (v for v in spec_deps.values() if v.virtual):
if index.providers_for(vspec): if index.providers_for(vspec):
vspec._replace_with(pkg_dep) vspec._replace_with(pkg_dep)
@ -718,7 +718,7 @@ def normalize(self):
# Remove virtual deps that are already provided by something in the spec # Remove virtual deps that are already provided by something in the spec
spec_packages = [d.package for d in spec_deps.values() if not d.virtual] spec_packages = [d.package for d in spec_deps.values() if not d.virtual]
index = packages.ProviderIndex(spec_deps.values(), restrict=True) index = ProviderIndex(spec_deps.values(), restrict=True)
visited = set() visited = set()
self._normalize_helper(visited, spec_deps, index) self._normalize_helper(visited, spec_deps, index)
@ -754,7 +754,7 @@ def validate_names(self):
for spec in self.preorder_traversal(): for spec in self.preorder_traversal():
# Don't get a package for a virtual name. # Don't get a package for a virtual name.
if not spec.virtual: if not spec.virtual:
packages.get(spec.name) spack.db.get(spec.name)
# validate compiler name in addition to the package name. # validate compiler name in addition to the package name.
if spec.compiler: if spec.compiler:
@ -888,10 +888,8 @@ def satisfies_dependencies(self, other):
return False return False
# For virtual dependencies, we need to dig a little deeper. # For virtual dependencies, we need to dig a little deeper.
self_index = packages.ProviderIndex( self_index = ProviderIndex(self.preorder_traversal(), restrict=True)
self.preorder_traversal(), restrict=True) other_index = ProviderIndex(other.preorder_traversal(), restrict=True)
other_index = packages.ProviderIndex(
other.preorder_traversal(), restrict=True)
# This handles cases where there are already providers for both vpkgs # This handles cases where there are already providers for both vpkgs
if not self_index.satisfies(other_index): if not self_index.satisfies(other_index):

View file

@ -30,6 +30,8 @@
import spack import spack
import spack.test.install
"""Names of tests to be included in Spack's test suite""" """Names of tests to be included in Spack's test suite"""
test_names = ['versions', test_names = ['versions',
@ -40,7 +42,8 @@
'spec_semantics', 'spec_semantics',
'spec_dag', 'spec_dag',
'concretize', 'concretize',
'multimethod'] 'multimethod',
'install']
def list_tests(): def list_tests():

View file

@ -24,7 +24,7 @@
############################################################################## ##############################################################################
import unittest import unittest
import spack.packages as packages import spack
from spack.spec import Spec from spack.spec import Spec
from spack.test.mock_packages_test import * from spack.test.mock_packages_test import *
@ -113,22 +113,22 @@ def test_concretize_with_provides_when(self):
we ask for some advanced version. we ask for some advanced version.
""" """
self.assertTrue(not any(spec.satisfies('mpich2@:1.0') self.assertTrue(not any(spec.satisfies('mpich2@:1.0')
for spec in packages.providers_for('mpi@2.1'))) for spec in spack.db.providers_for('mpi@2.1')))
self.assertTrue(not any(spec.satisfies('mpich2@:1.1') self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
for spec in packages.providers_for('mpi@2.2'))) for spec in spack.db.providers_for('mpi@2.2')))
self.assertTrue(not any(spec.satisfies('mpich2@:1.1') self.assertTrue(not any(spec.satisfies('mpich2@:1.1')
for spec in packages.providers_for('mpi@2.2'))) for spec in spack.db.providers_for('mpi@2.2')))
self.assertTrue(not any(spec.satisfies('mpich@:1') self.assertTrue(not any(spec.satisfies('mpich@:1')
for spec in packages.providers_for('mpi@2'))) for spec in spack.db.providers_for('mpi@2')))
self.assertTrue(not any(spec.satisfies('mpich@:1') self.assertTrue(not any(spec.satisfies('mpich@:1')
for spec in packages.providers_for('mpi@3'))) for spec in spack.db.providers_for('mpi@3')))
self.assertTrue(not any(spec.satisfies('mpich2') self.assertTrue(not any(spec.satisfies('mpich2')
for spec in packages.providers_for('mpi@3'))) for spec in spack.db.providers_for('mpi@3')))
def test_virtual_is_fully_expanded_for_callpath(self): def test_virtual_is_fully_expanded_for_callpath(self):
@ -162,8 +162,4 @@ def test_virtual_is_fully_expanded_for_mpileaks(self):
def test_my_dep_depends_on_provider_of_my_virtual_dep(self): def test_my_dep_depends_on_provider_of_my_virtual_dep(self):
spec = Spec('indirect_mpich') spec = Spec('indirect_mpich')
spec.normalize() spec.normalize()
print
print spec.tree(color=True)
spec.concretize() spec.concretize()

View file

@ -0,0 +1,98 @@
##############################################################################
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://scalability-llnl.github.io/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License (as published by
# the Free Software Foundation) version 2.1 dated February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import unittest
import shutil
from contextlib import closing
from llnl.util.filesystem import *
import spack
from spack.stage import Stage
from spack.util.executable import which
from spack.test.mock_packages_test import *
dir_name = 'trivial-1.0'
archive_name = 'trivial-1.0.tar.gz'
install_test_package = 'trivial_install_test_package'
class InstallTest(MockPackagesTest):
"""Tests install and uninstall on a trivial package."""
def setUp(self):
super(InstallTest, self).setUp()
self.stage = Stage('not_a_real_url')
archive_dir = join_path(self.stage.path, dir_name)
dummy_configure = join_path(archive_dir, 'configure')
mkdirp(archive_dir)
with closing(open(dummy_configure, 'w')) as configure:
configure.write(
"#!/bin/sh\n"
"prefix=$(echo $1 | sed 's/--prefix=//')\n"
"cat > Makefile <<EOF\n"
"all:\n"
"\techo Building...\n\n"
"install:\n"
"\tmkdir -p $prefix\n"
"\ttouch $prefix/dummy_file\n"
"EOF\n")
os.chmod(dummy_configure, 0755)
with working_dir(self.stage.path):
tar = which('tar')
tar('-czf', archive_name, dir_name)
# We use a fake pacakge, so skip the checksum.
spack.do_checksum = False
def tearDown(self):
super(InstallTest, self).tearDown()
if self.stage is not None:
self.stage.destroy()
# Turn checksumming back on
spack.do_checksum = True
def test_install_and_uninstall(self):
# Get a basic concrete spec for the trivial install package.
spec = Spec(install_test_package)
spec.concretize()
# Get the package
pkg = spack.db.get(spec)
# Fake some values
archive_path = join_path(self.stage.path, archive_name)
pkg.url = 'file://' + archive_path
try:
pkg.do_install()
pkg.do_uninstall()
except:
if pkg: pkg.remove_prefix()
raise

View file

@ -1,24 +0,0 @@
##############################################################################
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://scalability-llnl.github.io/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License (as published by
# the Free Software Foundation) version 2.1 dated February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################

View file

@ -24,16 +24,15 @@
############################################################################## ##############################################################################
from spack import * from spack import *
class DirectoryPkg(Package): class TrivialInstallTestPackage(Package):
"""This is a fake package that tests spack's ability to load packages in """This package is a stub with a trivial install method. It allows us
directories with __init__.py files. to test the install and uninstall logic of spack."""
""" homepage = "http://www.example.com/trivial_install"
homepage = "http://www.example.com" url = "http://www.unit-test-should-replace-this-url/trivial_install-1.0.tar.gz"
url = "http://www.example.com/directory-pkg-1.0.tar.gz"
versions = { '1.0' : '0123456789abcdef0123456789abcdef' } versions = { '1.0' : 'foobarbaz' }
this_is_a_directory_pkg = True def install(self, spec, prefix):
configure('--prefix=%s' % prefix)
def install(self): make()
pass make('install')

View file

@ -28,54 +28,31 @@
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 PackageDB
from spack.spec import Spec from spack.spec import Spec
mock_packages_path = join_path(spack.module_path, 'test', 'mock_packages') mock_packages_path = join_path(spack.module_path, 'test', 'mock_packages')
original_deps = None
def set_pkg_dep(pkg, spec): def set_pkg_dep(pkg, spec):
"""Alters dependence information for a pacakge. """Alters dependence information for a pacakge.
Use this to mock up constraints. Use this to mock up constraints.
""" """
spec = Spec(spec) spec = Spec(spec)
packages.get(pkg).dependencies[spec.name] = spec spack.db.get(pkg).dependencies[spec.name] = spec
def restore_dependencies():
# each time through restore original dependencies & constraints
global original_deps
for pkg_name, deps in original_deps.iteritems():
packages.get(pkg_name).dependencies.clear()
for dep in deps:
set_pkg_dep(pkg_name, dep)
class MockPackagesTest(unittest.TestCase): class MockPackagesTest(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUp(self):
# Use a different packages directory for these tests. We want to use # Use the mock packages database for these tests. This allows
# mocked up packages that don't interfere with the real ones. # us to set up contrived packages that don't interfere with
cls.real_packages_path = spack.packages_path # real ones.
spack.packages_path = mock_packages_path self.real_db = spack.db
spack.db = PackageDB(mock_packages_path)
# First time through, record original relationships bt/w packages
global original_deps
original_deps = {}
for name in list_modules(mock_packages_path):
pkg = packages.get(name)
original_deps[name] = [
spec for spec in pkg.dependencies.values()]
@classmethod @classmethod
def tearDownClass(cls): def tearDown(self):
"""Restore the real packages path after any test.""" """Restore the real packages path after any test."""
restore_dependencies() #restore_dependencies()
spack.packages_path = cls.real_packages_path spack.db = self.real_db
def setUp(self):
"""Before each test, restore deps between packages to original state."""
restore_dependencies()

View file

@ -27,7 +27,7 @@
""" """
import unittest import unittest
import spack.packages as packages import spack
from spack.multimethod import * from spack.multimethod import *
from spack.version import * from spack.version import *
from spack.spec import Spec from spack.spec import Spec
@ -38,37 +38,37 @@
class MultiMethodTest(MockPackagesTest): class MultiMethodTest(MockPackagesTest):
def test_no_version_match(self): def test_no_version_match(self):
pkg = packages.get('multimethod@2.0') pkg = spack.db.get('multimethod@2.0')
self.assertRaises(NoSuchMethodError, pkg.no_version_2) self.assertRaises(NoSuchMethodError, pkg.no_version_2)
def test_one_version_match(self): def test_one_version_match(self):
pkg = packages.get('multimethod@1.0') pkg = spack.db.get('multimethod@1.0')
self.assertEqual(pkg.no_version_2(), 1) self.assertEqual(pkg.no_version_2(), 1)
pkg = packages.get('multimethod@3.0') pkg = spack.db.get('multimethod@3.0')
self.assertEqual(pkg.no_version_2(), 3) self.assertEqual(pkg.no_version_2(), 3)
pkg = packages.get('multimethod@4.0') pkg = spack.db.get('multimethod@4.0')
self.assertEqual(pkg.no_version_2(), 4) self.assertEqual(pkg.no_version_2(), 4)
def test_version_overlap(self): def test_version_overlap(self):
pkg = packages.get('multimethod@2.0') pkg = spack.db.get('multimethod@2.0')
self.assertEqual(pkg.version_overlap(), 1) self.assertEqual(pkg.version_overlap(), 1)
pkg = packages.get('multimethod@5.0') pkg = spack.db.get('multimethod@5.0')
self.assertEqual(pkg.version_overlap(), 2) self.assertEqual(pkg.version_overlap(), 2)
def test_mpi_version(self): def test_mpi_version(self):
pkg = packages.get('multimethod^mpich@3.0.4') pkg = spack.db.get('multimethod^mpich@3.0.4')
self.assertEqual(pkg.mpi_version(), 3) self.assertEqual(pkg.mpi_version(), 3)
pkg = packages.get('multimethod^mpich2@1.2') pkg = spack.db.get('multimethod^mpich2@1.2')
self.assertEqual(pkg.mpi_version(), 2) self.assertEqual(pkg.mpi_version(), 2)
pkg = packages.get('multimethod^mpich@1.0') pkg = spack.db.get('multimethod^mpich@1.0')
self.assertEqual(pkg.mpi_version(), 1) self.assertEqual(pkg.mpi_version(), 1)
@ -76,54 +76,54 @@ def test_undefined_mpi_version(self):
# This currently fails because provides() doesn't do # This currently fails because provides() doesn't do
# the right thing undefined version ranges. # the right thing undefined version ranges.
# TODO: fix this. # TODO: fix this.
pkg = packages.get('multimethod^mpich@0.4') pkg = spack.db.get('multimethod^mpich@0.4')
self.assertEqual(pkg.mpi_version(), 0) self.assertEqual(pkg.mpi_version(), 0)
def test_default_works(self): def test_default_works(self):
pkg = packages.get('multimethod%gcc') pkg = spack.db.get('multimethod%gcc')
self.assertEqual(pkg.has_a_default(), 'gcc') self.assertEqual(pkg.has_a_default(), 'gcc')
pkg = packages.get('multimethod%intel') pkg = spack.db.get('multimethod%intel')
self.assertEqual(pkg.has_a_default(), 'intel') self.assertEqual(pkg.has_a_default(), 'intel')
pkg = packages.get('multimethod%pgi') pkg = spack.db.get('multimethod%pgi')
self.assertEqual(pkg.has_a_default(), 'default') self.assertEqual(pkg.has_a_default(), 'default')
def test_architecture_match(self): def test_architecture_match(self):
pkg = packages.get('multimethod=x86_64') pkg = spack.db.get('multimethod=x86_64')
self.assertEqual(pkg.different_by_architecture(), 'x86_64') self.assertEqual(pkg.different_by_architecture(), 'x86_64')
pkg = packages.get('multimethod=ppc64') pkg = spack.db.get('multimethod=ppc64')
self.assertEqual(pkg.different_by_architecture(), 'ppc64') self.assertEqual(pkg.different_by_architecture(), 'ppc64')
pkg = packages.get('multimethod=ppc32') pkg = spack.db.get('multimethod=ppc32')
self.assertEqual(pkg.different_by_architecture(), 'ppc32') self.assertEqual(pkg.different_by_architecture(), 'ppc32')
pkg = packages.get('multimethod=arm64') pkg = spack.db.get('multimethod=arm64')
self.assertEqual(pkg.different_by_architecture(), 'arm64') self.assertEqual(pkg.different_by_architecture(), 'arm64')
pkg = packages.get('multimethod=macos') pkg = spack.db.get('multimethod=macos')
self.assertRaises(NoSuchMethodError, pkg.different_by_architecture) self.assertRaises(NoSuchMethodError, pkg.different_by_architecture)
def test_dependency_match(self): def test_dependency_match(self):
pkg = packages.get('multimethod^zmpi') pkg = spack.db.get('multimethod^zmpi')
self.assertEqual(pkg.different_by_dep(), 'zmpi') self.assertEqual(pkg.different_by_dep(), 'zmpi')
pkg = packages.get('multimethod^mpich') pkg = spack.db.get('multimethod^mpich')
self.assertEqual(pkg.different_by_dep(), 'mpich') self.assertEqual(pkg.different_by_dep(), 'mpich')
# If we try to switch on some entirely different dep, it's ambiguous, # If we try to switch on some entirely different dep, it's ambiguous,
# but should take the first option # but should take the first option
pkg = packages.get('multimethod^foobar') pkg = spack.db.get('multimethod^foobar')
self.assertEqual(pkg.different_by_dep(), 'mpich') self.assertEqual(pkg.different_by_dep(), 'mpich')
def test_virtual_dep_match(self): def test_virtual_dep_match(self):
pkg = packages.get('multimethod^mpich2') pkg = spack.db.get('multimethod^mpich2')
self.assertEqual(pkg.different_by_virtual_dep(), 2) self.assertEqual(pkg.different_by_virtual_dep(), 2)
pkg = packages.get('multimethod^mpich@1.0') pkg = spack.db.get('multimethod^mpich@1.0')
self.assertEqual(pkg.different_by_virtual_dep(), 1) self.assertEqual(pkg.different_by_virtual_dep(), 1)

View file

@ -25,45 +25,29 @@
import unittest import unittest
from spack.test.mock_packages_test import * from spack.test.mock_packages_test import *
import spack.packages as packages import spack
class PackagesTest(MockPackagesTest): class PackagesTest(MockPackagesTest):
def test_load_regular_package(self): def test_load_package(self):
pkg = packages.get('mpich') pkg = spack.db.get('mpich')
def test_regular_package_name(self): def test_package_name(self):
pkg = packages.get('mpich') pkg = spack.db.get('mpich')
self.assertEqual(pkg.name, 'mpich') self.assertEqual(pkg.name, 'mpich')
def test_regular_package_filename(self): def test_package_filename(self):
filename = packages.filename_for_package_name('mpich') filename = spack.db.filename_for_package_name('mpich')
self.assertEqual(filename, join_path(mock_packages_path, 'mpich.py')) self.assertEqual(filename, join_path(mock_packages_path, 'mpich', 'package.py'))
def test_regular_package_name(self): def test_package_name(self):
pkg = packages.get('mpich') pkg = spack.db.get('mpich')
self.assertEqual(pkg.name, 'mpich') self.assertEqual(pkg.name, 'mpich')
def test_load_directory_package(self):
pkg = packages.get('directory-pkg')
self.assertTrue(hasattr(pkg, 'this_is_a_directory_pkg'))
self.assertTrue(pkg.this_is_a_directory_pkg)
def test_directory_package_name(self):
pkg = packages.get('directory-pkg')
self.assertEqual(pkg.name, 'directory-pkg')
def test_directory_package_filename(self):
filename = packages.filename_for_package_name('directory-pkg')
self.assertEqual(filename, join_path(mock_packages_path, 'directory-pkg/__init__.py'))
def test_nonexisting_package_filename(self): def test_nonexisting_package_filename(self):
filename = packages.filename_for_package_name('some-nonexisting-package') filename = spack.db.filename_for_package_name('some-nonexisting-package')
self.assertEqual(filename, join_path(mock_packages_path, 'some-nonexisting-package.py')) self.assertEqual(filename, join_path(mock_packages_path, 'some-nonexisting-package', 'package.py'))

View file

@ -30,7 +30,6 @@
""" """
import spack import spack
import spack.package import spack.package
import spack.packages as packages
from llnl.util.lang import list_modules from llnl.util.lang import list_modules

142
lib/spack/spack/virtual.py Normal file
View file

@ -0,0 +1,142 @@
##############################################################################
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://scalability-llnl.github.io/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License (as published by
# the Free Software Foundation) version 2.1 dated February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
"""
The ``virtual`` module contains utility classes for virtual dependencies.
"""
import spack.spec
class ProviderIndex(object):
"""This is a dict of dicts used for finding providers of particular
virtual dependencies. The dict of dicts looks like:
{ vpkg name :
{ full vpkg spec : package providing spec } }
Callers can use this to first find which packages provide a vpkg,
then find a matching full spec. e.g., in this scenario:
{ 'mpi' :
{ mpi@:1.1 : mpich,
mpi@:2.3 : mpich2@1.9: } }
Calling providers_for(spec) will find specs that provide a
matching implementation of MPI.
"""
def __init__(self, specs, **kwargs):
# TODO: come up with another name for this. This "restricts" values to
# the verbatim impu specs (i.e., it doesn't pre-apply package's constraints, and
# keeps things as broad as possible, so it's really the wrong name)
self.restrict = kwargs.setdefault('restrict', False)
self.providers = {}
for spec in specs:
if not isinstance(spec, spack.spec.Spec):
spec = spack.spec.Spec(spec)
if spec.virtual:
continue
self.update(spec)
def update(self, spec):
if type(spec) != spack.spec.Spec:
spec = spack.spec.Spec(spec)
assert(not spec.virtual)
pkg = spec.package
for provided_spec, provider_spec in pkg.provided.iteritems():
if provider_spec.satisfies(spec, deps=False):
provided_name = provided_spec.name
if provided_name not in self.providers:
self.providers[provided_name] = {}
if self.restrict:
self.providers[provided_name][provided_spec] = spec
else:
# Before putting the spec in the map, constrain it so that
# it provides what was asked for.
constrained = spec.copy()
constrained.constrain(provider_spec)
self.providers[provided_name][provided_spec] = constrained
def providers_for(self, *vpkg_specs):
"""Gives specs of all packages that provide virtual packages
with the supplied specs."""
providers = set()
for vspec in vpkg_specs:
# Allow string names to be passed as input, as well as specs
if type(vspec) == str:
vspec = spack.spec.Spec(vspec)
# Add all the providers that satisfy the vpkg spec.
if vspec.name in self.providers:
for provider_spec, spec in self.providers[vspec.name].items():
if provider_spec.satisfies(vspec, deps=False):
providers.add(spec)
# Return providers in order
return sorted(providers)
# TODO: this is pretty darned nasty, and inefficient.
def _cross_provider_maps(self, lmap, rmap):
result = {}
for lspec in lmap:
for rspec in rmap:
try:
constrained = lspec.copy().constrain(rspec)
if lmap[lspec].name != rmap[rspec].name:
continue
result[constrained] = lmap[lspec].copy().constrain(
rmap[rspec], deps=False)
except spack.spec.UnsatisfiableSpecError:
continue
return result
def __contains__(self, name):
"""Whether a particular vpkg name is in the index."""
return name in self.providers
def satisfies(self, other):
"""Check that providers of virtual specs are compatible."""
common = set(self.providers) & set(other.providers)
if not common:
return True
result = {}
for name in common:
crossed = self._cross_provider_maps(self.providers[name],
other.providers[name])
if crossed:
result[name] = crossed
return bool(result)