relations are now "directives", and code is cleaned up.

This commit is contained in:
Todd Gamblin 2015-03-17 20:59:47 -04:00
parent 8e87b2176a
commit 0944ba120c
5 changed files with 82 additions and 82 deletions

View File

@ -126,9 +126,9 @@ def caller_locals():
del stack del stack
def get_calling_package_name(): def get_calling_module_name():
"""Make sure that the caller is a class definition, and return the """Make sure that the caller is a class definition, and return the
module's name. enclosing module's name.
""" """
stack = inspect.stack() stack = inspect.stack()
try: try:
@ -322,6 +322,27 @@ def match(string):
return match 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
return value
def setdefault(self, *args):
return dictionary.setdefault(*args)
def get(self, *args):
return dictionary.get(*args)
return wrapper()
class RequiredAttributeError(ValueError): class RequiredAttributeError(ValueError):
def __init__(self, message): def __init__(self, message):
super(RequiredAttributeError, self).__init__(message) super(RequiredAttributeError, self).__init__(message)

View File

@ -146,9 +146,9 @@
from llnl.util.filesystem import * from llnl.util.filesystem import *
__all__ += llnl.util.filesystem.__all__ __all__ += llnl.util.filesystem.__all__
import spack.relations import spack.directives
from spack.relations import * from spack.directives import *
__all__ += spack.relations.__all__ __all__ += spack.directives.__all__
import spack.util.executable import spack.util.executable
from spack.util.executable import * from spack.util.executable import *

View File

@ -22,51 +22,26 @@
# 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
############################################################################## ##############################################################################
""" """This package contains directives that can be used within a package.
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): Directives are functions that can be called inside a package
definition to modify the package, for example:
class OpenMpi(Package):
depends_on("hwloc") depends_on("hwloc")
provides("mpi") provides("mpi")
... ...
The available relations are: ``provides`` and ``depends_on`` are spack directives.
depends_on The available directives are:
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 * ``version``
This is useful when more than one package can satisfy a dependence. Above, * ``depends_on``
OpenMPI declares that it "provides" mpi. Other implementations of the MPI * ``provides``
interface, like mvapich and mpich, also provide mpi, e.g.: * ``extends``
* ``patch``
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' ] __all__ = [ 'depends_on', 'extends', 'provides', 'patch', 'version' ]
@ -84,14 +59,27 @@ class Mpileaks(Package):
from spack.spec import Spec, parse_anonymous_spec from spack.spec import Spec, parse_anonymous_spec
def directive(fun):
"""Decorator that allows a function to be called while a class is
being constructed, and to modify the class.
def version(ver, checksum=None, **kwargs): Adds the class scope as an initial parameter when called, like
a class method would.
"""
def directive_function(*args, **kwargs):
pkg = DictWrapper(caller_locals())
pkg.name = get_calling_module_name()
return fun(pkg, *args, **kwargs)
return directive_function
@directive
def version(pkg, ver, checksum=None, **kwargs):
"""Adds a version and metadata describing how to fetch it. """Adds a version and metadata describing how to fetch it.
Metadata is just stored as a dict in the package's versions Metadata is just stored as a dict in the package's versions
dictionary. Package must turn it into a valid fetch strategy dictionary. Package must turn it into a valid fetch strategy
later. later.
""" """
pkg = caller_locals()
versions = pkg.setdefault('versions', {}) versions = pkg.setdefault('versions', {})
# special case checksum for backward compatibility # special case checksum for backward compatibility
@ -103,21 +91,21 @@ def version(ver, checksum=None, **kwargs):
versions[Version(ver)] = kwargs versions[Version(ver)] = kwargs
def depends_on(*specs): @directive
def depends_on(pkg, *specs):
"""Adds a dependencies local variable in the locals of """Adds a dependencies local variable in the locals of
the calling class, based on args. """ the calling class, based on args. """
pkg = get_calling_package_name() dependencies = pkg.setdefault('dependencies', {})
clocals = caller_locals()
dependencies = clocals.setdefault('dependencies', {})
for string in specs: for string in specs:
for spec in spack.spec.parse(string): for spec in spack.spec.parse(string):
if pkg == spec.name: if pkg.name == spec.name:
raise CircularReferenceError('depends_on', pkg) raise CircularReferenceError('depends_on', pkg.name)
dependencies[spec.name] = spec dependencies[spec.name] = spec
def extends(spec, **kwargs): @directive
def extends(pkg, spec, **kwargs):
"""Same as depends_on, but dependency is symlinked into parent prefix. """Same as depends_on, but dependency is symlinked into parent prefix.
This is for Python and other language modules where the module This is for Python and other language modules where the module
@ -131,64 +119,54 @@ def extends(spec, **kwargs):
mechanism. mechanism.
""" """
pkg = get_calling_package_name() dependencies = pkg.setdefault('dependencies', {})
clocals = caller_locals() extendees = pkg.setdefault('extendees', {})
dependencies = clocals.setdefault('dependencies', {})
extendees = clocals.setdefault('extendees', {})
if extendees: if extendees:
raise RelationError("Packages can extend at most one other package.") raise RelationError("Packages can extend at most one other package.")
spec = Spec(spec) spec = Spec(spec)
if pkg == spec.name: if pkg.name == spec.name:
raise CircularReferenceError('extends', pkg) raise CircularReferenceError('extends', pkg.name)
dependencies[spec.name] = spec dependencies[spec.name] = spec
extendees[spec.name] = (spec, kwargs) extendees[spec.name] = (spec, kwargs)
def provides(*specs, **kwargs): @directive
def provides(pkg, *specs, **kwargs):
"""Allows packages to provide a virtual dependency. If a package provides """Allows packages to provide a virtual dependency. If a package provides
'mpi', other packages can declare that they depend on "mpi", and spack 'mpi', other packages can declare that they depend on "mpi", and spack
can use the providing package to satisfy the dependency. can use the providing package to satisfy the dependency.
""" """
pkg = get_calling_package_name() spec_string = kwargs.get('when', pkg.name)
spec_string = kwargs.get('when', pkg) provider_spec = parse_anonymous_spec(spec_string, pkg.name)
provider_spec = parse_anonymous_spec(spec_string, pkg)
provided = caller_locals().setdefault("provided", {}) provided = pkg.setdefault("provided", {})
for string in specs: for string in specs:
for provided_spec in spack.spec.parse(string): for provided_spec in spack.spec.parse(string):
if pkg == provided_spec.name: if pkg.name == provided_spec.name:
raise CircularReferenceError('depends_on', pkg) raise CircularReferenceError('depends_on', pkg.name)
provided[provided_spec] = provider_spec provided[provided_spec] = provider_spec
def patch(url_or_filename, **kwargs): @directive
def patch(pkg, url_or_filename, **kwargs):
"""Packages can declare patches to apply to source. You can """Packages can declare patches to apply to source. You can
optionally provide a when spec to indicate that a particular optionally provide a when spec to indicate that a particular
patch should only be applied when the package's spec meets patch should only be applied when the package's spec meets
certain conditions (e.g. a particular version). certain conditions (e.g. a particular version).
""" """
pkg = get_calling_package_name()
level = kwargs.get('level', 1) level = kwargs.get('level', 1)
when_spec = parse_anonymous_spec(kwargs.get('when', pkg), pkg) when = kwargs.get('when', pkg.name)
patches = caller_locals().setdefault('patches', {}) patches = pkg.setdefault('patches', {})
when_spec = parse_anonymous_spec(when, pkg.name)
if when_spec not in patches: if when_spec not in patches:
patches[when_spec] = [Patch(pkg, url_or_filename, level)] patches[when_spec] = [Patch(pkg.name, url_or_filename, level)]
else: else:
# if this spec is identical to some other, then append this # if this spec is identical to some other, then append this
# patch to the existing list. # patch to the existing list.
patches[when_spec].append(Patch(pkg, url_or_filename, level)) patches[when_spec].append(Patch(pkg.name, 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): class RelationError(spack.error.SpackError):

View File

@ -195,7 +195,7 @@ def install(self, prefix):
""" """
class when(object): class when(object):
def __init__(self, spec): def __init__(self, spec):
pkg = get_calling_package_name() pkg = get_calling_module_name()
self.spec = parse_anonymous_spec(spec, pkg) self.spec = parse_anonymous_spec(spec, pkg)
def __call__(self, method): def __call__(self, method):

View File

@ -303,7 +303,8 @@ class SomePackage(Package):
""" """
# #
# These variables are defaults for the various "relations". # These variables are defaults for Spack's various package
# directives.
# #
"""Map of information about Versions of this package. """Map of information about Versions of this package.
Map goes: Version -> dict of attributes""" Map goes: Version -> dict of attributes"""