From 0944ba120cad3de7b84b15e19b9c889f5bea6241 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Tue, 17 Mar 2015 20:59:47 -0400 Subject: relations are now "directives", and code is cleaned up. --- lib/spack/llnl/util/lang.py | 25 ++++- lib/spack/spack/__init__.py | 6 +- lib/spack/spack/directives.py | 193 ++++++++++++++++++++++++++++++++++++ lib/spack/spack/multimethod.py | 2 +- lib/spack/spack/package.py | 3 +- lib/spack/spack/relations.py | 215 ----------------------------------------- 6 files changed, 222 insertions(+), 222 deletions(-) create mode 100644 lib/spack/spack/directives.py delete mode 100644 lib/spack/spack/relations.py (limited to 'lib') diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py index 332367f537..13453c20ed 100644 --- a/lib/spack/llnl/util/lang.py +++ b/lib/spack/llnl/util/lang.py @@ -126,9 +126,9 @@ 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: @@ -322,6 +322,27 @@ def match_predicate(*args): 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): def __init__(self, message): super(RequiredAttributeError, self).__init__(message) diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index eb891e3d57..053c4036d8 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -146,9 +146,9 @@ import llnl.util.filesystem 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 * diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py new file mode 100644 index 0000000000..e1589c019f --- /dev/null +++ b/lib/spack/spack/directives.py @@ -0,0 +1,193 @@ +############################################################################## +# 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`` + +""" +__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 directive(fun): + """Decorator that allows a function to be called while a class is + being constructed, and to modify the class. + + 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. + Metadata is just stored as a dict in the package's versions + dictionary. Package must turn it into a valid fetch strategy + later. + """ + 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 + + +@directive +def depends_on(pkg, *specs): + """Adds a dependencies local variable in the locals of + the calling class, based on args. """ + dependencies = pkg.setdefault('dependencies', {}) + + for string in specs: + for spec in spack.spec.parse(string): + if pkg.name == spec.name: + raise CircularReferenceError('depends_on', pkg.name) + dependencies[spec.name] = spec + + +@directive +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. + + """ + dependencies = pkg.setdefault('dependencies', {}) + extendees = pkg.setdefault('extendees', {}) + if extendees: + raise RelationError("Packages can extend at most one other package.") + + spec = Spec(spec) + if pkg.name == spec.name: + raise CircularReferenceError('extends', pkg.name) + dependencies[spec.name] = spec + extendees[spec.name] = (spec, kwargs) + + +@directive +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) + + provided = pkg.setdefault("provided", {}) + for string in specs: + for provided_spec in spack.spec.parse(string): + if pkg.name == provided_spec.name: + raise CircularReferenceError('depends_on', pkg.name) + provided[provided_spec] = provider_spec + + +@directive +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) + + patches = pkg.setdefault('patches', {}) + + when_spec = parse_anonymous_spec(when, pkg.name) + if when_spec not in patches: + 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. + patches[when_spec].append(Patch(pkg.name, url_or_filename, level)) + + +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 diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py index 974401e1aa..892619c6ac 100644 --- a/lib/spack/spack/multimethod.py +++ b/lib/spack/spack/multimethod.py @@ -195,7 +195,7 @@ class when(object): """ 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): diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 7d9eca5077..7f2b53ceed 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -303,7 +303,8 @@ class Package(object): """ # - # 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 goes: Version -> dict of attributes""" diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py deleted file mode 100644 index a0c7723473..0000000000 --- a/lib/spack/spack/relations.py +++ /dev/null @@ -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 -- cgit v1.2.3-70-g09d2