Merge remote-tracking branch 'origin/features/variants' into features/optional-deps
This commit is contained in:
@@ -22,7 +22,12 @@
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
|
||||
<br/>
|
||||
Written by Todd Gamblin (<a href="mailto:tgamblin@llnl.gov">tgamblin@llnl.gov</a>) and
|
||||
many contributors. LLNL-CODE-647188.
|
||||
|
||||
{%- if last_updated %}
|
||||
<br/>
|
||||
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
</p>
|
||||
@@ -33,4 +38,3 @@
|
||||
{%- endif %}
|
||||
|
||||
</footer>
|
||||
|
||||
|
@@ -94,7 +94,7 @@
|
||||
|
||||
# General information about the project.
|
||||
project = u'Spack'
|
||||
copyright = u'2013-2014, Lawrence Livermore National Laboratory'
|
||||
copyright = u'2013-2015, Lawrence Livermore National Laboratory.'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -203,7 +203,7 @@
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
#html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
@@ -126,22 +126,20 @@ def caller_locals():
|
||||
del stack
|
||||
|
||||
|
||||
def get_calling_package_name():
|
||||
def get_calling_module_name():
|
||||
"""Make sure that the caller is a class definition, and return the
|
||||
module's name.
|
||||
enclosing module's name.
|
||||
"""
|
||||
stack = inspect.stack()
|
||||
try:
|
||||
# get calling function name (the relation)
|
||||
relation = stack[1][3]
|
||||
|
||||
# Make sure locals contain __module__
|
||||
caller_locals = stack[2][0].f_locals
|
||||
finally:
|
||||
del stack
|
||||
|
||||
if not '__module__' in caller_locals:
|
||||
raise ScopeError(relation)
|
||||
raise RuntimeError("Must invoke get_calling_module_name() "
|
||||
"from inside a class definition!")
|
||||
|
||||
module_name = caller_locals['__module__']
|
||||
base_name = module_name.split('.')[-1]
|
||||
@@ -322,6 +320,24 @@ def match(string):
|
||||
return match
|
||||
|
||||
|
||||
|
||||
def DictWrapper(dictionary):
|
||||
"""Returns a class that wraps a dictionary and enables it to be used
|
||||
like an object."""
|
||||
class wrapper(object):
|
||||
def __getattr__(self, name): return dictionary[name]
|
||||
def __setattr__(self, name, value): dictionary[name] = value
|
||||
def setdefault(self, *args): return dictionary.setdefault(*args)
|
||||
def get(self, *args): return dictionary.get(*args)
|
||||
def keys(self): return dictionary.keys()
|
||||
def values(self): return dictionary.values()
|
||||
def items(self): return dictionary.items()
|
||||
def __iter__(self): return iter(dictionary)
|
||||
|
||||
|
||||
return wrapper()
|
||||
|
||||
|
||||
class RequiredAttributeError(ValueError):
|
||||
def __init__(self, message):
|
||||
super(RequiredAttributeError, self).__init__(message)
|
||||
|
@@ -146,9 +146,9 @@
|
||||
from llnl.util.filesystem import *
|
||||
__all__ += llnl.util.filesystem.__all__
|
||||
|
||||
import spack.relations
|
||||
from spack.relations import *
|
||||
__all__ += spack.relations.__all__
|
||||
import spack.directives
|
||||
from spack.directives import *
|
||||
__all__ += spack.directives.__all__
|
||||
|
||||
import spack.util.executable
|
||||
from spack.util.executable import *
|
||||
|
@@ -22,12 +22,22 @@
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
##############################################################################
|
||||
import textwrap
|
||||
from llnl.util.tty.colify import *
|
||||
import spack
|
||||
import spack.fetch_strategy as fs
|
||||
|
||||
description = "Get detailed information on a particular package"
|
||||
|
||||
def padder(str_list, extra=0):
|
||||
"""Return a function to pad elements of a list."""
|
||||
length = max(len(str(s)) for s in str_list) + extra
|
||||
def pad(string):
|
||||
string = str(string)
|
||||
padding = max(0, length - len(string))
|
||||
return string + (padding * ' ')
|
||||
return pad
|
||||
|
||||
|
||||
def setup_parser(subparser):
|
||||
subparser.add_argument('name', metavar="PACKAGE", help="Name of package to get info for.")
|
||||
@@ -42,13 +52,24 @@ def print_text_info(pkg):
|
||||
print "Safe versions: "
|
||||
|
||||
if not pkg.versions:
|
||||
print("None.")
|
||||
print("None")
|
||||
else:
|
||||
maxlen = max(len(str(v)) for v in pkg.versions)
|
||||
fmt = "%%-%ss" % maxlen
|
||||
pad = padder(pkg.versions, 4)
|
||||
for v in reversed(sorted(pkg.versions)):
|
||||
f = fs.for_package_version(pkg, v)
|
||||
print " " + (fmt % v) + " " + str(f)
|
||||
print " %s%s" % (pad(v), str(f))
|
||||
|
||||
print
|
||||
print "Variants:"
|
||||
if not pkg.variants:
|
||||
print "None"
|
||||
else:
|
||||
pad = padder(pkg.variants, 4)
|
||||
for name in sorted(pkg.variants):
|
||||
v = pkg.variants[name]
|
||||
print " %s%s" % (
|
||||
pad(('+' if v.default else '-') + name + ':'),
|
||||
"\n".join(textwrap.wrap(v.description)))
|
||||
|
||||
print
|
||||
print "Dependencies:"
|
||||
|
@@ -101,6 +101,16 @@ def concretize_architecture(self, spec):
|
||||
spec.architecture = spack.architecture.sys_type()
|
||||
|
||||
|
||||
def concretize_variants(self, spec):
|
||||
"""If the spec already has variants filled in, return. Otherwise, add
|
||||
the default variants from the package specification.
|
||||
"""
|
||||
for name, variant in spec.package.variants.items():
|
||||
if name not in spec.variants:
|
||||
spec.variants[name] = spack.spec.VariantSpec(
|
||||
name, variant.default)
|
||||
|
||||
|
||||
def concretize_compiler(self, spec):
|
||||
"""If the spec already has a compiler, we're done. If not, then take
|
||||
the compiler used for the nearest ancestor with a compiler
|
||||
|
270
lib/spack/spack/directives.py
Normal file
270
lib/spack/spack/directives.py
Normal file
@@ -0,0 +1,270 @@
|
||||
##############################################################################
|
||||
# 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
|
||||
##############################################################################
|
||||
"""This package contains directives that can be used within a package.
|
||||
|
||||
Directives are functions that can be called inside a package
|
||||
definition to modify the package, for example:
|
||||
|
||||
class OpenMpi(Package):
|
||||
depends_on("hwloc")
|
||||
provides("mpi")
|
||||
...
|
||||
|
||||
``provides`` and ``depends_on`` are spack directives.
|
||||
|
||||
The available directives are:
|
||||
|
||||
* ``version``
|
||||
* ``depends_on``
|
||||
* ``provides``
|
||||
* ``extends``
|
||||
* ``patch``
|
||||
* ``variant``
|
||||
|
||||
"""
|
||||
__all__ = [ 'depends_on', 'extends', 'provides', 'patch', 'version',
|
||||
'variant' ]
|
||||
|
||||
import re
|
||||
import inspect
|
||||
|
||||
from llnl.util.lang import *
|
||||
|
||||
import spack
|
||||
import spack.spec
|
||||
import spack.error
|
||||
import spack.url
|
||||
from spack.version import Version
|
||||
from spack.patch import Patch
|
||||
from spack.variant import Variant
|
||||
from spack.spec import Spec, parse_anonymous_spec
|
||||
|
||||
|
||||
#
|
||||
# This is a list of all directives, built up as they are defined in
|
||||
# this file.
|
||||
#
|
||||
directives = {}
|
||||
|
||||
|
||||
def ensure_dicts(pkg):
|
||||
"""Ensure that a package has all the dicts required by directives."""
|
||||
for name, d in directives.items():
|
||||
d.ensure_dicts(pkg)
|
||||
|
||||
|
||||
class directive(object):
|
||||
"""Decorator for Spack directives.
|
||||
|
||||
Spack directives allow you to modify a package while it is being
|
||||
defined, e.g. to add version or depenency information. Directives
|
||||
are one of the key pieces of Spack's package "langauge", which is
|
||||
embedded in python.
|
||||
|
||||
Here's an example directive:
|
||||
|
||||
@directive(dicts='versions')
|
||||
version(pkg, ...):
|
||||
...
|
||||
|
||||
This directive allows you write:
|
||||
|
||||
class Foo(Package):
|
||||
version(...)
|
||||
|
||||
The ``@directive`` decorator handles a couple things for you:
|
||||
|
||||
1. Adds the class scope (pkg) as an initial parameter when
|
||||
called, like a class method would. This allows you to modify
|
||||
a package from within a directive, while the package is still
|
||||
being defined.
|
||||
|
||||
2. It automatically adds a dictionary called "versions" to the
|
||||
package so that you can refer to pkg.versions.
|
||||
|
||||
The ``(dicts='versions')`` part ensures that ALL packages in Spack
|
||||
will have a ``versions`` attribute after they're constructed, and
|
||||
that if no directive actually modified it, it will just be an
|
||||
empty dict.
|
||||
|
||||
This is just a modular way to add storage attributes to the
|
||||
Package class, and it's how Spack gets information from the
|
||||
packages to the core.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# dict argument allows directives to have storage on the package.
|
||||
dicts = kwargs.get('dicts', None)
|
||||
|
||||
if isinstance(dicts, basestring):
|
||||
dicts = (dicts,)
|
||||
elif type(dicts) not in (list, tuple):
|
||||
raise TypeError(
|
||||
"dicts arg must be list, tuple, or string. Found %s."
|
||||
% type(dicts))
|
||||
|
||||
self.dicts = dicts
|
||||
|
||||
|
||||
def ensure_dicts(self, pkg):
|
||||
"""Ensure that a package has the dicts required by this directive."""
|
||||
for d in self.dicts:
|
||||
if not hasattr(pkg, d):
|
||||
setattr(pkg, d, {})
|
||||
|
||||
attr = getattr(pkg, d)
|
||||
if not isinstance(attr, dict):
|
||||
raise spack.error.SpackError(
|
||||
"Package %s has non-dict %s attribute!" % (pkg, d))
|
||||
|
||||
|
||||
def __call__(self, directive_function):
|
||||
directives[directive_function.__name__] = self
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
pkg = DictWrapper(caller_locals())
|
||||
self.ensure_dicts(pkg)
|
||||
|
||||
pkg.name = get_calling_module_name()
|
||||
return directive_function(pkg, *args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
@directive(dicts='versions')
|
||||
def version(pkg, ver, checksum=None, **kwargs):
|
||||
"""Adds a version and metadata describing how to fetch it.
|
||||
Metadata is just stored as a dict in the package's versions
|
||||
dictionary. Package must turn it into a valid fetch strategy
|
||||
later.
|
||||
"""
|
||||
# special case checksum for backward compatibility
|
||||
if checksum:
|
||||
kwargs['md5'] = checksum
|
||||
|
||||
# Store kwargs for the package to later with a fetch_strategy.
|
||||
pkg.versions[Version(ver)] = kwargs
|
||||
|
||||
|
||||
@directive(dicts='dependencies')
|
||||
def depends_on(pkg, *specs):
|
||||
"""Adds a dependencies local variable in the locals of
|
||||
the calling class, based on args. """
|
||||
for string in specs:
|
||||
for spec in spack.spec.parse(string):
|
||||
if pkg.name == spec.name:
|
||||
raise CircularReferenceError('depends_on', pkg.name)
|
||||
pkg.dependencies[spec.name] = spec
|
||||
|
||||
|
||||
@directive(dicts=('extendees', 'dependencies'))
|
||||
def extends(pkg, spec, **kwargs):
|
||||
"""Same as depends_on, but dependency is symlinked into parent prefix.
|
||||
|
||||
This is for Python and other language modules where the module
|
||||
needs to be installed into the prefix of the Python installation.
|
||||
Spack handles this by installing modules into their own prefix,
|
||||
but allowing ONE module version to be symlinked into a parent
|
||||
Python install at a time.
|
||||
|
||||
keyword arguments can be passed to extends() so that extension
|
||||
packages can pass parameters to the extendee's extension
|
||||
mechanism.
|
||||
|
||||
"""
|
||||
if pkg.extendees:
|
||||
raise DirectiveError("Packages can extend at most one other package.")
|
||||
|
||||
spec = Spec(spec)
|
||||
if pkg.name == spec.name:
|
||||
raise CircularReferenceError('extends', pkg.name)
|
||||
pkg.dependencies[spec.name] = spec
|
||||
pkg.extendees[spec.name] = (spec, kwargs)
|
||||
|
||||
|
||||
@directive(dicts='provided')
|
||||
def provides(pkg, *specs, **kwargs):
|
||||
"""Allows packages to provide a virtual dependency. If a package provides
|
||||
'mpi', other packages can declare that they depend on "mpi", and spack
|
||||
can use the providing package to satisfy the dependency.
|
||||
"""
|
||||
spec_string = kwargs.get('when', pkg.name)
|
||||
provider_spec = parse_anonymous_spec(spec_string, pkg.name)
|
||||
|
||||
for string in specs:
|
||||
for provided_spec in spack.spec.parse(string):
|
||||
if pkg.name == provided_spec.name:
|
||||
raise CircularReferenceError('depends_on', pkg.name)
|
||||
pkg.provided[provided_spec] = provider_spec
|
||||
|
||||
|
||||
@directive(dicts='patches')
|
||||
def patch(pkg, url_or_filename, **kwargs):
|
||||
"""Packages can declare patches to apply to source. You can
|
||||
optionally provide a when spec to indicate that a particular
|
||||
patch should only be applied when the package's spec meets
|
||||
certain conditions (e.g. a particular version).
|
||||
"""
|
||||
level = kwargs.get('level', 1)
|
||||
when = kwargs.get('when', pkg.name)
|
||||
|
||||
when_spec = parse_anonymous_spec(when, pkg.name)
|
||||
if when_spec not in pkg.patches:
|
||||
pkg.patches[when_spec] = [Patch(pkg.name, url_or_filename, level)]
|
||||
else:
|
||||
# if this spec is identical to some other, then append this
|
||||
# patch to the existing list.
|
||||
pkg.patches[when_spec].append(Patch(pkg.name, url_or_filename, level))
|
||||
|
||||
|
||||
@directive(dicts='variants')
|
||||
def variant(pkg, name, **kwargs):
|
||||
"""Define a variant for the package. Packager can specify a default
|
||||
value (on or off) as well as a text description."""
|
||||
|
||||
default = bool(kwargs.get('default', False))
|
||||
description = str(kwargs.get('description', "")).strip()
|
||||
|
||||
if not re.match(spack.spec.identifier_re, name):
|
||||
raise DirectiveError("Invalid variant name in %s: '%s'" % (pkg.name, name))
|
||||
|
||||
pkg.variants[name] = Variant(default, description)
|
||||
|
||||
|
||||
class DirectiveError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a package directive."""
|
||||
def __init__(self, directive, message):
|
||||
super(DirectiveError, self).__init__(message)
|
||||
self.directive = directive
|
||||
|
||||
|
||||
class CircularReferenceError(DirectiveError):
|
||||
"""This is raised when something depends on itself."""
|
||||
def __init__(self, directive, package):
|
||||
super(CircularReferenceError, self).__init__(
|
||||
directive,
|
||||
"Package '%s' cannot pass itself to %s." % (package, directive))
|
||||
self.package = package
|
@@ -195,7 +195,7 @@ def install(self, prefix):
|
||||
"""
|
||||
class when(object):
|
||||
def __init__(self, spec):
|
||||
pkg = get_calling_package_name()
|
||||
pkg = get_calling_module_name()
|
||||
self.spec = parse_anonymous_spec(spec, pkg)
|
||||
|
||||
def __call__(self, method):
|
||||
|
@@ -55,6 +55,7 @@
|
||||
import spack.compilers
|
||||
import spack.mirror
|
||||
import spack.hooks
|
||||
import spack.directives
|
||||
import spack.build_environment as build_env
|
||||
import spack.url as url
|
||||
import spack.fetch_strategy as fs
|
||||
@@ -301,32 +302,6 @@ class SomePackage(Package):
|
||||
clean() (some of them do this), and others to provide custom behavior.
|
||||
|
||||
"""
|
||||
|
||||
#
|
||||
# These variables are defaults for the various "relations".
|
||||
#
|
||||
"""Map of information about Versions of this package.
|
||||
Map goes: Version -> dict of attributes"""
|
||||
versions = {}
|
||||
|
||||
"""Specs of dependency packages, keyed by name."""
|
||||
dependencies = {}
|
||||
|
||||
"""Specs of virtual packages provided by this package, keyed by name."""
|
||||
provided = {}
|
||||
|
||||
"""Specs of conflicting packages, keyed by name. """
|
||||
conflicted = {}
|
||||
|
||||
"""Patches to apply to newly expanded source, if any."""
|
||||
patches = {}
|
||||
|
||||
"""Specs of package this one extends, or None.
|
||||
|
||||
Currently, ppackages can extend at most one other package.
|
||||
"""
|
||||
extendees = {}
|
||||
|
||||
#
|
||||
# These are default values for instance variables.
|
||||
#
|
||||
@@ -350,20 +325,8 @@ def __init__(self, spec):
|
||||
if '.' in self.name:
|
||||
self.name = self.name[self.name.rindex('.') + 1:]
|
||||
|
||||
# Sanity check some required variables that could be
|
||||
# overridden by package authors.
|
||||
def ensure_has_dict(attr_name):
|
||||
if not hasattr(self, attr_name):
|
||||
raise PackageError("Package %s must define %s" % attr_name)
|
||||
|
||||
attr = getattr(self, attr_name)
|
||||
if not isinstance(attr, dict):
|
||||
raise PackageError("Package %s has non-dict %s attribute!"
|
||||
% (self.name, attr_name))
|
||||
ensure_has_dict('versions')
|
||||
ensure_has_dict('dependencies')
|
||||
ensure_has_dict('conflicted')
|
||||
ensure_has_dict('patches')
|
||||
# Sanity check attributes required by Spack directives.
|
||||
spack.directives.ensure_dicts(type(self))
|
||||
|
||||
# Check versions in the versions dict.
|
||||
for v in self.versions:
|
||||
|
@@ -1,215 +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
|
||||
##############################################################################
|
||||
"""
|
||||
This package contains relationships that can be defined among packages.
|
||||
Relations are functions that can be called inside a package definition,
|
||||
for example:
|
||||
|
||||
class OpenMPI(Package):
|
||||
depends_on("hwloc")
|
||||
provides("mpi")
|
||||
...
|
||||
|
||||
The available relations are:
|
||||
|
||||
depends_on
|
||||
Above, the OpenMPI package declares that it "depends on" hwloc. This means
|
||||
that the hwloc package needs to be installed before OpenMPI can be
|
||||
installed. When a user runs 'spack install openmpi', spack will fetch
|
||||
hwloc and install it first.
|
||||
|
||||
provides
|
||||
This is useful when more than one package can satisfy a dependence. Above,
|
||||
OpenMPI declares that it "provides" mpi. Other implementations of the MPI
|
||||
interface, like mvapich and mpich, also provide mpi, e.g.:
|
||||
|
||||
class Mvapich(Package):
|
||||
provides("mpi")
|
||||
...
|
||||
|
||||
class Mpich(Package):
|
||||
provides("mpi")
|
||||
...
|
||||
|
||||
Instead of depending on openmpi, mvapich, or mpich, another package can
|
||||
declare that it depends on "mpi":
|
||||
|
||||
class Mpileaks(Package):
|
||||
depends_on("mpi")
|
||||
...
|
||||
|
||||
Now the user can pick which MPI they would like to build with when they
|
||||
install mpileaks. For example, the user could install 3 instances of
|
||||
mpileaks, one for each MPI version, by issuing these three commands:
|
||||
|
||||
spack install mpileaks ^openmpi
|
||||
spack install mpileaks ^mvapich
|
||||
spack install mpileaks ^mpich
|
||||
"""
|
||||
__all__ = [ 'depends_on', 'extends', 'provides', 'patch', 'version' ]
|
||||
|
||||
import re
|
||||
import inspect
|
||||
|
||||
from llnl.util.lang import *
|
||||
|
||||
import spack
|
||||
import spack.spec
|
||||
import spack.error
|
||||
import spack.url
|
||||
from spack.version import Version
|
||||
from spack.patch import Patch
|
||||
from spack.spec import Spec, parse_anonymous_spec
|
||||
|
||||
|
||||
|
||||
def version(ver, checksum=None, **kwargs):
|
||||
"""Adds a version and metadata describing how to fetch it.
|
||||
Metadata is just stored as a dict in the package's versions
|
||||
dictionary. Package must turn it into a valid fetch strategy
|
||||
later.
|
||||
"""
|
||||
pkg = caller_locals()
|
||||
versions = pkg.setdefault('versions', {})
|
||||
|
||||
# special case checksum for backward compatibility
|
||||
if checksum:
|
||||
kwargs['md5'] = checksum
|
||||
|
||||
# Store the kwargs for the package to use later when constructing
|
||||
# a fetch strategy.
|
||||
versions[Version(ver)] = kwargs
|
||||
|
||||
|
||||
def depends_on(*specs):
|
||||
"""Adds a dependencies local variable in the locals of
|
||||
the calling class, based on args. """
|
||||
pkg = get_calling_package_name()
|
||||
clocals = caller_locals()
|
||||
dependencies = clocals.setdefault('dependencies', {})
|
||||
|
||||
for string in specs:
|
||||
for spec in spack.spec.parse(string):
|
||||
if pkg == spec.name:
|
||||
raise CircularReferenceError('depends_on', pkg)
|
||||
dependencies[spec.name] = spec
|
||||
|
||||
|
||||
def extends(spec, **kwargs):
|
||||
"""Same as depends_on, but dependency is symlinked into parent prefix.
|
||||
|
||||
This is for Python and other language modules where the module
|
||||
needs to be installed into the prefix of the Python installation.
|
||||
Spack handles this by installing modules into their own prefix,
|
||||
but allowing ONE module version to be symlinked into a parent
|
||||
Python install at a time.
|
||||
|
||||
keyword arguments can be passed to extends() so that extension
|
||||
packages can pass parameters to the extendee's extension
|
||||
mechanism.
|
||||
|
||||
"""
|
||||
pkg = get_calling_package_name()
|
||||
clocals = caller_locals()
|
||||
dependencies = clocals.setdefault('dependencies', {})
|
||||
extendees = clocals.setdefault('extendees', {})
|
||||
if extendees:
|
||||
raise RelationError("Packages can extend at most one other package.")
|
||||
|
||||
spec = Spec(spec)
|
||||
if pkg == spec.name:
|
||||
raise CircularReferenceError('extends', pkg)
|
||||
dependencies[spec.name] = spec
|
||||
extendees[spec.name] = (spec, kwargs)
|
||||
|
||||
|
||||
def provides(*specs, **kwargs):
|
||||
"""Allows packages to provide a virtual dependency. If a package provides
|
||||
'mpi', other packages can declare that they depend on "mpi", and spack
|
||||
can use the providing package to satisfy the dependency.
|
||||
"""
|
||||
pkg = get_calling_package_name()
|
||||
spec_string = kwargs.get('when', pkg)
|
||||
provider_spec = parse_anonymous_spec(spec_string, pkg)
|
||||
|
||||
provided = caller_locals().setdefault("provided", {})
|
||||
for string in specs:
|
||||
for provided_spec in spack.spec.parse(string):
|
||||
if pkg == provided_spec.name:
|
||||
raise CircularReferenceError('depends_on', pkg)
|
||||
provided[provided_spec] = provider_spec
|
||||
|
||||
|
||||
def patch(url_or_filename, **kwargs):
|
||||
"""Packages can declare patches to apply to source. You can
|
||||
optionally provide a when spec to indicate that a particular
|
||||
patch should only be applied when the package's spec meets
|
||||
certain conditions (e.g. a particular version).
|
||||
"""
|
||||
pkg = get_calling_package_name()
|
||||
level = kwargs.get('level', 1)
|
||||
when_spec = parse_anonymous_spec(kwargs.get('when', pkg), pkg)
|
||||
|
||||
patches = caller_locals().setdefault('patches', {})
|
||||
if when_spec not in patches:
|
||||
patches[when_spec] = [Patch(pkg, url_or_filename, level)]
|
||||
else:
|
||||
# if this spec is identical to some other, then append this
|
||||
# patch to the existing list.
|
||||
patches[when_spec].append(Patch(pkg, url_or_filename, level))
|
||||
|
||||
|
||||
def conflicts(*specs):
|
||||
"""Packages can declare conflicts with other packages.
|
||||
This can be as specific as you like: use regular spec syntax.
|
||||
|
||||
NOT YET IMPLEMENTED.
|
||||
"""
|
||||
# TODO: implement conflicts
|
||||
pass
|
||||
|
||||
|
||||
class RelationError(spack.error.SpackError):
|
||||
"""This is raised when something is wrong with a package relation."""
|
||||
def __init__(self, relation, message):
|
||||
super(RelationError, self).__init__(message)
|
||||
self.relation = relation
|
||||
|
||||
|
||||
class ScopeError(RelationError):
|
||||
"""This is raised when a relation is called from outside a spack package."""
|
||||
def __init__(self, relation):
|
||||
super(ScopeError, self).__init__(
|
||||
relation,
|
||||
"Must invoke '%s' from inside a class definition!" % relation)
|
||||
|
||||
|
||||
class CircularReferenceError(RelationError):
|
||||
"""This is raised when something depends on itself."""
|
||||
def __init__(self, relation, package):
|
||||
super(CircularReferenceError, self).__init__(
|
||||
relation,
|
||||
"Package '%s' cannot pass itself to %s." % (package, relation))
|
||||
self.package = package
|
@@ -110,6 +110,9 @@
|
||||
from spack.util.prefix import Prefix
|
||||
from spack.virtual import ProviderIndex
|
||||
|
||||
# Valid pattern for an identifier in Spack
|
||||
identifier_re = r'\w[\w-]*'
|
||||
|
||||
# Convenient names for color formats so that other things can use them
|
||||
compiler_color = '@g'
|
||||
version_color = '@c'
|
||||
@@ -267,7 +270,7 @@ def __repr__(self):
|
||||
|
||||
|
||||
@key_ordering
|
||||
class Variant(object):
|
||||
class VariantSpec(object):
|
||||
"""Variants are named, build-time options for a package. Names depend
|
||||
on the particular package being built, and each named variant can
|
||||
be enabled or disabled.
|
||||
@@ -282,7 +285,7 @@ def _cmp_key(self):
|
||||
|
||||
|
||||
def copy(self):
|
||||
return Variant(self.name, self.enabled)
|
||||
return VariantSpec(self.name, self.enabled)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -291,9 +294,44 @@ def __str__(self):
|
||||
|
||||
|
||||
class VariantMap(HashableMap):
|
||||
def __init__(self, spec):
|
||||
super(VariantMap, self).__init__()
|
||||
self.spec = spec
|
||||
|
||||
|
||||
def satisfies(self, other):
|
||||
return all(self[key].enabled == other[key].enabled
|
||||
for key in other if key in self)
|
||||
if self.spec._concrete:
|
||||
return all(k in self and self[k].enabled == other[k].enabled
|
||||
for k in other)
|
||||
else:
|
||||
return all(self[k].enabled == other[k].enabled
|
||||
for k in other if k in self)
|
||||
|
||||
|
||||
def constrain(self, other):
|
||||
if other.spec._concrete:
|
||||
for k in self:
|
||||
if k not in other:
|
||||
raise UnsatisfiableVariantSpecError(self[k], '<absent>')
|
||||
|
||||
for k in other:
|
||||
if k in self:
|
||||
if self[k].enabled != other[k].enabled:
|
||||
raise UnsatisfiableVariantSpecError(self[k], other[k])
|
||||
else:
|
||||
self[k] = other[k].copy()
|
||||
|
||||
@property
|
||||
def concrete(self):
|
||||
return self.spec._concrete or all(
|
||||
v in self for v in self.spec.package.variants)
|
||||
|
||||
|
||||
def copy(self):
|
||||
clone = VariantMap(None)
|
||||
for name, variant in self.items():
|
||||
clone[name] = variant.copy()
|
||||
return clone
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -340,10 +378,11 @@ def __init__(self, spec_like, *dep_like, **kwargs):
|
||||
self.name = other.name
|
||||
self.dependents = other.dependents
|
||||
self.versions = other.versions
|
||||
self.variants = other.variants
|
||||
self.architecture = other.architecture
|
||||
self.compiler = other.compiler
|
||||
self.dependencies = other.dependencies
|
||||
self.variants = other.variants
|
||||
self.variants.spec = self
|
||||
|
||||
# Specs are by default not assumed to be normal, but in some
|
||||
# cases we've read them from a file want to assume normal.
|
||||
@@ -372,7 +411,7 @@ def _add_variant(self, name, enabled):
|
||||
"""Called by the parser to add a variant."""
|
||||
if name in self.variants: raise DuplicateVariantError(
|
||||
"Cannot specify variant '%s' twice" % name)
|
||||
self.variants[name] = Variant(name, enabled)
|
||||
self.variants[name] = VariantSpec(name, enabled)
|
||||
|
||||
|
||||
def _set_compiler(self, compiler):
|
||||
@@ -436,14 +475,15 @@ def virtual(self):
|
||||
@property
|
||||
def concrete(self):
|
||||
"""A spec is concrete if it can describe only ONE build of a package.
|
||||
If any of the name, version, architecture, compiler, or depdenencies
|
||||
are ambiguous,then it is not concrete.
|
||||
If any of the name, version, architecture, compiler,
|
||||
variants, or depdenencies are ambiguous,then it is not concrete.
|
||||
"""
|
||||
if self._concrete:
|
||||
return True
|
||||
|
||||
self._concrete = bool(not self.virtual
|
||||
and self.versions.concrete
|
||||
and self.variants.concrete
|
||||
and self.architecture
|
||||
and self.compiler and self.compiler.concrete
|
||||
and self.dependencies.concrete)
|
||||
@@ -604,6 +644,7 @@ def _concretize_helper(self, presets=None, visited=None):
|
||||
spack.concretizer.concretize_architecture(self)
|
||||
spack.concretizer.concretize_compiler(self)
|
||||
spack.concretizer.concretize_version(self)
|
||||
spack.concretizer.concretize_variants(self)
|
||||
presets[self.name] = self
|
||||
|
||||
visited.add(self.name)
|
||||
@@ -786,8 +827,7 @@ def _normalize_helper(self, visited, spec_deps, provider_index):
|
||||
else:
|
||||
required = index.providers_for(vspec.name)
|
||||
if required:
|
||||
raise UnsatisfiableProviderSpecError(
|
||||
required[0], pkg_dep)
|
||||
raise UnsatisfiableProviderSpecError(required[0], pkg_dep)
|
||||
provider_index.update(pkg_dep)
|
||||
|
||||
if name not in spec_deps:
|
||||
@@ -893,6 +933,11 @@ def validate_names(self):
|
||||
if not compilers.supported(spec.compiler):
|
||||
raise UnsupportedCompilerError(spec.compiler.name)
|
||||
|
||||
# Ensure that variants all exist.
|
||||
for vname, variant in spec.variants.items():
|
||||
if vname not in spec.package.variants:
|
||||
raise UnknownVariantError(spec.name, vname)
|
||||
|
||||
|
||||
def constrain(self, other, **kwargs):
|
||||
other = self._autospec(other)
|
||||
@@ -921,7 +966,7 @@ def constrain(self, other, **kwargs):
|
||||
self.compiler = other.compiler
|
||||
|
||||
self.versions.intersect(other.versions)
|
||||
self.variants.update(other.variants)
|
||||
self.variants.constrain(other.variants)
|
||||
self.architecture = self.architecture or other.architecture
|
||||
|
||||
if constrain_deps:
|
||||
@@ -990,11 +1035,13 @@ def satisfies(self, other, **kwargs):
|
||||
# All these attrs have satisfies criteria of their own,
|
||||
# but can be None to indicate no constraints.
|
||||
for s, o in ((self.versions, other.versions),
|
||||
(self.variants, other.variants),
|
||||
(self.compiler, other.compiler)):
|
||||
if s and o and not s.satisfies(o):
|
||||
return False
|
||||
|
||||
if not self.variants.satisfies(other.variants):
|
||||
return False
|
||||
|
||||
# Architecture satisfaction is currently just string equality.
|
||||
# Can be None for unconstrained, though.
|
||||
if (self.architecture and other.architecture and
|
||||
@@ -1061,11 +1108,12 @@ def _dup(self, other, **kwargs):
|
||||
# Local node attributes get copied first.
|
||||
self.name = other.name
|
||||
self.versions = other.versions.copy()
|
||||
self.variants = other.variants.copy()
|
||||
self.architecture = other.architecture
|
||||
self.compiler = other.compiler.copy() if other.compiler else None
|
||||
self.dependents = DependencyMap()
|
||||
self.dependencies = DependencyMap()
|
||||
self.variants = other.variants.copy()
|
||||
self.variants.spec = self
|
||||
|
||||
# If we copy dependencies, preserve DAG structure in the new spec
|
||||
if kwargs.get('deps', True):
|
||||
@@ -1354,6 +1402,8 @@ def __init__(self):
|
||||
(r'\~', lambda scanner, val: self.token(OFF, val)),
|
||||
(r'\%', lambda scanner, val: self.token(PCT, val)),
|
||||
(r'\=', lambda scanner, val: self.token(EQ, val)),
|
||||
# This is more liberal than identifier_re (see above).
|
||||
# Checked by check_identifier() for better error messages.
|
||||
(r'\w[\w.-]*', lambda scanner, val: self.token(ID, val)),
|
||||
(r'\s+', lambda scanner, val: None)])
|
||||
|
||||
@@ -1399,7 +1449,7 @@ def spec(self):
|
||||
spec = Spec.__new__(Spec)
|
||||
spec.name = self.token.value
|
||||
spec.versions = VersionList()
|
||||
spec.variants = VariantMap()
|
||||
spec.variants = VariantMap(spec)
|
||||
spec.architecture = None
|
||||
spec.compiler = None
|
||||
spec.dependents = DependencyMap()
|
||||
@@ -1580,6 +1630,13 @@ def __init__(self, compiler_name):
|
||||
"The '%s' compiler is not yet supported." % compiler_name)
|
||||
|
||||
|
||||
class UnknownVariantError(SpecError):
|
||||
"""Raised when the same variant occurs in a spec twice."""
|
||||
def __init__(self, pkg, variant):
|
||||
super(UnknownVariantError, self).__init__(
|
||||
"Package %s has no variant %s!" % (pkg, variant))
|
||||
|
||||
|
||||
class DuplicateArchitectureError(SpecError):
|
||||
"""Raised when the same architecture occurs in a spec twice."""
|
||||
def __init__(self, message):
|
||||
|
@@ -35,7 +35,13 @@ def check_spec(self, abstract, concrete):
|
||||
self.assertEqual(abstract.versions, concrete.versions)
|
||||
|
||||
if abstract.variants:
|
||||
self.assertEqual(abstract.versions, concrete.versions)
|
||||
for name in abstract.variants:
|
||||
avariant = abstract.variants[name]
|
||||
cvariant = concrete.variants[name]
|
||||
self.assertEqual(avariant.enabled, cvariant.enabled)
|
||||
|
||||
for name in abstract.package.variants:
|
||||
self.assertTrue(name in concrete.variants)
|
||||
|
||||
if abstract.compiler and abstract.compiler.concrete:
|
||||
self.assertEqual(abstract.compiler, concrete.compiler)
|
||||
@@ -66,6 +72,12 @@ def test_concretize_dag(self):
|
||||
self.check_concretize('libelf')
|
||||
|
||||
|
||||
def test_concretize_variant(self):
|
||||
self.check_concretize('mpich+debug')
|
||||
self.check_concretize('mpich~debug')
|
||||
self.check_concretize('mpich')
|
||||
|
||||
|
||||
def test_concretize_with_virtual(self):
|
||||
self.check_concretize('mpileaks ^mpi')
|
||||
self.check_concretize('mpileaks ^mpi@:1.1')
|
||||
|
@@ -242,12 +242,6 @@ def test_unsatisfiable_compiler_version(self):
|
||||
self.assertRaises(spack.spec.UnsatisfiableCompilerSpecError, spec.normalize)
|
||||
|
||||
|
||||
def test_unsatisfiable_variant(self):
|
||||
set_pkg_dep('mpileaks', 'mpich+debug')
|
||||
spec = Spec('mpileaks ^mpich~debug ^callpath ^dyninst ^libelf ^libdwarf')
|
||||
self.assertRaises(spack.spec.UnsatisfiableVariantSpecError, spec.normalize)
|
||||
|
||||
|
||||
def test_unsatisfiable_architecture(self):
|
||||
set_pkg_dep('mpileaks', 'mpich=bgqos_0')
|
||||
spec = Spec('mpileaks ^mpich=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf')
|
||||
|
@@ -33,8 +33,8 @@ class SpecSematicsTest(MockPackagesTest):
|
||||
# ================================================================================
|
||||
# Utility functions to set everything up.
|
||||
# ================================================================================
|
||||
def check_satisfies(self, spec, anon_spec):
|
||||
left = Spec(spec)
|
||||
def check_satisfies(self, spec, anon_spec, concrete=False):
|
||||
left = Spec(spec, concrete=concrete)
|
||||
right = parse_anonymous_spec(anon_spec, left.name)
|
||||
|
||||
# Satisfies is one-directional.
|
||||
@@ -46,8 +46,8 @@ def check_satisfies(self, spec, anon_spec):
|
||||
right.copy().constrain(left)
|
||||
|
||||
|
||||
def check_unsatisfiable(self, spec, anon_spec):
|
||||
left = Spec(spec)
|
||||
def check_unsatisfiable(self, spec, anon_spec, concrete=False):
|
||||
left = Spec(spec, concrete=concrete)
|
||||
right = parse_anonymous_spec(anon_spec, left.name)
|
||||
|
||||
self.assertFalse(left.satisfies(right))
|
||||
@@ -71,7 +71,7 @@ def check_invalid_constraint(self, spec, constraint):
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# Satisfiability and constraints
|
||||
# Satisfiability
|
||||
# ================================================================================
|
||||
def test_satisfies(self):
|
||||
self.check_satisfies('libelf@0.8.13', '@0:1')
|
||||
@@ -96,6 +96,9 @@ def test_satisfies_compiler_version(self):
|
||||
self.check_unsatisfiable('foo@4.0%pgi', '@1:3%pgi')
|
||||
self.check_unsatisfiable('foo@4.0%pgi@4.5', '@1:3%pgi@4.4:4.6')
|
||||
|
||||
self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
|
||||
self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
|
||||
|
||||
|
||||
def test_satisfies_architecture(self):
|
||||
self.check_satisfies('foo=chaos_5_x86_64_ib', '=chaos_5_x86_64_ib')
|
||||
@@ -147,7 +150,40 @@ def test_satisfies_virtual_dependency_versions(self):
|
||||
self.check_unsatisfiable('mpileaks^mpi@3:', '^mpich@1.0')
|
||||
|
||||
|
||||
def test_constrain(self):
|
||||
def test_satisfies_matching_variant(self):
|
||||
self.check_satisfies('mpich+foo', 'mpich+foo')
|
||||
self.check_satisfies('mpich~foo', 'mpich~foo')
|
||||
|
||||
|
||||
def test_satisfies_unconstrained_variant(self):
|
||||
# only asked for mpich, no constraints. Either will do.
|
||||
self.check_satisfies('mpich+foo', 'mpich')
|
||||
self.check_satisfies('mpich~foo', 'mpich')
|
||||
|
||||
|
||||
def test_unsatisfiable_variants(self):
|
||||
# This case is different depending on whether the specs are concrete.
|
||||
|
||||
# 'mpich' is not concrete:
|
||||
self.check_satisfies('mpich', 'mpich+foo', False)
|
||||
self.check_satisfies('mpich', 'mpich~foo', False)
|
||||
|
||||
# 'mpich' is concrete:
|
||||
self.check_unsatisfiable('mpich', 'mpich+foo', True)
|
||||
self.check_unsatisfiable('mpich', 'mpich~foo', True)
|
||||
|
||||
|
||||
def test_unsatisfiable_variant_mismatch(self):
|
||||
# No matchi in specs
|
||||
self.check_unsatisfiable('mpich~foo', 'mpich+foo')
|
||||
self.check_unsatisfiable('mpich+foo', 'mpich~foo')
|
||||
|
||||
|
||||
|
||||
# ================================================================================
|
||||
# Constraints
|
||||
# ================================================================================
|
||||
def test_constrain_variants(self):
|
||||
self.check_constrain('libelf@2.1:2.5', 'libelf@0:2.5', 'libelf@2.1:3')
|
||||
self.check_constrain('libelf@2.1:2.5%gcc@4.5:4.6',
|
||||
'libelf@0:2.5%gcc@2:4.6', 'libelf@2.1:3%gcc@4.5:4.7')
|
||||
@@ -158,6 +194,8 @@ def test_constrain(self):
|
||||
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo')
|
||||
self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo')
|
||||
|
||||
|
||||
def test_constrain_arch(self):
|
||||
self.check_constrain('libelf=bgqos_0', 'libelf=bgqos_0', 'libelf=bgqos_0')
|
||||
self.check_constrain('libelf=bgqos_0', 'libelf', 'libelf=bgqos_0')
|
||||
|
||||
@@ -170,8 +208,3 @@ def test_invalid_constraint(self):
|
||||
self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
|
||||
|
||||
self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54')
|
||||
|
||||
|
||||
def test_compiler_satisfies(self):
|
||||
self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
|
||||
self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
|
||||
|
36
lib/spack/spack/variant.py
Normal file
36
lib/spack/spack/variant.py
Normal file
@@ -0,0 +1,36 @@
|
||||
##############################################################################
|
||||
# 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
|
||||
##############################################################################
|
||||
"""Variant is a class describing flags on builds, or "variants".
|
||||
|
||||
Could be generalized later to describe aribitrary parameters, but
|
||||
currently variants are just flags.
|
||||
|
||||
"""
|
||||
|
||||
class Variant(object):
|
||||
"""Represents a variant on a build. Can be either on or off."""
|
||||
def __init__(self, default, description):
|
||||
self.default = bool(default)
|
||||
self.description = str(description)
|
Reference in New Issue
Block a user