summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/basic_usage.rst37
-rw-r--r--lib/spack/docs/index.rst2
-rw-r--r--lib/spack/docs/mirrors.rst15
-rw-r--r--lib/spack/docs/packaging_guide.rst123
-rw-r--r--lib/spack/docs/site_configuration.rst148
-rwxr-xr-xlib/spack/env/cc18
-rw-r--r--lib/spack/llnl/util/lang.py4
-rw-r--r--lib/spack/spack/__init__.py14
-rw-r--r--lib/spack/spack/abi.py128
-rw-r--r--lib/spack/spack/cmd/create.py6
-rw-r--r--lib/spack/spack/cmd/diy.py4
-rw-r--r--lib/spack/spack/compilers/gcc.py3
-rw-r--r--lib/spack/spack/concretize.py223
-rw-r--r--lib/spack/spack/config.py79
-rw-r--r--lib/spack/spack/directory_layout.py4
-rw-r--r--lib/spack/spack/hooks/sbang.py43
-rw-r--r--lib/spack/spack/package.py42
-rw-r--r--lib/spack/spack/preferred_packages.py175
-rw-r--r--lib/spack/spack/repository.py9
-rw-r--r--lib/spack/spack/spec.py246
-rw-r--r--lib/spack/spack/stage.py24
-rw-r--r--lib/spack/spack/test/__init__.py13
-rw-r--r--lib/spack/spack/test/concretize.py119
-rw-r--r--lib/spack/spack/test/directory_layout.py6
-rw-r--r--lib/spack/spack/test/mock_packages_test.py20
-rw-r--r--lib/spack/spack/test/sbang.py93
-rw-r--r--lib/spack/spack/test/stage.py35
-rw-r--r--lib/spack/spack/url.py2
-rw-r--r--lib/spack/spack/util/compression.py7
29 files changed, 1391 insertions, 251 deletions
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index f94ac3d2ba..accf09cc2a 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -357,7 +357,7 @@ Spack, you can simply run ``spack compiler add`` with the path to
where the compiler is installed. For example::
$ spack compiler add /usr/local/tools/ic-13.0.079
- ==> Added 1 new compiler to /Users/gamblin2/.spackconfig
+ ==> Added 1 new compiler to /Users/gamblin2/.spack/compilers.yaml
intel@13.0.079
Or you can run ``spack compiler add`` with no arguments to force
@@ -367,7 +367,7 @@ installed, but you know that new compilers have been added to your
$ module load gcc-4.9.0
$ spack compiler add
- ==> Added 1 new compiler to /Users/gamblin2/.spackconfig
+ ==> Added 1 new compiler to /Users/gamblin2/.spack/compilers.yaml
gcc@4.9.0
This loads the environment module for gcc-4.9.0 to get it into the
@@ -398,27 +398,34 @@ Manual compiler configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If auto-detection fails, you can manually configure a compiler by
-editing your ``~/.spackconfig`` file. You can do this by running
-``spack config edit``, which will open the file in your ``$EDITOR``.
+editing your ``~/.spack/compilers.yaml`` file. You can do this by running
+``spack config edit compilers``, which will open the file in your ``$EDITOR``.
Each compiler configuration in the file looks like this::
...
- [compiler "intel@15.0.0"]
- cc = /usr/local/bin/icc-15.0.024-beta
- cxx = /usr/local/bin/icpc-15.0.024-beta
- f77 = /usr/local/bin/ifort-15.0.024-beta
- fc = /usr/local/bin/ifort-15.0.024-beta
- ...
+ chaos_5_x86_64_ib:
+ ...
+ intel@15.0.0:
+ cc: /usr/local/bin/icc-15.0.024-beta
+ cxx: /usr/local/bin/icpc-15.0.024-beta
+ f77: /usr/local/bin/ifort-15.0.024-beta
+ fc: /usr/local/bin/ifort-15.0.024-beta
+ ...
+
+The chaos_5_x86_64_ib string is an architecture string, and multiple
+compilers can be listed underneath an architecture. The architecture
+string may be replaced with the string 'all' to signify compilers that
+work on all architectures.
For compilers, like ``clang``, that do not support Fortran, put
``None`` for ``f77`` and ``fc``::
- [compiler "clang@3.3svn"]
- cc = /usr/bin/clang
- cxx = /usr/bin/clang++
- f77 = None
- fc = None
+ clang@3.3svn:
+ cc: /usr/bin/clang
+ cxx: /usr/bin/clang++
+ f77: None
+ fc: None
Once you save the file, the configured compilers will show up in the
list displayed by ``spack compilers``.
diff --git a/lib/spack/docs/index.rst b/lib/spack/docs/index.rst
index 79757208c9..d6ce52b747 100644
--- a/lib/spack/docs/index.rst
+++ b/lib/spack/docs/index.rst
@@ -18,7 +18,7 @@ configurations can coexist on the same system.
Most importantly, Spack is *simple*. It offers a simple *spec* syntax
so that users can specify versions and configuration options
concisely. Spack is also simple for package authors: package files
-are writtin in pure Python, and specs allow package authors to
+are written in pure Python, and specs allow package authors to
maintain a single file for many different builds of the same package.
See the :doc:`features` for examples and highlights.
diff --git a/lib/spack/docs/mirrors.rst b/lib/spack/docs/mirrors.rst
index d732a3dd54..dad04d053b 100644
--- a/lib/spack/docs/mirrors.rst
+++ b/lib/spack/docs/mirrors.rst
@@ -38,7 +38,7 @@ contains tarballs for each package, named after each package.
.. note::
- Archives are **not** named exactly they were in the package's fetch
+ Archives are **not** named exactly the way they were in the package's fetch
URL. They have the form ``<name>-<version>.<extension>``, where
``<name>`` is Spack's name for the package, ``<version>`` is the
version of the tarball, and ``<extension>`` is whatever format the
@@ -186,7 +186,7 @@ Each mirror has a name so that you can refer to it again later.
``spack mirror list``
----------------------------
-If you want to see all the mirrors Spack knows about you can run ``spack mirror list``::
+To see all the mirrors Spack knows about, run ``spack mirror list``::
$ spack mirror list
local_filesystem file:///Users/gamblin2/spack-mirror-2014-06-24
@@ -196,7 +196,7 @@ If you want to see all the mirrors Spack knows about you can run ``spack mirror
``spack mirror remove``
----------------------------
-And, if you want to remove a mirror, just remove it by name::
+To remove a mirror by name::
$ spack mirror remove local_filesystem
$ spack mirror list
@@ -205,12 +205,11 @@ And, if you want to remove a mirror, just remove it by name::
Mirror precedence
----------------------------
-Adding a mirror really just adds a section in ``~/.spackconfig``::
+Adding a mirror really adds a line in ``~/.spack/mirrors.yaml``::
- [mirror "local_filesystem"]
- url = file:///Users/gamblin2/spack-mirror-2014-06-24
- [mirror "remote_server"]
- url = https://example.com/some/web-hosted/directory/spack-mirror-2014-06-24
+ mirrors:
+ local_filesystem: file:///Users/gamblin2/spack-mirror-2014-06-24
+ remote_server: https://example.com/some/web-hosted/directory/spack-mirror-2014-06-24
If you want to change the order in which mirrors are searched for
packages, you can edit this file and reorder the sections. Spack will
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index b7e0b6a4f3..c1077e4497 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -419,7 +419,7 @@ directory to the directory containing the downloaded archive before it
calls your ``install`` method. Within ``install``, the path to the
downloaded archive is available as ``self.stage.archive_file``.
-Here is an example snippet for packages distribuetd as self-extracting
+Here is an example snippet for packages distributed as self-extracting
archives. The example sets permissions on the downloaded file to make
it executable, then runs it with some arguments.
@@ -661,7 +661,7 @@ Default
revision instead.
Revisions
- Add ``hg`` and ``revision``parameters:
+ Add ``hg`` and ``revision`` parameters:
.. code-block:: python
@@ -1553,6 +1553,69 @@ This is useful when you want to know exactly what Spack will do when
you ask for a particular spec.
+``Concretization Policies``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A user may have certain preferences for how packages should
+be concretized on their system. For example, one user may prefer packages
+built with OpenMPI and the Intel compiler. Another user may prefer
+packages be built with MVAPICH and GCC.
+
+Spack can be configured to prefer certain compilers, package
+versions, depends_on, and variants during concretization.
+The preferred configuration can be controlled via the
+``~/.spack/packages.yaml`` file for user configuations, or the
+``etc/spack/packages.yaml`` site configuration.
+
+
+Here's an example packages.yaml file that sets preferred packages:
+
+.. code-block:: sh
+
+ packages:
+ dyninst:
+ compiler: [gcc@4.9]
+ variants: +debug
+ gperftools:
+ version: [2.2, 2.4, 2.3]
+ all:
+ compiler: [gcc@4.4.7, gcc@4.6:, intel, clang, pgi]
+ providers:
+ mpi: [mvapich, mpich, openmpi]
+
+
+At a high level, this example is specifying how packages should be
+concretized. The dyninst package should prefer using gcc 4.9 and
+be built with debug options. The gperftools package should prefer version
+2.2 over 2.4. Every package on the system should prefer mvapich for
+its MPI and gcc 4.4.7 (except for Dyninst, which overrides this by preferring gcc 4.9).
+These options are used to fill in implicit defaults. Any of them can be overwritten
+on the command line if explicitly requested.
+
+Each packages.yaml file begins with the string ``packages:`` and
+package names are specified on the next level. The special string ``all``
+applies settings to each package. Underneath each package name is
+one or more components: ``compiler``, ``variants``, ``version``,
+or ``providers``. Each component has an ordered list of spec
+``constraints``, with earlier entries in the list being preferred over
+later entries.
+
+Sometimes a package installation may have constraints that forbid
+the first concretization rule, in which case Spack will use the first
+legal concretization rule. Going back to the example, if a user
+requests gperftools 2.3 or later, then Spack will install version 2.4
+as the 2.4 version of gperftools is preferred over 2.3.
+
+An explicit concretization rule in the preferred section will always
+take preference over unlisted concretizations. In the above example,
+xlc isn't listed in the compiler list. Every listed compiler from
+gcc to pgi will thus be preferred over the xlc compiler.
+
+The syntax for the ``provider`` section differs slightly from other
+concretization rules. A provider lists a value that packages may
+``depend_on`` (e.g, mpi) and a list of rules for fulfilling that
+dependency.
+
.. _install-method:
Implementing the ``install`` method
@@ -2097,6 +2160,62 @@ package, this allows us to avoid race conditions in the library's
build system.
+.. _sanity-checks:
+
+Sanity checking an intallation
+--------------------------------
+
+By default, Spack assumes that a build has failed if nothing is
+written to the install prefix, and that it has succeeded if anything
+(a file, a directory, etc.) is written to the install prefix after
+``install()`` completes.
+
+Consider a simple autotools build like this:
+
+.. code-block:: python
+
+ def install(self, spec, prefix):
+ configure("--prefix=" + prefix)
+ make()
+ make("install")
+
+If you are using using standard autotools or CMake, ``configure`` and
+``make`` will not write anything to the install prefix. Only ``make
+install`` writes the files, and only once the build is already
+complete. Not all builds are like this. Many builds of scientific
+software modify the install prefix *before* ``make install``. Builds
+like this can falsely report that they were successfully installed if
+an error occurs before the install is complete but after files have
+been written to the ``prefix``.
+
+
+``sanity_check_is_file`` and ``sanity_check_is_dir``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can optionally specify *sanity checks* to deal with this problem.
+Add properties like this to your package:
+
+.. code-block:: python
+
+ class MyPackage(Package):
+ ...
+
+ sanity_check_is_file = ['include/libelf.h']
+ sanity_check_is_dir = [lib]
+
+ def install(self, spec, prefix):
+ configure("--prefix=" + prefix)
+ make()
+ make("install")
+
+Now, after ``install()`` runs, Spack will check whether
+``$prefix/include/libelf.h`` exists and is a file, and whether
+``$prefix/lib`` exists and is a directory. If the checks fail, then
+the build will fail and the install prefix will be removed. If they
+succeed, Spack considers the build succeeful and keeps the prefix in
+place.
+
+
.. _file-manipulation:
File manipulation functions
diff --git a/lib/spack/docs/site_configuration.rst b/lib/spack/docs/site_configuration.rst
index b03df29573..3abfa21a9d 100644
--- a/lib/spack/docs/site_configuration.rst
+++ b/lib/spack/docs/site_configuration.rst
@@ -54,87 +54,73 @@ more elements to the list to indicate where your own site's temporary
directory is.
-.. _concretization-policies:
-
-Concretization policies
-----------------------------
-
-When a user asks for a package like ``mpileaks`` to be installed,
-Spack has to make decisions like what version should be installed,
-what compiler to use, and how its dependencies should be configured.
-This process is called *concretization*, and it's covered in detail in
-:ref:`its own section <abstract-and-concrete>`.
-
-The default concretization policies are in the
-:py:mod:`spack.concretize` module, specifically in the
-:py:class:`spack.concretize.DefaultConcretizer` class. These are the
-important methods used in the concretization process:
-
-* :py:meth:`concretize_version(self, spec) <spack.concretize.DefaultConcretizer.concretize_version>`
-* :py:meth:`concretize_architecture(self, spec) <spack.concretize.DefaultConcretizer.concretize_architecture>`
-* :py:meth:`concretize_compiler(self, spec) <spack.concretize.DefaultConcretizer.concretize_compiler>`
-* :py:meth:`choose_provider(self, spec, providers) <spack.concretize.DefaultConcretizer.choose_provider>`
-
-The first three take a :py:class:`Spec <spack.spec.Spec>` object and
-modify it by adding constraints for the version. For example, if the
-input spec had a version range like `1.0:5.0.3`, then the
-``concretize_version`` method should set the spec's version to a
-*single* version in that range. Likewise, ``concretize_architecture``
-selects an architecture when the input spec does not have one, and
-``concretize_compiler`` needs to set both a concrete compiler and a
-concrete compiler version.
-
-``choose_provider()`` affects how concrete implementations are chosen
-based on a virtual dependency spec. The input spec is some virtual
-dependency and the ``providers`` index is a :py:class:`ProviderIndex
-<spack.packages.ProviderIndex>` object. The ``ProviderIndex`` maps
-the virtual spec to specs for possible implementations, and
-``choose_provider()`` should simply choose one of these. The
-``concretize_*`` methods will be called on the chosen implementation
-later, so there is no need to fully concretize the spec when returning
-it.
-
-The ``DefaultConcretizer`` is intended to provide sensible defaults
-for each policy, but there are certain choices that it can't know
-about. For example, one site might prefer ``OpenMPI`` over ``MPICH``,
-or another might prefer an old version of some packages. These types
-of special cases can be integrated with custom concretizers.
-
-Writing a custom concretizer
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-To write your own concretizer, you need only subclass
-``DefaultConcretizer`` and override the methods you want to change.
-For example, you might write a class like this to change *only* the
-``concretize_version()`` behavior:
-
-.. code-block:: python
-
- from spack.concretize import DefaultConcretizer
-
- class MyConcretizer(DefaultConcretizer):
- def concretize_version(self, spec):
- # implement custom logic here.
-
-Once you have written your custom concretizer, you can make Spack use
-it by editing ``globals.py``. Find this part of the file:
-
-.. code-block:: python
-
- #
- # This controls how things are concretized in spack.
- # Replace it with a subclass if you want different
- # policies.
- #
- concretizer = DefaultConcretizer()
-
-Set concretizer to *your own* class instead of the default:
-
-.. code-block:: python
-
- concretizer = MyConcretizer()
-
-The next time you run Spack, your changes should take effect.
+External Packages
+~~~~~~~~~~~~~~~~~~~~~
+Spack can be configured to use externally-installed
+packages rather than building its own packages. This may be desirable
+if machines ship with system packages, such as a customized MPI
+that should be used instead of Spack building its own MPI.
+
+External packages are configured through the ``packages.yaml`` file found
+in a Spack installation's ``etc/spack/`` or a user's ``~/.spack/``
+directory. Here's an example of an external configuration:
+
+.. code-block:: yaml
+
+ packages:
+ openmpi:
+ paths:
+ openmpi@1.4.3%gcc@4.4.7=chaos_5_x86_64_ib: /opt/openmpi-1.4.3
+ openmpi@1.4.3%gcc@4.4.7=chaos_5_x86_64_ib+debug: /opt/openmpi-1.4.3-debug
+ openmpi@1.6.5%intel@10.1=chaos_5_x86_64_ib: /opt/openmpi-1.6.5-intel
+
+This example lists three installations of OpenMPI, one built with gcc,
+one built with gcc and debug information, and another built with Intel.
+If Spack is asked to build a package that uses one of these MPIs as a
+dependency, it will use the the pre-installed OpenMPI in
+the given directory.
+
+Each ``packages.yaml`` begins with a ``packages:`` token, followed
+by a list of package names. To specify externals, add a ``paths``
+token under the package name, which lists externals in a
+``spec : /path`` format. Each spec should be as
+well-defined as reasonably possible. If a
+package lacks a spec component, such as missing a compiler or
+package version, then Spack will guess the missing component based
+on its most-favored packages, and it may guess incorrectly.
+
+Each package version and compilers listed in an external should
+have entries in Spack's packages and compiler configuration, even
+though the package and compiler may not every be built.
+
+The packages configuration can tell Spack to use an external location
+for certain package versions, but it does not restrict Spack to using
+external packages. In the above example, if an OpenMPI 1.8.4 became
+available Spack may choose to start building and linking with that version
+rather than continue using the pre-installed OpenMPI versions.
+
+To prevent this, the ``packages.yaml`` configuration also allows packages
+to be flagged as non-buildable. The previous example could be modified to
+be:
+
+.. code-block:: yaml
+
+ packages:
+ openmpi:
+ paths:
+ openmpi@1.4.3%gcc@4.4.7=chaos_5_x86_64_ib: /opt/openmpi-1.4.3
+ openmpi@1.4.3%gcc@4.4.7=chaos_5_x86_64_ib+debug: /opt/openmpi-1.4.3-debug
+ openmpi@1.6.5%intel@10.1=chaos_5_x86_64_ib: /opt/openmpi-1.6.5-intel
+ buildable: False
+
+The addition of the ``buildable`` flag tells Spack that it should never build
+its own version of OpenMPI, and it will instead always rely on a pre-built
+OpenMPI. Similar to ``paths``, ``buildable`` is specified as a property under
+a package name.
+
+The ``buildable`` does not need to be paired with external packages.
+It could also be used alone to forbid packages that may be
+buggy or otherwise undesirable.
Profiling
diff --git a/lib/spack/env/cc b/lib/spack/env/cc
index a19346ce97..4a3e6eddc9 100755
--- a/lib/spack/env/cc
+++ b/lib/spack/env/cc
@@ -113,14 +113,22 @@ case "$command" in
;;
esac
-# Finish setting up the mode.
+# If any of the arguments below is present then the mode is vcheck. In vcheck mode nothing is added in terms of extra search paths or libraries
if [ -z "$mode" ]; then
- mode=ccld
for arg in "$@"; do
if [ "$arg" = -v -o "$arg" = -V -o "$arg" = --version -o "$arg" = -dumpversion ]; then
mode=vcheck
break
- elif [ "$arg" = -E ]; then
+ fi
+ done
+fi
+
+# Finish setting up the mode.
+
+if [ -z "$mode" ]; then
+ mode=ccld
+ for arg in "$@"; do
+ if [ "$arg" = -E ]; then
mode=cpp
break
elif [ "$arg" = -c ]; then
@@ -145,6 +153,10 @@ fi
# Save original command for debug logging
input_command="$@"
+if [ "$mode" == vcheck ] ; then
+ exec ${command} "$@"
+fi
+
#
# Now do real parsing of the command line args, trying hard to keep
# non-rpath linker arguments in the proper order w.r.t. other command
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 1c4d1ed623..13d301f84e 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -235,11 +235,11 @@ def key_ordering(cls):
if not has_method(cls, '_cmp_key'):
raise TypeError("'%s' doesn't define _cmp_key()." % cls.__name__)
- setter('__eq__', lambda s,o: o is not None and s._cmp_key() == o._cmp_key())
+ setter('__eq__', lambda s,o: (s is o) or (o is not None and s._cmp_key() == o._cmp_key()))
setter('__lt__', lambda s,o: o is not None and s._cmp_key() < o._cmp_key())
setter('__le__', lambda s,o: o is not None and s._cmp_key() <= o._cmp_key())
- setter('__ne__', lambda s,o: o is None or s._cmp_key() != o._cmp_key())
+ setter('__ne__', lambda s,o: (s is not o) and (o is None or s._cmp_key() != o._cmp_key()))
setter('__gt__', lambda s,o: o is None or s._cmp_key() > o._cmp_key())
setter('__ge__', lambda s,o: o is None or s._cmp_key() >= o._cmp_key())
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 5d62d597cb..0ba42bbbfc 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -82,6 +82,20 @@ from spack.directory_layout import YamlDirectoryLayout
install_layout = YamlDirectoryLayout(install_path)
#
+# This controls how packages are sorted when trying to choose
+# the most preferred package. More preferred packages are sorted
+# first.
+#
+from spack.preferred_packages import PreferredPackages
+pkgsort = PreferredPackages()
+
+#
+# This tests ABI compatibility between packages
+#
+from spack.abi import ABI
+abi = ABI()
+
+#
# This controls how things are concretized in spack.
# Replace it with a subclass if you want different
# policies.
diff --git a/lib/spack/spack/abi.py b/lib/spack/spack/abi.py
new file mode 100644
index 0000000000..7e565bcbf9
--- /dev/null
+++ b/lib/spack/spack/abi.py
@@ -0,0 +1,128 @@
+##############################################################################
+# Copyright (c) 2015, 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 os
+import spack
+import spack.spec
+from spack.spec import CompilerSpec
+from spack.util.executable import Executable, ProcessError
+from llnl.util.lang import memoized
+
+class ABI(object):
+ """This class provides methods to test ABI compatibility between specs.
+ The current implementation is rather rough and could be improved."""
+
+ def architecture_compatible(self, parent, child):
+ """Returns true iff the parent and child specs have ABI compatible architectures."""
+ return not parent.architecture or not child.architecture or parent.architecture == child.architecture
+
+
+ @memoized
+ def _gcc_get_libstdcxx_version(self, version):
+ """Returns gcc ABI compatibility info by getting the library version of
+ a compiler's libstdc++.so or libgcc_s.so"""
+ spec = CompilerSpec("gcc", version)
+ compilers = spack.compilers.compilers_for_spec(spec)
+ if not compilers:
+ return None
+ compiler = compilers[0]
+ rungcc = None
+ libname = None
+ output = None
+ if compiler.cxx:
+ rungcc = Executable(compiler.cxx)
+ libname = "libstdc++.so"
+ elif compiler.cc:
+ rungcc = Executable(compiler.cc)
+ libname = "libgcc_s.so"
+ else:
+ return None
+ try:
+ output = rungcc("--print-file-name=%s" % libname, return_output=True)
+ except ProcessError, e:
+ return None
+ if not output:
+ return None
+ libpath = os.readlink(output.strip())
+ if not libpath:
+ return None
+ return os.path.basename(libpath)
+
+
+ @memoized
+ def _gcc_compiler_compare(self, pversion, cversion):
+ """Returns true iff the gcc version pversion and cversion
+ are ABI compatible."""
+ plib = self._gcc_get_libstdcxx_version(pversion)
+ clib = self._gcc_get_libstdcxx_version(cversion)
+ if not plib or not clib:
+ return False
+ return plib == clib
+
+
+ def _intel_compiler_compare(self, pversion, cversion):
+ """Returns true iff the intel version pversion and cversion
+ are ABI compatible"""
+
+ # Test major and minor versions. Ignore build version.
+ if (len(pversion.version) < 2 or len(cversion.version) < 2):
+ return False
+ return pversion.version[:2] == cversion.version[:2]
+
+
+ def compiler_compatible(self, parent, child, **kwargs):
+ """Returns true iff the compilers for parent and child specs are ABI compatible"""
+ if not parent.compiler or not child.compiler:
+ return True
+
+ if parent.compiler.name != child.compiler.name:
+ # Different compiler families are assumed ABI incompatible
+ return False
+
+ if kwargs.get('loose', False):
+ return True
+
+ # TODO: Can we move the specialized ABI matching stuff
+ # TODO: into compiler classes?
+ for pversion in parent.compiler.versions:
+ for cversion in child.compiler.versions:
+ # For a few compilers use specialized comparisons. Otherwise
+ # match on version match.
+ if pversion.satisfies(cversion):
+ return True
+ elif (parent.compiler.name == "gcc" and
+ self._gcc_compiler_compare(pversion, cversion)):
+ return True
+ elif (parent.compiler.name == "intel" and
+ self._intel_compiler_compare(pversion, cversion)):
+ return True
+ return False
+
+
+ def compatible(self, parent, child, **kwargs):
+ """Returns true iff a parent and child spec are ABI compatible"""
+ loosematch = kwargs.get('loose', False)
+ return self.architecture_compatible(parent, child) and \
+ self.compiler_compatible(parent, child, loose=loosematch)
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index 4564143f83..f0cd50b8df 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -208,7 +208,7 @@ def find_repository(spec, args):
return repo
-def fetch_tarballs(url, name, args):
+def fetch_tarballs(url, name, version):
"""Try to find versions of the supplied archive by scraping the web.
Prompts the user to select how many to download if many are found.
@@ -222,7 +222,7 @@ def fetch_tarballs(url, name, args):
archives_to_fetch = 1
if not versions:
# If the fetch failed for some reason, revert to what the user provided
- versions = { "version" : url }
+ versions = { version : url }
elif len(versions) > 1:
tty.msg("Found %s versions of %s:" % (len(versions), name),
*spack.cmd.elide_list(
@@ -256,7 +256,7 @@ def create(parser, args):
tty.msg("Creating template for package %s" % name)
# Fetch tarballs (prompting user if necessary)
- versions, urls = fetch_tarballs(url, name, args)
+ versions, urls = fetch_tarballs(url, name, version)
# Try to guess what configure system is used.
guesser = ConfigureGuesser()
diff --git a/lib/spack/spack/cmd/diy.py b/lib/spack/spack/cmd/diy.py
index 2c3a8761ab..45f13e4463 100644
--- a/lib/spack/spack/cmd/diy.py
+++ b/lib/spack/spack/cmd/diy.py
@@ -75,8 +75,8 @@ def diy(self, args):
edit_package(spec.name, spack.repo.first_repo(), None, True)
return
- if not spec.version.concrete:
- tty.die("spack diy spec must have a single, concrete version.")
+ if not spec.versions.concrete:
+ tty.die("spack diy spec must have a single, concrete version. Did you forget a package version number?")
spec.concretize()
package = spack.repo.get(spec)
diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py
index 495b638a3a..64214db32d 100644
--- a/lib/spack/spack/compilers/gcc.py
+++ b/lib/spack/spack/compilers/gcc.py
@@ -40,7 +40,8 @@ class Gcc(Compiler):
fc_names = ['gfortran']
# MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
- suffixes = [r'-mp-\d\.\d']
+ # Homebrew and Linuxes may build gcc with -X, -X.Y suffixes
+ suffixes = [r'-mp-\d\.\d', r'-\d\.\d', r'-\d']
# Named wrapper links within spack.build_env_path
link_paths = {'cc' : 'gcc/gcc',
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index 85cdb202d5..8083f91982 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -33,12 +33,16 @@ or user preferences.
TODO: make this customizable and allow users to configure
concretization policies.
"""
+import spack
import spack.spec
import spack.compilers
import spack.architecture
import spack.error
from spack.version import *
-
+from functools import partial
+from spec import DependencyMap
+from itertools import chain
+from spack.config import *
class DefaultConcretizer(object):
"""This class doesn't have any state, it just provides some methods for
@@ -46,10 +50,92 @@ class DefaultConcretizer(object):
default concretization strategies, or you can override all of them.
"""
+ def _valid_virtuals_and_externals(self, spec):
+ """Returns a list of candidate virtual dep providers and external
+ packages that coiuld be used to concretize a spec."""
+ # First construct a list of concrete candidates to replace spec with.
+ candidates = [spec]
+ if spec.virtual:
+ providers = spack.repo.providers_for(spec)
+ if not providers:
+ raise UnsatisfiableProviderSpecError(providers[0], spec)
+ spec_w_preferred_providers = find_spec(
+ spec, lambda(x): spack.pkgsort.spec_has_preferred_provider(x.name, spec.name))
+ if not spec_w_preferred_providers:
+ spec_w_preferred_providers = spec
+ provider_cmp = partial(spack.pkgsort.provider_compare, spec_w_preferred_providers.name, spec.name)
+ candidates = sorted(providers, cmp=provider_cmp)
+
+ # For each candidate package, if it has externals, add those to the usable list.
+ # if it's not buildable, then *only* add the externals.
+ usable = []
+ for cspec in candidates:
+ if is_spec_buildable(cspec):
+ usable.append(cspec)
+ externals = spec_externals(cspec)
+ for ext in externals:
+ if ext.satisfies(spec):
+ usable.append(ext)
+
+ # If nothing is in the usable list now, it's because we aren't
+ # allowed to build anything.
+ if not usable:
+ raise NoBuildError(spec)
+
+ def cmp_externals(a, b):
+ if a.name != b.name:
+ # We're choosing between different providers, so
+ # maintain order from provider sort
+ return candidates.index(a) - candidates.index(b)
+
+ result = cmp_specs(a, b)
+ if result != 0:
+ return result
+
+ # prefer external packages to internal packages.
+ if a.external is None or b.external is None:
+ return -cmp(a.external, b.external)
+ else:
+ return cmp(a.external, b.external)
+
+ usable.sort(cmp=cmp_externals)
+ return usable
+
+
+ def choose_virtual_or_external(self, spec):
+ """Given a list of candidate virtual and external packages, try to
+ find one that is most ABI compatible.
+ """
+ candidates = self._valid_virtuals_and_externals(spec)
+ if not candidates:
+ return candidates
+
+ # Find the nearest spec in the dag that has a compiler. We'll
+ # use that spec to calibrate compiler compatibility.
+ abi_exemplar = find_spec(spec, lambda(x): x.compiler)
+ if not abi_exemplar:
+ abi_exemplar = spec.root
+
+ # Make a list including ABI compatibility of specs with the exemplar.
+ strict = [spack.abi.compatible(c, abi_exemplar) for c in candidates]
+ loose = [spack.abi.compatible(c, abi_exemplar, loose=True) for c in candidates]
+ keys = zip(strict, loose, candidates)
+
+ # Sort candidates from most to least compatibility.
+ # Note:
+ # 1. We reverse because True > False.
+ # 2. Sort is stable, so c's keep their order.
+ keys.sort(key=lambda k:k[:2], reverse=True)
+
+ # Pull the candidates back out and return them in order
+ candidates = [c for s,l,c in keys]
+ return candidates
+
+
def concretize_version(self, spec):
"""If the spec is already concrete, return. Otherwise take
- the most recent available version, and default to the package's
- version if there are no avaialble versions.
+ the preferred version from spackconfig, and default to the package's
+ version if there are no available versions.
TODO: In many cases we probably want to look for installed
versions of each package and use an installed version
@@ -67,20 +153,14 @@ class DefaultConcretizer(object):
# If there are known available versions, return the most recent
# version that satisfies the spec
pkg = spec.package
-
- # Key function to sort versions first by whether they were
- # marked `preferred=True`, then by most recent.
- def preferred_key(v):
- prefer = pkg.versions[v].get('preferred', False)
- return (prefer, v)
-
+ cmp_versions = partial(spack.pkgsort.version_compare, spec.name)
valid_versions = sorted(
[v for v in pkg.versions
if any(v.satisfies(sv) for sv in spec.versions)],
- key=preferred_key)
+ cmp=cmp_versions)
if valid_versions:
- spec.versions = ver([valid_versions[-1]])
+ spec.versions = ver([valid_versions[0]])
else:
# We don't know of any SAFE versions that match the given
# spec. Grab the spec's versions and grab the highest
@@ -134,7 +214,7 @@ class DefaultConcretizer(object):
the default variants from the package specification.
"""
changed = False
- for name, variant in spec.package.variants.items():
+ for name, variant in spec.package_class.variants.items():
if name not in spec.variants:
spec.variants[name] = spack.spec.VariantSpec(name, variant.default)
changed = True
@@ -145,10 +225,10 @@ class DefaultConcretizer(object):
"""If the spec already has a compiler, we're done. If not, then take
the compiler used for the nearest ancestor with a compiler
spec and use that. If the ancestor's compiler is not
- concrete, then give it a valid version. If there is no
- ancestor with a compiler, use the system default compiler.
+ concrete, then used the preferred compiler as specified in
+ spackconfig.
- Intuition: Use the system default if no package that depends on
+ Intuition: Use the spackconfig default if no package that depends on
this one has a strict compiler requirement. Otherwise, try to
build with the compiler that will be used by libraries that
link to this one, to maximize compatibility.
@@ -160,40 +240,91 @@ class DefaultConcretizer(object):
spec.compiler in all_compilers):
return False
- try:
- nearest = next(p for p in spec.traverse(direction='parents')
- if p.compiler is not None).compiler
+ #Find the another spec that has a compiler, or the root if none do
+ other_spec = find_spec(spec, lambda(x) : x.compiler)
+ if not other_spec:
+ other_spec = spec.root
+ other_compiler = other_spec.compiler
+ assert(other_spec)
+
+ # Check if the compiler is already fully specified
+ if other_compiler in all_compilers:
+ spec.compiler = other_compiler.copy()
+ return True
+
+ # Filter the compilers into a sorted list based on the compiler_order from spackconfig
+ compiler_list = all_compilers if not other_compiler else spack.compilers.find(other_compiler)
+ cmp_compilers = partial(spack.pkgsort.compiler_compare, other_spec.name)
+ matches = sorted(compiler_list, cmp=cmp_compilers)
+ if not matches:
+ raise UnavailableCompilerVersionError(other_compiler)
+
+ # copy concrete version into other_compiler
+ spec.compiler = matches[0].copy()
+ assert(spec.compiler.concrete)
+ return True # things changed.
- if not nearest in all_compilers:
- # Take the newest compiler that saisfies the spec
- matches = sorted(spack.compilers.find(nearest))
- if not matches:
- raise UnavailableCompilerVersionError(nearest)
- # copy concrete version into nearest spec
- nearest.versions = matches[-1].versions.copy()
- assert(nearest.concrete)
+def find_spec(spec, condition):
+ """Searches the dag from spec in an intelligent order and looks
+ for a spec that matches a condition"""
+ # First search parents, then search children
+ dagiter = chain(spec.traverse(direction='parents', root=False),
+ spec.traverse(direction='children', root=False))
+ visited = set()
+ for relative in dagiter:
+ if condition(relative):
+ return relative
+ visited.add(id(relative))
- spec.compiler = nearest.copy()
+ # Then search all other relatives in the DAG *except* spec
+ for relative in spec.root.traverse():
+ if relative is spec: continue
+ if id(relative) in visited: continue
+ if condition(relative):
+ return relative
- except StopIteration:
- spec.compiler = spack.compilers.default_compiler().copy()
+ # Finally search spec itself.
+ if condition(spec):
+ return spec
- return True # things changed.
+ return None # Nohting matched the condition.
- def choose_provider(self, spec, providers):
- """This is invoked for virtual specs. Given a spec with a virtual name,
- say "mpi", and a list of specs of possible providers of that spec,
- select a provider and return it.
- """
- assert(spec.virtual)
- assert(providers)
+def cmp_specs(lhs, rhs):
+ # Package name sort order is not configurable, always goes alphabetical
+ if lhs.name != rhs.name:
+ return cmp(lhs.name, rhs.name)
+
+ # Package version is second in compare order
+ pkgname = lhs.name
+ if lhs.versions != rhs.versions:
+ return spack.pkgsort.version_compare(
+ pkgname, lhs.versions, rhs.versions)
+
+ # Compiler is third
+ if lhs.compiler != rhs.compiler:
+ return spack.pkgsort.compiler_compare(
+ pkgname, lhs.compiler, rhs.compiler)
+
+ # Variants
+ if lhs.variants != rhs.variants:
+ return spack.pkgsort.variant_compare(
+ pkgname, lhs.variants, rhs.variants)
+
+ # Architecture
+ if lhs.architecture != rhs.architecture:
+ return spack.pkgsort.architecture_compare(
+ pkgname, lhs.architecture, rhs.architecture)
+
+ # Dependency is not configurable
+ lhash, rhash = hash(lhs), hash(rhs)
+ if lhash != rhash:
+ return -1 if lhash < rhash else 1
+
+ # Equal specs
+ return 0
- index = spack.spec.index_specs(providers)
- first_key = sorted(index.keys())[0]
- latest_version = sorted(index[first_key])[-1]
- return latest_version
class UnavailableCompilerVersionError(spack.error.SpackError):
@@ -211,3 +342,11 @@ class NoValidVersionError(spack.error.SpackError):
def __init__(self, spec):
super(NoValidVersionError, self).__init__(
"There are no valid versions for %s that match '%s'" % (spec.name, spec.versions))
+
+
+class NoBuildError(spack.error.SpackError):
+ """Raised when a package is configured with the buildable option False, but
+ no satisfactory external versions can be found"""
+ def __init__(self, spec):
+ super(NoBuildError, self).__init__(
+ "The spec '%s' is configured as not buildable, and no matching external installs were found" % spec.name)
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index 576a5afa2e..6afd69b3ac 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -129,6 +129,7 @@ from ordereddict_backport import OrderedDict
import llnl.util.tty as tty
from llnl.util.filesystem import mkdirp
+import copy
import spack
from spack.error import SpackError
@@ -194,6 +195,49 @@ section_schemas = {
'default': [],
'items': {
'type': 'string'},},},},
+ 'packages': {
+ '$schema': 'http://json-schema.org/schema#',
+ 'title': 'Spack package configuration file schema',
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'patternProperties': {
+ r'packages:?': {
+ 'type': 'object',
+ 'default': {},
+ 'additionalProperties': False,
+ 'patternProperties': {
+ r'\w[\w-]*': { # package name
+ 'type': 'object',
+ 'default': {},
+ 'additionalProperties': False,
+ 'properties': {
+ 'version': {
+ 'type' : 'array',
+ 'default' : [],
+ 'items' : { 'anyOf' : [ { 'type' : 'string' },
+ { 'type' : 'number'}]}}, #version strings
+ 'compiler': {
+ 'type' : 'array',
+ 'default' : [],
+ 'items' : { 'type' : 'string' } }, #compiler specs
+ 'buildable': {
+ 'type': 'boolean',
+ 'default': True,
+ },
+ 'providers': {
+ 'type': 'object',
+ 'default': {},
+ 'additionalProperties': False,
+ 'patternProperties': {
+ r'\w[\w-]*': {
+ 'type' : 'array',
+ 'default' : [],
+ 'items' : { 'type' : 'string' },},},},
+ 'paths': {
+ 'type' : 'object',
+ 'default' : {},
+ }
+ },},},},},}
}
"""OrderedDict of config scopes keyed by name.
@@ -494,6 +538,39 @@ def print_section(section):
raise ConfigError("Error reading configuration: %s" % section)
+def spec_externals(spec):
+ """Return a list of external specs (with external directory path filled in),
+ one for each known external installation."""
+ allpkgs = get_config('packages')
+ name = spec.name
+
+ external_specs = []
+ pkg_paths = allpkgs.get(name, {}).get('paths', None)
+ if not pkg_paths:
+ return []
+
+ for external_spec, path in pkg_paths.iteritems():
+ if not path:
+ # skip entries without paths (avoid creating extra Specs)
+ continue
+
+ external_spec = spack.spec.Spec(external_spec, external=path)
+ if external_spec.satisfies(spec):
+ external_specs.append(external_spec)
+ return external_specs
+
+
+def is_spec_buildable(spec):
+ """Return true if the spec pkgspec is configured as buildable"""
+ allpkgs = get_config('packages')
+ name = spec.name
+ if not spec.name in allpkgs:
+ return True
+ if not 'buildable' in allpkgs[spec.name]:
+ return True
+ return allpkgs[spec.name]['buildable']
+
+
class ConfigError(SpackError): pass
class ConfigFileError(ConfigError): pass
@@ -509,7 +586,7 @@ class ConfigFormatError(ConfigError):
# Try to get line number from erroneous instance and its parent
instance_mark = getattr(validation_error.instance, '_start_mark', None)
parent_mark = getattr(validation_error.parent, '_start_mark', None)
- path = getattr(validation_error, 'path', None)
+ path = [str(s) for s in getattr(validation_error, 'path', None)]
# Try really hard to get the parent (which sometimes is not
# set) This digs it out of the validated structure if it's not
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 242eb1afa0..39ee4e203d 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -198,6 +198,10 @@ class YamlDirectoryLayout(DirectoryLayout):
def relative_path_for_spec(self, spec):
_check_concrete(spec)
+
+ if spec.external:
+ return spec.external
+
dir_name = "%s-%s-%s" % (
spec.name,
spec.version,
diff --git a/lib/spack/spack/hooks/sbang.py b/lib/spack/spack/hooks/sbang.py
index 3390ecea29..d78adb576e 100644
--- a/lib/spack/spack/hooks/sbang.py
+++ b/lib/spack/spack/hooks/sbang.py
@@ -35,7 +35,7 @@ import spack.modules
shebang_limit = 127
def shebang_too_long(path):
- """Detects whether an file has a shebang line that is too long."""
+ """Detects whether a file has a shebang line that is too long."""
with open(path, 'r') as script:
bytes = script.read(2)
if bytes != '#!':
@@ -47,14 +47,21 @@ def shebang_too_long(path):
def filter_shebang(path):
"""Adds a second shebang line, using sbang, at the beginning of a file."""
+ with open(path, 'r') as original_file:
+ original = original_file.read()
+
+ # This line will be prepended to file
+ new_sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.spack_root
+
+ # Skip files that are already using sbang.
+ if original.startswith(new_sbang_line):
+ return
+
backup = path + ".shebang.bak"
os.rename(path, backup)
- with open(backup, 'r') as bak_file:
- original = bak_file.read()
-
with open(path, 'w') as new_file:
- new_file.write('#!/bin/bash %s/bin/sbang\n' % spack.spack_root)
+ new_file.write(new_sbang_line)
new_file.write(original)
copy_mode(backup, path)
@@ -63,15 +70,29 @@ def filter_shebang(path):
tty.warn("Patched overly long shebang in %s" % path)
+def filter_shebangs_in_directory(directory):
+ for file in os.listdir(directory):
+ path = os.path.join(directory, file)
+
+ # only handle files
+ if not os.path.isfile(path):
+ continue
+
+ # only handle links that resolve within THIS package's prefix.
+ if os.path.islink(path):
+ real_path = os.path.realpath(path)
+ if not real_path.startswith(directory + os.sep):
+ continue
+
+ # test the file for a long shebang, and filter
+ if shebang_too_long(path):
+ filter_shebang(path)
+
+
def post_install(pkg):
"""This hook edits scripts so that they call /bin/bash
$spack_prefix/bin/sbang instead of something longer than the
shebang limit."""
if not os.path.isdir(pkg.prefix.bin):
return
-
- for file in os.listdir(pkg.prefix.bin):
- path = os.path.join(pkg.prefix.bin, file)
- if shebang_too_long(path):
- filter_shebang(path)
-
+ filter_shebangs_in_directory(pkg.prefix.bin)
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 4c458522e0..b488e4c49d 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -318,6 +318,18 @@ class Package(object):
"""Most packages are NOT extendable. Set to True if you want extensions."""
extendable = False
+ """List of prefix-relative file paths (or a single path). If these do
+ not exist after install, or if they exist but are not files,
+ sanity checks fail.
+ """
+ sanity_check_is_file = []
+
+ """List of prefix-relative directory paths (or a single path). If
+ these do not exist after install, or if they exist but are not
+ directories, sanity checks will fail.
+ """
+ sanity_check_is_dir = []
+
def __init__(self, spec):
# this determines how the package should be built.
@@ -825,7 +837,7 @@ class Package(object):
def do_install(self,
- keep_prefix=False, keep_stage=None, ignore_deps=False,
+ keep_prefix=False, keep_stage=False, ignore_deps=False,
skip_patch=False, verbose=False, make_jobs=None, fake=False):
"""Called by commands to install a package and its dependencies.
@@ -834,8 +846,9 @@ class Package(object):
Args:
keep_prefix -- Keep install prefix on failure. By default, destroys it.
- keep_stage -- Set to True or false to always keep or always delete stage.
- By default, stage is destroyed only if there are no exceptions.
+ keep_stage -- By default, stage is destroyed only if there are no
+ exceptions during build. Set to True to keep the stage
+ even with exceptions.
ignore_deps -- Do not install dependencies before installing this package.
fake -- Don't really build -- install fake stub files instead.
skip_patch -- Skip patch stage of build if True.
@@ -845,6 +858,11 @@ class Package(object):
if not self.spec.concrete:
raise ValueError("Can only install concrete packages.")
+ # No installation needed if package is external
+ if self.spec.external:
+ tty.msg("%s is externally installed in %s" % (self.name, self.spec.external))
+ return
+
# Ensure package is not already installed
if spack.install_layout.check_installed(self.spec):
tty.msg("%s is already installed in %s" % (self.name, self.prefix))
@@ -903,7 +921,7 @@ class Package(object):
raise e
# Ensure that something was actually installed.
- self._sanity_check_install()
+ self.sanity_check_prefix()
# Copy provenance into the install directory on success
log_install_path = spack.install_layout.build_log_path(self.spec)
@@ -946,7 +964,21 @@ class Package(object):
spack.hooks.post_install(self)
- def _sanity_check_install(self):
+ def sanity_check_prefix(self):
+ """This function checks whether install succeeded."""
+ def check_paths(path_list, filetype, predicate):
+ if isinstance(path_list, basestring):
+ path_list = [path_list]
+
+ for path in path_list:
+ abs_path = os.path.join(self.prefix, path)
+ if not predicate(abs_path):
+ raise InstallError("Install failed for %s. No such %s in prefix: %s"
+ % (self.name, filetype, path))
+
+ check_paths(self.sanity_check_is_file, 'file', os.path.isfile)
+ check_paths(self.sanity_check_is_dir, 'directory', os.path.isdir)
+
installed = set(os.listdir(self.prefix))
installed.difference_update(spack.install_layout.hidden_file_paths)
if not installed:
diff --git a/lib/spack/spack/preferred_packages.py b/lib/spack/spack/preferred_packages.py
new file mode 100644
index 0000000000..4d8526c75f
--- /dev/null
+++ b/lib/spack/spack/preferred_packages.py
@@ -0,0 +1,175 @@
+##############################################################################
+# 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 spack
+from spack.version import *
+
+class PreferredPackages(object):
+ _default_order = {'compiler' : [ 'gcc', 'intel', 'clang', 'pgi', 'xlc' ] } # Arbitrary, but consistent
+
+ def __init__(self):
+ self.preferred = spack.config.get_config('packages')
+ self._spec_for_pkgname_cache = {}
+
+ # Given a package name, sort component (e.g, version, compiler, ...), and
+ # a second_key (used by providers), return the list
+ def _order_for_package(self, pkgname, component, second_key, test_all=True):
+ pkglist = [pkgname]
+ if test_all:
+ pkglist.append('all')
+ for pkg in pkglist:
+ order = self.preferred.get(pkg, {}).get(component, {})
+ if type(order) is dict:
+ order = order.get(second_key, {})
+ if not order:
+ continue
+ return [str(s).strip() for s in order]
+ return []
+
+
+ # A generic sorting function. Given a package name and sort
+ # component, return less-than-0, 0, or greater-than-0 if
+ # a is respectively less-than, equal to, or greater than b.
+ def _component_compare(self, pkgname, component, a, b, reverse_natural_compare, second_key):
+ if a is None:
+ return -1
+ if b is None:
+ return 1
+ orderlist = self._order_for_package(pkgname, component, second_key)
+ a_in_list = str(a) in orderlist
+ b_in_list = str(b) in orderlist
+ if a_in_list and not b_in_list:
+ return -1
+ elif b_in_list and not a_in_list:
+ return 1
+
+ cmp_a = None
+ cmp_b = None
+ reverse = None
+ if not a_in_list and not b_in_list:
+ cmp_a = a
+ cmp_b = b
+ reverse = -1 if reverse_natural_compare else 1
+ else:
+ cmp_a = orderlist.index(str(a))
+ cmp_b = orderlist.index(str(b))
+ reverse = 1
+
+ if cmp_a < cmp_b:
+ return -1 * reverse
+ elif cmp_a > cmp_b:
+ return 1 * reverse
+ else:
+ return 0
+
+
+ # A sorting function for specs. Similar to component_compare, but
+ # a and b are considered to match entries in the sorting list if they
+ # satisfy the list component.
+ def _spec_compare(self, pkgname, component, a, b, reverse_natural_compare, second_key):
+ if not a or not a.concrete:
+ return -1
+ if not b or not b.concrete:
+ return 1
+ specs = self._spec_for_pkgname(pkgname, component, second_key)
+ a_index = None
+ b_index = None
+ reverse = -1 if reverse_natural_compare else 1
+ for i, cspec in enumerate(specs):
+ if a_index == None and (cspec.satisfies(a) or a.satisfies(cspec)):
+ a_index = i
+ if b_index:
+ break
+ if b_index == None and (cspec.satisfies(b) or b.satisfies(cspec)):
+ b_index = i
+ if a_index:
+ break
+
+ if a_index != None and b_index == None: return -1
+ elif a_index == None and b_index != None: return 1
+ elif a_index != None and b_index == a_index: return -1 * cmp(a, b)
+ elif a_index != None and b_index != None and a_index != b_index: return cmp(a_index, b_index)
+ else: return cmp(a, b) * reverse
+
+
+
+ # Given a sort order specified by the pkgname/component/second_key, return
+ # a list of CompilerSpecs, VersionLists, or Specs for that sorting list.
+ def _spec_for_pkgname(self, pkgname, component, second_key):
+ key = (pkgname, component, second_key)
+ if not key in self._spec_for_pkgname_cache:
+ pkglist = self._order_for_package(pkgname, component, second_key)
+ if not pkglist:
+ if component in self._default_order:
+ pkglist = self._default_order[component]
+ if component == 'compiler':
+ self._spec_for_pkgname_cache[key] = [spack.spec.CompilerSpec(s) for s in pkglist]
+ elif component == 'version':
+ self._spec_for_pkgname_cache[key] = [VersionList(s) for s in pkglist]
+ else:
+ self._spec_for_pkgname_cache[key] = [spack.spec.Spec(s) for s in pkglist]
+ return self._spec_for_pkgname_cache[key]
+
+
+ def provider_compare(self, pkgname, provider_str, a, b):
+ """Return less-than-0, 0, or greater than 0 if a is respecively less-than, equal-to, or
+ greater-than b. A and b are possible implementations of provider_str.
+ One provider is less-than another if it is preferred over the other.
+ For example, provider_compare('scorep', 'mpi', 'mvapich', 'openmpi') would return -1 if
+ mvapich should be preferred over openmpi for scorep."""
+ return self._spec_compare(pkgname, 'providers', a, b, False, provider_str)
+
+
+ def spec_has_preferred_provider(self, pkgname, provider_str):
+ """Return True iff the named package has a list of preferred provider"""
+ return bool(self._order_for_package(pkgname, 'providers', provider_str, False))
+
+
+ def version_compare(self, pkgname, a, b):
+ """Return less-than-0, 0, or greater than 0 if version a of pkgname is
+ respecively less-than, equal-to, or greater-than version b of pkgname.
+ One version is less-than another if it is preferred over the other."""
+ return self._spec_compare(pkgname, 'version', a, b, True, None)
+
+
+ def variant_compare(self, pkgname, a, b):
+ """Return less-than-0, 0, or greater than 0 if variant a of pkgname is
+ respecively less-than, equal-to, or greater-than variant b of pkgname.
+ One variant is less-than another if it is preferred over the other."""
+ return self._component_compare(pkgname, 'variant', a, b, False, None)
+
+
+ def architecture_compare(self, pkgname, a, b):
+ """Return less-than-0, 0, or greater than 0 if architecture a of pkgname is
+ respecively less-than, equal-to, or greater-than architecture b of pkgname.
+ One architecture is less-than another if it is preferred over the other."""
+ return self._component_compare(pkgname, 'architecture', a, b, False, None)
+
+
+ def compiler_compare(self, pkgname, a, b):
+ """Return less-than-0, 0, or greater than 0 if compiler a of pkgname is
+ respecively less-than, equal-to, or greater-than compiler b of pkgname.
+ One compiler is less-than another if it is preferred over the other."""
+ return self._spec_compare(pkgname, 'compiler', a, b, False, None)
diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py
index 3c3ba08bcc..d2fdc937f7 100644
--- a/lib/spack/spack/repository.py
+++ b/lib/spack/spack/repository.py
@@ -316,6 +316,11 @@ class RepoPath(object):
return self.repo_for_pkg(spec).get(spec)
+ def get_pkg_class(self, pkg_name):
+ """Find a class for the spec's package and return the class object."""
+ return self.repo_for_pkg(pkg_name).get_pkg_class(pkg_name)
+
+
@_autospec
def dump_provenance(self, spec, path):
"""Dump provenance information for a spec to a particular path.
@@ -550,7 +555,7 @@ class Repo(object):
key = hash(spec)
if new or key not in self._instances:
- package_class = self._get_pkg_class(spec.name)
+ package_class = self.get_pkg_class(spec.name)
try:
copy = spec.copy() # defensive copy. Package owns its spec.
self._instances[key] = package_class(copy)
@@ -715,7 +720,7 @@ class Repo(object):
return self._modules[pkg_name]
- def _get_pkg_class(self, pkg_name):
+ def get_pkg_class(self, pkg_name):
"""Get the class for the package out of its module.
First loads (or fetches from cache) a module for the
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 10e246bf2e..d04135860e 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -353,7 +353,7 @@ class VariantMap(HashableMap):
@property
def concrete(self):
return self.spec._concrete or all(
- v in self for v in self.spec.package.variants)
+ v in self for v in self.spec.package_class.variants)
def copy(self):
@@ -418,9 +418,12 @@ class Spec(object):
# cases we've read them from a file want to assume normal.
# This allows us to manipulate specs that Spack doesn't have
# package.py files for.
- self._normal = kwargs.get('normal', False)
+ self._normal = kwargs.get('normal', False)
self._concrete = kwargs.get('concrete', False)
+ # Allow a spec to be constructed with an external path.
+ self.external = kwargs.get('external', None)
+
# This allows users to construct a spec DAG with literals.
# Note that given two specs a and b, Spec(a) copies a, but
# Spec(a, b) will copy a but just add b as a dep.
@@ -498,6 +501,14 @@ class Spec(object):
@property
+ def package_class(self):
+ """Internal package call gets only the class object for a package.
+ Use this to just get package metadata.
+ """
+ return spack.repo.get_pkg_class(self.name)
+
+
+ @property
def virtual(self):
"""Right now, a spec is virtual if no package exists with its name.
@@ -770,12 +781,11 @@ class Spec(object):
# Concretize virtual dependencies last. Because they're added
# to presets below, their constraints will all be merged, but we'll
# still need to select a concrete package later.
- if not self.virtual:
- changed |= any(
- (spack.concretizer.concretize_architecture(self),
- spack.concretizer.concretize_compiler(self),
- spack.concretizer.concretize_version(self),
- spack.concretizer.concretize_variants(self)))
+ changed |= any(
+ (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 +796,30 @@ class Spec(object):
"""Replace this virtual spec with a concrete spec."""
assert(self.virtual)
for name, dependent in self.dependents.items():
+ # remove self from all dependents.
+ del dependent.dependencies[self.name]
+
+ # add the replacement, unless it is already a dep of dependent.
+ if concrete.name not in dependent.dependencies:
+ dependent._add_dependency(concrete)
+
+
+ def _replace_node(self, replacement):
+ """Replace this spec with another.
+
+ Connects all dependents of this spec to its replacement, and
+ disconnects this spec from any dependencies it has. New spec
+ will have any dependencies the replacement had, and may need
+ to be normalized.
+
+ """
+ for name, dependent in self.dependents.items():
del dependent.dependencies[self.name]
- dependent._add_dependency(concrete)
+ dependent._add_dependency(replacement)
+
+ for name, dep in self.dependencies.items():
+ del dep.dependents[self.name]
+ del self.dependencies[dep.name]
def _expand_virtual_packages(self):
@@ -807,22 +839,81 @@ class Spec(object):
this are infrequent, but should implement this before it is
a problem.
"""
+ # Make an index of stuff this spec already provides
+ self_index = ProviderIndex(self.traverse(), restrict=True)
+
changed = False
- while True:
- virtuals =[v for v in self.traverse() if v.virtual]
- if not virtuals:
- return changed
+ done = False
+ while not done:
+ done = True
+ for spec in list(self.traverse()):
+ replacement = None
+ if spec.virtual:
+ replacement = self._find_provider(spec, self_index)
+ if replacement:
+ # TODO: may break if in-place on self but
+ # shouldn't happen if root is traversed first.
+ spec._replace_with(replacement)
+ done=False
+ break
+
+ if not replacement:
+ # Get a list of possible replacements in order of preference.
+ candidates = spack.concretizer.choose_virtual_or_external(spec)
+
+ # Try the replacements in order, skipping any that cause
+ # satisfiability problems.
+ for replacement in candidates:
+ if replacement is spec:
+ break
+
+ # Replace spec with the candidate and normalize
+ copy = self.copy()
+ copy[spec.name]._dup(replacement.copy(deps=False))
+
+ try:
+ # If there are duplicate providers or duplicate provider
+ # deps, consolidate them and merge constraints.
+ copy.normalize(force=True)
+ break
+ except SpecError as e:
+ # On error, we'll try the next replacement.
+ continue
+
+ # If replacement is external then trim the dependencies
+ if replacement.external:
+ if (spec.dependencies):
+ changed = True
+ spec.dependencies = DependencyMap()
+ replacement.dependencies = DependencyMap()
+
+ # TODO: could this and the stuff in _dup be cleaned up?
+ def feq(cfield, sfield):
+ return (not cfield) or (cfield == sfield)
+
+ if replacement is spec or (feq(replacement.name, spec.name) and
+ feq(replacement.versions, spec.versions) and
+ feq(replacement.compiler, spec.compiler) and
+ feq(replacement.architecture, spec.architecture) and
+ feq(replacement.dependencies, spec.dependencies) and
+ feq(replacement.variants, spec.variants) and
+ feq(replacement.external, spec.external)):
+ continue
+
+ # Refine this spec to the candidate. This uses
+ # replace_with AND dup so that it can work in
+ # place. TODO: make this more efficient.
+ if spec.virtual:
+ spec._replace_with(replacement)
+ changed = True
+ if spec._dup(replacement, deps=False, cleardeps=False):
+ changed = True
- for spec in virtuals:
- providers = spack.repo.providers_for(spec)
- concrete = spack.concretizer.choose_provider(spec, providers)
- concrete = concrete.copy()
- spec._replace_with(concrete)
- changed = True
+ self_index.update(spec)
+ done=False
+ break
- # If there are duplicate providers or duplicate provider deps, this
- # consolidates them and merge constraints.
- changed |= self.normalize(force=True)
+ return changed
def concretize(self):
@@ -837,6 +928,7 @@ class Spec(object):
with requirements of its pacakges. See flatten() and normalize() for
more details on this.
"""
+
if self._concrete:
return
@@ -844,7 +936,7 @@ class Spec(object):
force = False
while changed:
- changes = (self.normalize(force=force),
+ changes = (self.normalize(force),
self._expand_virtual_packages(),
self._concretize_helper())
changed = any(changes)
@@ -970,8 +1062,8 @@ class Spec(object):
def _find_provider(self, vdep, provider_index):
"""Find provider for a virtual spec in the provider index.
- Raise an exception if there is a conflicting virtual
- dependency already in this spec.
+ Raise an exception if there is a conflicting virtual
+ dependency already in this spec.
"""
assert(vdep.virtual)
providers = provider_index.providers_for(vdep)
@@ -1012,17 +1104,14 @@ class Spec(object):
"""
changed = False
- # If it's a virtual dependency, try to find a provider and
- # merge that.
+ # If it's a virtual dependency, try to find an existing
+ # provider in the spec, and merge that.
if dep.virtual:
visited.add(dep.name)
provider = self._find_provider(dep, provider_index)
if provider:
dep = provider
-
else:
- # if it's a real dependency, check whether it provides
- # something already required in the spec.
index = ProviderIndex([dep], restrict=True)
for vspec in (v for v in spec_deps.values() if v.virtual):
if index.providers_for(vspec):
@@ -1069,7 +1158,7 @@ class Spec(object):
# if we descend into a virtual spec, there's nothing more
# to normalize. Concretize will finish resolving it later.
- if self.virtual:
+ if self.virtual or self.external:
return False
# Combine constraints from package deps with constraints from
@@ -1119,13 +1208,14 @@ class Spec(object):
# Get all the dependencies into one DependencyMap
spec_deps = self.flat_dependencies(copy=False)
- # Initialize index of virtual dependency providers
- index = ProviderIndex(spec_deps.values(), restrict=True)
+ # Initialize index of virtual dependency providers if
+ # concretize didn't pass us one already
+ provider_index = ProviderIndex(spec_deps.values(), restrict=True)
# traverse the package DAG and fill out dependencies according
# to package files & their 'when' specs
visited = set()
- any_change = self._normalize_helper(visited, spec_deps, index)
+ any_change = self._normalize_helper(visited, spec_deps, provider_index)
# If there are deps specified but not visited, they're not
# actually deps of this package. Raise an error.
@@ -1163,7 +1253,7 @@ class Spec(object):
# Ensure that variants all exist.
for vname, variant in spec.variants.items():
- if vname not in spec.package.variants:
+ if vname not in spec.package_class.variants:
raise UnknownVariantError(spec.name, vname)
@@ -1404,15 +1494,25 @@ class Spec(object):
Whether deps should be copied too. Set to false to copy a
spec but not its dependencies.
"""
+ # We don't count dependencies as changes here
+ changed = True
+ if hasattr(self, 'name'):
+ changed = (self.name != other.name and self.versions != other.versions and
+ self.architecture != other.architecture and self.compiler != other.compiler and
+ self.variants != other.variants and self._normal != other._normal and
+ self.concrete != other.concrete and self.external != other.external)
+
# Local node attributes get copied first.
self.name = other.name
self.versions = other.versions.copy()
self.architecture = other.architecture
self.compiler = other.compiler.copy() if other.compiler else None
- self.dependents = DependencyMap()
- self.dependencies = DependencyMap()
+ if kwargs.get('cleardeps', True):
+ self.dependents = DependencyMap()
+ self.dependencies = DependencyMap()
self.variants = other.variants.copy()
self.variants.spec = self
+ self.external = other.external
self.namespace = other.namespace
# If we copy dependencies, preserve DAG structure in the new spec
@@ -1431,6 +1531,8 @@ class Spec(object):
# Since we preserved structure, we can copy _normal safely.
self._normal = other._normal
self._concrete = other._concrete
+ self.external = other.external
+ return changed
def copy(self, **kwargs):
@@ -1571,14 +1673,28 @@ class Spec(object):
$_ Package name
$. Full package name (with namespace)
- $@ Version
- $% Compiler
- $%@ Compiler & compiler version
+ $@ Version with '@' prefix
+ $% Compiler with '%' prefix
+ $%@ Compiler with '%' prefix & compiler version with '@' prefix
$+ Options
- $= Architecture
- $# 7-char prefix of DAG hash
+ $= Architecture with '=' prefix
+ $# 7-char prefix of DAG hash with '-' prefix
$$ $
+ You can also use full-string versions, which leave off the prefixes:
+
+ ${PACKAGE} Package name
+ ${VERSION} Version
+ ${COMPILER} Full compiler string
+ ${COMPILERNAME} Compiler name
+ ${COMPILERVER} Compiler version
+ ${OPTIONS} Options
+ ${ARCHITECTURE} Architecture
+ ${SHA1} Dependencies 8-char sha1 prefix
+
+ ${SPACK_ROOT} The spack root directory
+ ${SPACK_INSTALL} The default spack install directory, ${SPACK_PREFIX}/opt
+
Optionally you can provide a width, e.g. $20_ for a 20-wide name.
Like printf, you can provide '-' for left justification, e.g.
$-20_ for a left-justified name.
@@ -1594,7 +1710,8 @@ class Spec(object):
color = kwargs.get('color', False)
length = len(format_string)
out = StringIO()
- escape = compiler = False
+ named = escape = compiler = False
+ named_str = fmt = ''
def write(s, c):
if color:
@@ -1636,9 +1753,12 @@ class Spec(object):
elif c == '#':
out.write('-' + fmt % (self.dag_hash(7)))
elif c == '$':
- if fmt != '':
+ if fmt != '%s':
raise ValueError("Can't use format width with $$.")
out.write('$')
+ elif c == '{':
+ named = True
+ named_str = ''
escape = False
elif compiler:
@@ -1652,6 +1772,43 @@ class Spec(object):
out.write(c)
compiler = False
+ elif named:
+ if not c == '}':
+ if i == length - 1:
+ raise ValueError("Error: unterminated ${ in format: '%s'"
+ % format_string)
+ named_str += c
+ continue;
+ if named_str == 'PACKAGE':
+ write(fmt % self.name, '@')
+ if named_str == 'VERSION':
+ if self.versions and self.versions != _any_version:
+ write(fmt % str(self.versions), '@')
+ elif named_str == 'COMPILER':
+ if self.compiler:
+ write(fmt % self.compiler, '%')
+ elif named_str == 'COMPILERNAME':
+ if self.compiler:
+ write(fmt % self.compiler.name, '%')
+ elif named_str == 'COMPILERVER':
+ if self.compiler:
+ write(fmt % self.compiler.versions, '%')
+ elif named_str == 'OPTIONS':
+ if self.variants:
+ write(fmt % str(self.variants), '+')
+ elif named_str == 'ARCHITECTURE':
+ if self.architecture:
+ write(fmt % str(self.architecture), '=')
+ elif named_str == 'SHA1':
+ if self.dependencies:
+ out.write(fmt % str(self.dag_hash(7)))
+ elif named_str == 'SPACK_ROOT':
+ out.write(fmt % spack.prefix)
+ elif named_str == 'SPACK_INSTALL':
+ out.write(fmt % spack.install_path)
+
+ named = False
+
elif c == '$':
escape = True
if i == length - 1:
@@ -1782,6 +1939,7 @@ class SpecParser(spack.parse.Parser):
spec.variants = VariantMap(spec)
spec.architecture = None
spec.compiler = None
+ spec.external = None
spec.dependents = DependencyMap()
spec.dependencies = DependencyMap()
spec.namespace = spec_namespace
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 5354135e6a..f88f82fc2d 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -88,7 +88,8 @@ class Stage(object):
similar, and are intended to persist for only one run of spack.
"""
- def __init__(self, url_or_fetch_strategy, name=None, mirror_path=None, keep=None):
+ def __init__(self, url_or_fetch_strategy,
+ name=None, mirror_path=None, keep=False):
"""Create a stage object.
Parameters:
url_or_fetch_strategy
@@ -108,10 +109,9 @@ class Stage(object):
keep
By default, when used as a context manager, the Stage
- is cleaned up when everything goes well, and it is
- kept intact when an exception is raised. You can
- override this behavior by setting keep to True
- (always keep) or False (always delete).
+ is deleted on exit when no exceptions are raised.
+ Pass True to keep the stage intact even if no
+ exceptions are raised.
"""
# TODO: fetch/stage coupling needs to be reworked -- the logic
# TODO: here is convoluted and not modular enough.
@@ -166,12 +166,8 @@ class Stage(object):
Returns:
Boolean
"""
- if self.keep is None:
- # Default: delete when there are no exceptions.
- if exc_type is None: self.destroy()
-
- elif not self.keep:
- # Overridden. Either always keep or always delete.
+ # Delete when there are no exceptions, unless asked to keep.
+ if exc_type is None and not self.keep:
self.destroy()
@@ -195,8 +191,8 @@ class Stage(object):
real_tmp = os.path.realpath(self.tmp_root)
if spack.use_tmp_stage:
- # If we're using a tmp dir, it's a link, and it points at the right spot,
- # then keep it.
+ # If we're using a tmp dir, it's a link, and it points at the
+ # right spot, then keep it.
if (real_path.startswith(real_tmp) and os.path.exists(real_path)):
return False
else:
@@ -441,7 +437,7 @@ class StageComposite:
def __exit__(self, exc_type, exc_val, exc_tb):
for item in reversed(self):
- item.keep = getattr(self, 'keep', None)
+ item.keep = getattr(self, 'keep', False)
item.__exit__(exc_type, exc_val, exc_tb)
#
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index 4b9a361d4b..d5d8b64765 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -65,7 +65,8 @@ test_names = ['versions',
'lock',
'database',
'namespace_trie',
- 'yaml']
+ 'yaml',
+ 'sbang']
def list_tests():
@@ -87,20 +88,20 @@ def run(names, outputDir, verbose=False):
"Valid names are:")
colify(sorted(test_names), indent=4)
sys.exit(1)
-
+
tally = Tally()
for test in names:
module = 'spack.test.' + test
print module
-
+
tty.msg("Running test: %s" % test)
-
+
runOpts = ["--with-%s" % spack.test.tally_plugin.Tally.name]
-
+
if outputDir:
xmlOutputFname = "unittests-{0}.xml".format(test)
xmlOutputPath = join_path(outputDir, xmlOutputFname)
- runOpts += ["--with-xunit",
+ runOpts += ["--with-xunit",
"--xunit-file={0}".format(xmlOutputPath)]
argv = [""] + runOpts + [module]
result = nose.run(argv=argv, addplugins=[tally])
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 794344fb6a..f264faf17a 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -24,6 +24,7 @@
##############################################################################
import spack
from spack.spec import Spec, CompilerSpec
+from spack.concretize import find_spec
from spack.test.mock_packages_test import *
class ConcretizeTest(MockPackagesTest):
@@ -141,6 +142,34 @@ class ConcretizeTest(MockPackagesTest):
for spec in spack.repo.providers_for('mpi@3')))
+ def test_concretize_two_virtuals(self):
+ """Test a package with multiple virtual dependencies."""
+ s = Spec('hypre').concretize()
+
+
+ def test_concretize_two_virtuals_with_one_bound(self):
+ """Test a package with multiple virtual dependencies and one preset."""
+ s = Spec('hypre ^openblas').concretize()
+
+
+ def test_concretize_two_virtuals_with_two_bound(self):
+ """Test a package with multiple virtual dependencies and two of them preset."""
+ s = Spec('hypre ^openblas ^netlib-lapack').concretize()
+
+
+ def test_concretize_two_virtuals_with_dual_provider(self):
+ """Test a package with multiple virtual dependencies and force a provider
+ that provides both."""
+ s = Spec('hypre ^openblas-with-lapack').concretize()
+
+
+ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(self):
+ """Test a package with multiple virtual dependencies and force a provider
+ that provides both, and another conflicting package that provides one."""
+ s = Spec('hypre ^openblas-with-lapack ^netlib-lapack')
+ self.assertRaises(spack.spec.MultipleProviderError, s.concretize)
+
+
def test_virtual_is_fully_expanded_for_callpath(self):
# force dependence on fake "zmpi" by asking for MPI 10.0
spec = Spec('callpath ^mpi@10.0')
@@ -190,3 +219,93 @@ class ConcretizeTest(MockPackagesTest):
# TODO: not exactly the syntax I would like.
self.assertTrue(spec['libdwarf'].compiler.satisfies('clang'))
self.assertTrue(spec['libelf'].compiler.satisfies('clang'))
+
+
+ def test_external_package(self):
+ spec = Spec('externaltool')
+ spec.concretize()
+
+ self.assertEqual(spec['externaltool'].external, '/path/to/external_tool')
+ self.assertFalse('externalprereq' in spec)
+ self.assertTrue(spec['externaltool'].compiler.satisfies('gcc'))
+
+
+ def test_nobuild_package(self):
+ got_error = False
+ spec = Spec('externaltool%clang')
+ try:
+ spec.concretize()
+ except spack.concretize.NoBuildError:
+ got_error = True
+ self.assertTrue(got_error)
+
+
+ def test_external_and_virtual(self):
+ spec = Spec('externaltest')
+ spec.concretize()
+ self.assertEqual(spec['externaltool'].external, '/path/to/external_tool')
+ self.assertEqual(spec['stuff'].external, '/path/to/external_virtual_gcc')
+ self.assertTrue(spec['externaltool'].compiler.satisfies('gcc'))
+ self.assertTrue(spec['stuff'].compiler.satisfies('gcc'))
+
+
+ def test_find_spec_parents(self):
+ """Tests the spec finding logic used by concretization. """
+ s = Spec('a +foo',
+ Spec('b +foo',
+ Spec('c'),
+ Spec('d +foo')),
+ Spec('e +foo'))
+
+ self.assertEqual('a', find_spec(s['b'], lambda s: '+foo' in s).name)
+
+
+ def test_find_spec_children(self):
+ s = Spec('a',
+ Spec('b +foo',
+ Spec('c'),
+ Spec('d +foo')),
+ Spec('e +foo'))
+ self.assertEqual('d', find_spec(s['b'], lambda s: '+foo' in s).name)
+ s = Spec('a',
+ Spec('b +foo',
+ Spec('c +foo'),
+ Spec('d')),
+ Spec('e +foo'))
+ self.assertEqual('c', find_spec(s['b'], lambda s: '+foo' in s).name)
+
+
+ def test_find_spec_sibling(self):
+ s = Spec('a',
+ Spec('b +foo',
+ Spec('c'),
+ Spec('d')),
+ Spec('e +foo'))
+ self.assertEqual('e', find_spec(s['b'], lambda s: '+foo' in s).name)
+ self.assertEqual('b', find_spec(s['e'], lambda s: '+foo' in s).name)
+
+ s = Spec('a',
+ Spec('b +foo',
+ Spec('c'),
+ Spec('d')),
+ Spec('e',
+ Spec('f +foo')))
+ self.assertEqual('f', find_spec(s['b'], lambda s: '+foo' in s).name)
+
+
+ def test_find_spec_self(self):
+ s = Spec('a',
+ Spec('b +foo',
+ Spec('c'),
+ Spec('d')),
+ Spec('e'))
+ self.assertEqual('b', find_spec(s['b'], lambda s: '+foo' in s).name)
+
+
+ def test_find_spec_none(self):
+ s = Spec('a',
+ Spec('b',
+ Spec('c'),
+ Spec('d')),
+ Spec('e'))
+ self.assertEqual(None, find_spec(s['b'], lambda s: '+foo' in s))
diff --git a/lib/spack/spack/test/directory_layout.py b/lib/spack/spack/test/directory_layout.py
index d814572d4a..8ad8f1a360 100644
--- a/lib/spack/spack/test/directory_layout.py
+++ b/lib/spack/spack/test/directory_layout.py
@@ -66,6 +66,9 @@ class DirectoryLayoutTest(MockPackagesTest):
packages = list(spack.repo.all_packages())[:max_packages]
for pkg in packages:
+ if pkg.name.startswith('external'):
+ #External package tests cannot be installed
+ continue
spec = pkg.spec
# If a spec fails to concretize, just skip it. If it is a
@@ -171,6 +174,9 @@ class DirectoryLayoutTest(MockPackagesTest):
# Create install prefixes for all packages in the list
installed_specs = {}
for pkg in packages:
+ if pkg.name.startswith('external'):
+ #External package tests cannot be installed
+ continue
spec = pkg.spec.concretized()
installed_specs[spec.name] = spec
self.layout.create_install_directory(spec)
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
index 0b8867b61e..6d24a84150 100644
--- a/lib/spack/spack/test/mock_packages_test.py
+++ b/lib/spack/spack/test/mock_packages_test.py
@@ -49,6 +49,19 @@ compilers:
fc: /path/to/gfortran
"""
+mock_packages_config = """\
+packages:
+ externaltool:
+ buildable: False
+ paths:
+ externaltool@1.0%gcc@4.5.0: /path/to/external_tool
+ externalvirtual:
+ buildable: False
+ paths:
+ externalvirtual@2.0%clang@3.3: /path/to/external_virtual_clang
+ externalvirtual@1.0%gcc@4.5.0: /path/to/external_virtual_gcc
+"""
+
class MockPackagesTest(unittest.TestCase):
def initmock(self):
# Use the mock packages database for these tests. This allows
@@ -66,9 +79,10 @@ class MockPackagesTest(unittest.TestCase):
self.mock_user_config = os.path.join(self.temp_config, 'user')
mkdirp(self.mock_site_config)
mkdirp(self.mock_user_config)
- comp_yaml = os.path.join(self.mock_site_config, 'compilers.yaml')
- with open(comp_yaml, 'w') as f:
- f.write(mock_compiler_config)
+ for confs in [('compilers.yaml', mock_compiler_config), ('packages.yaml', mock_packages_config)]:
+ conf_yaml = os.path.join(self.mock_site_config, confs[0])
+ with open(conf_yaml, 'w') as f:
+ f.write(confs[1])
# TODO: Mocking this up is kind of brittle b/c ConfigScope
# TODO: constructor modifies config_scopes. Make it cleaner.
diff --git a/lib/spack/spack/test/sbang.py b/lib/spack/spack/test/sbang.py
new file mode 100644
index 0000000000..825bc4be98
--- /dev/null
+++ b/lib/spack/spack/test/sbang.py
@@ -0,0 +1,93 @@
+##############################################################################
+# Copyright (c) 2013-2015, 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://github.com/llnl/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
+##############################################################################
+"""\
+Test that Spack's shebang filtering works correctly.
+"""
+import os
+import unittest
+import tempfile
+import shutil
+
+from llnl.util.filesystem import *
+from spack.hooks.sbang import filter_shebangs_in_directory
+import spack
+
+short_line = "#!/this/is/short/bin/bash\n"
+long_line = "#!/this/" + ('x' * 200) + "/is/long\n"
+sbang_line = '#!/bin/bash %s/bin/sbang\n' % spack.spack_root
+last_line = "last!\n"
+
+class SbangTest(unittest.TestCase):
+ def setUp(self):
+ self.tempdir = tempfile.mkdtemp()
+
+ # make sure we can ignore non-files
+ directory = os.path.join(self.tempdir, 'dir')
+ mkdirp(directory)
+
+ # Script with short shebang
+ self.short_shebang = os.path.join(self.tempdir, 'short')
+ with open(self.short_shebang, 'w') as f:
+ f.write(short_line)
+ f.write(last_line)
+
+ # Script with long shebang
+ self.long_shebang = os.path.join(self.tempdir, 'long')
+ with open(self.long_shebang, 'w') as f:
+ f.write(long_line)
+ f.write(last_line)
+
+ # Script already using sbang.
+ self.has_shebang = os.path.join(self.tempdir, 'shebang')
+ with open(self.has_shebang, 'w') as f:
+ f.write(sbang_line)
+ f.write(long_line)
+ f.write(last_line)
+
+
+ def tearDown(self):
+ shutil.rmtree(self.tempdir, ignore_errors=True)
+
+
+
+ def test_shebang_handling(self):
+ filter_shebangs_in_directory(self.tempdir)
+
+ # Make sure this is untouched
+ with open(self.short_shebang, 'r') as f:
+ self.assertEqual(f.readline(), short_line)
+ self.assertEqual(f.readline(), last_line)
+
+ # Make sure this got patched.
+ with open(self.long_shebang, 'r') as f:
+ self.assertEqual(f.readline(), sbang_line)
+ self.assertEqual(f.readline(), long_line)
+ self.assertEqual(f.readline(), last_line)
+
+ # Make sure this is untouched
+ with open(self.has_shebang, 'r') as f:
+ self.assertEqual(f.readline(), sbang_line)
+ self.assertEqual(f.readline(), long_line)
+ self.assertEqual(f.readline(), last_line)
diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py
index dbcf89d864..ea425127c4 100644
--- a/lib/spack/spack/test/stage.py
+++ b/lib/spack/spack/test/stage.py
@@ -277,3 +277,38 @@ class StageTest(unittest.TestCase):
self.check_chdir_to_source(stage, stage_name)
self.assertFalse('foobar' in os.listdir(stage.source_path))
self.check_destroy(stage, stage_name)
+
+
+ def test_no_keep_without_exceptions(self):
+ with Stage(archive_url, name=stage_name, keep=False) as stage:
+ pass
+ self.check_destroy(stage, stage_name)
+
+
+ def test_keep_without_exceptions(self):
+ with Stage(archive_url, name=stage_name, keep=True) as stage:
+ pass
+ path = self.get_stage_path(stage, stage_name)
+ self.assertTrue(os.path.isdir(path))
+
+
+ def test_no_keep_with_exceptions(self):
+ try:
+ with Stage(archive_url, name=stage_name, keep=False) as stage:
+ raise Exception()
+
+ path = self.get_stage_path(stage, stage_name)
+ self.assertTrue(os.path.isdir(path))
+ except:
+ pass # ignore here.
+
+
+ def test_keep_exceptions(self):
+ try:
+ with Stage(archive_url, name=stage_name, keep=True) as stage:
+ raise Exception()
+
+ path = self.get_stage_path(stage, stage_name)
+ self.assertTrue(os.path.isdir(path))
+ except:
+ pass # ignore here.
diff --git a/lib/spack/spack/url.py b/lib/spack/spack/url.py
index ad551a6ded..f51f05cad7 100644
--- a/lib/spack/spack/url.py
+++ b/lib/spack/spack/url.py
@@ -142,7 +142,7 @@ def split_url_extension(path):
def downloaded_file_extension(path):
"""This returns the type of archive a URL refers to. This is
- sometimes confusing becasue of URLs like:
+ sometimes confusing because of URLs like:
(1) https://github.com/petdance/ack/tarball/1.93_02
diff --git a/lib/spack/spack/util/compression.py b/lib/spack/spack/util/compression.py
index ea1f233bce..5ae5867428 100644
--- a/lib/spack/spack/util/compression.py
+++ b/lib/spack/spack/util/compression.py
@@ -27,13 +27,12 @@ import os
from itertools import product
from spack.util.executable import which
-# Supported archvie extensions.
+# Supported archive extensions.
PRE_EXTS = ["tar"]
EXTS = ["gz", "bz2", "xz", "Z", "zip", "tgz"]
-# Add EXTS last so that .tar.gz is matched *before* tar.gz
-ALLOWED_ARCHIVE_TYPES = [".".join(l) for l in product(PRE_EXTS, EXTS)] + EXTS
-
+# Add PRE_EXTS and EXTS last so that .tar.gz is matched *before* .tar or .gz
+ALLOWED_ARCHIVE_TYPES = [".".join(l) for l in product(PRE_EXTS, EXTS)] + PRE_EXTS + EXTS
def allowed_archive(path):
return any(path.endswith(t) for t in ALLOWED_ARCHIVE_TYPES)