summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/.gitignore1
-rw-r--r--lib/spack/docs/Makefile42
-rw-r--r--lib/spack/docs/_gh_pages_redirect/.nojekyll0
-rw-r--r--lib/spack/docs/_gh_pages_redirect/index.html10
-rw-r--r--lib/spack/docs/basic_usage.rst1387
-rw-r--r--lib/spack/docs/basic_usage_tutorial.rst1253
-rw-r--r--lib/spack/docs/build_settings.rst168
-rw-r--r--lib/spack/docs/command_index.in7
-rw-r--r--lib/spack/docs/conf.py69
-rw-r--r--lib/spack/docs/config_yaml.rst149
-rw-r--r--lib/spack/docs/configuration.rst468
-rw-r--r--lib/spack/docs/contribution_guide.rst522
-rw-r--r--lib/spack/docs/developer_guide.rst220
-rw-r--r--lib/spack/docs/features.rst51
-rw-r--r--lib/spack/docs/getting_started.rst1117
-rw-r--r--lib/spack/docs/index.rst41
-rw-r--r--lib/spack/docs/mirrors.rst141
-rw-r--r--lib/spack/docs/module_file_support.rst682
-rw-r--r--lib/spack/docs/module_file_tutorial.rst982
-rw-r--r--lib/spack/docs/package_tutorial.rst463
-rw-r--r--lib/spack/docs/packaging_guide.rst1642
-rw-r--r--lib/spack/docs/packaging_tutorial/0.package.py64
-rw-r--r--lib/spack/docs/packaging_tutorial/1.package.py (renamed from lib/spack/spack/schema/targets.py)42
-rw-r--r--lib/spack/docs/packaging_tutorial/2.package.py21
-rw-r--r--lib/spack/docs/packaging_tutorial/3.package.py17
-rw-r--r--lib/spack/docs/packaging_tutorial/4.package.py26
-rw-r--r--lib/spack/docs/workflows.rst1198
-rwxr-xr-xlib/spack/env/cc65
l---------lib/spack/env/clang/gfortran1
-rw-r--r--lib/spack/external/distro.py1049
-rw-r--r--lib/spack/llnl/util/filesystem.py258
-rw-r--r--lib/spack/llnl/util/lang.py16
-rw-r--r--lib/spack/llnl/util/lock.py142
-rw-r--r--lib/spack/llnl/util/tty/log.py238
-rw-r--r--lib/spack/spack/__init__.py185
-rw-r--r--lib/spack/spack/abi.py5
-rw-r--r--lib/spack/spack/architecture.py17
-rw-r--r--lib/spack/spack/build_environment.py286
-rw-r--r--lib/spack/spack/build_systems/__init__.py0
-rw-r--r--lib/spack/spack/build_systems/autotools.py183
-rw-r--r--lib/spack/spack/build_systems/cmake.py143
-rw-r--r--lib/spack/spack/build_systems/makefile.py77
-rw-r--r--lib/spack/spack/cmd/__init__.py20
-rw-r--r--lib/spack/spack/cmd/arch.py14
-rw-r--r--lib/spack/spack/cmd/bootstrap.py2
-rw-r--r--lib/spack/spack/cmd/build.py (renamed from lib/spack/spack/hooks/tclmodule.py)21
-rw-r--r--lib/spack/spack/cmd/checksum.py2
-rw-r--r--lib/spack/spack/cmd/common/arguments.py91
-rw-r--r--lib/spack/spack/cmd/compiler.py21
-rw-r--r--lib/spack/spack/cmd/config.py9
-rw-r--r--lib/spack/spack/cmd/configure.py90
-rw-r--r--lib/spack/spack/cmd/create.py245
-rw-r--r--lib/spack/spack/cmd/deactivate.py3
-rw-r--r--lib/spack/spack/cmd/debug.py100
-rw-r--r--lib/spack/spack/cmd/diy.py72
-rw-r--r--lib/spack/spack/cmd/extensions.py8
-rw-r--r--lib/spack/spack/cmd/find.py70
-rw-r--r--lib/spack/spack/cmd/flake8.py184
-rw-r--r--lib/spack/spack/cmd/graph.py46
-rw-r--r--lib/spack/spack/cmd/info.py18
-rw-r--r--lib/spack/spack/cmd/install.py302
-rw-r--r--lib/spack/spack/cmd/list.py138
-rw-r--r--lib/spack/spack/cmd/location.py5
-rw-r--r--lib/spack/spack/cmd/md5.py15
-rw-r--r--lib/spack/spack/cmd/module.py13
-rw-r--r--lib/spack/spack/cmd/package-list.py99
-rw-r--r--lib/spack/spack/cmd/purge.py10
-rw-r--r--lib/spack/spack/cmd/reindex.py4
-rw-r--r--lib/spack/spack/cmd/setup.py114
-rw-r--r--lib/spack/spack/cmd/spec.py39
-rw-r--r--lib/spack/spack/cmd/test-install.py245
-rw-r--r--lib/spack/spack/cmd/test.py2
-rw-r--r--lib/spack/spack/cmd/uninstall.py95
-rw-r--r--lib/spack/spack/cmd/url_parse.py (renamed from lib/spack/spack/cmd/url-parse.py)0
-rw-r--r--lib/spack/spack/compiler.py32
-rw-r--r--lib/spack/spack/compilers/__init__.py47
-rw-r--r--lib/spack/spack/compilers/clang.py129
-rw-r--r--lib/spack/spack/compilers/gcc.py9
-rw-r--r--lib/spack/spack/compilers/intel.py16
-rw-r--r--lib/spack/spack/compilers/nag.py10
-rw-r--r--lib/spack/spack/compilers/pgi.py8
-rw-r--r--lib/spack/spack/concretize.py91
-rw-r--r--lib/spack/spack/config.py215
-rw-r--r--lib/spack/spack/database.py237
-rw-r--r--lib/spack/spack/directives.py22
-rw-r--r--lib/spack/spack/directory_layout.py13
-rw-r--r--lib/spack/spack/environment.py19
-rw-r--r--lib/spack/spack/error.py37
-rw-r--r--lib/spack/spack/fetch_strategy.py162
-rw-r--r--lib/spack/spack/file_cache.py7
-rw-r--r--lib/spack/spack/graph.py133
-rw-r--r--lib/spack/spack/hooks/__init__.py14
-rw-r--r--lib/spack/spack/hooks/licensing.py5
-rw-r--r--lib/spack/spack/hooks/module_file_generation.py (renamed from lib/spack/spack/hooks/dotkit.py)10
-rw-r--r--lib/spack/spack/hooks/sbang.py12
-rw-r--r--lib/spack/spack/mirror.py72
-rw-r--r--lib/spack/spack/modules.py405
-rw-r--r--lib/spack/spack/multimethod.py12
-rw-r--r--lib/spack/spack/operating_systems/linux_distro.py11
-rw-r--r--lib/spack/spack/operating_systems/mac_os.py2
-rw-r--r--lib/spack/spack/package.py889
-rw-r--r--lib/spack/spack/patch.py118
-rw-r--r--lib/spack/spack/platforms/cray.py116
-rw-r--r--lib/spack/spack/platforms/darwin.py6
-rw-r--r--lib/spack/spack/platforms/linux.py5
-rw-r--r--lib/spack/spack/preferred_packages.py6
-rw-r--r--lib/spack/spack/provider_index.py21
-rw-r--r--lib/spack/spack/repository.py46
-rw-r--r--lib/spack/spack/schema/compilers.py36
-rw-r--r--lib/spack/spack/schema/config.py67
-rw-r--r--lib/spack/spack/schema/mirrors.py8
-rw-r--r--lib/spack/spack/schema/modules.py23
-rw-r--r--lib/spack/spack/schema/packages.py8
-rw-r--r--lib/spack/spack/schema/repos.py8
-rw-r--r--lib/spack/spack/spec.py419
-rw-r--r--lib/spack/spack/stage.py262
-rw-r--r--lib/spack/spack/store.py75
-rw-r--r--lib/spack/spack/test/__init__.py43
-rw-r--r--lib/spack/spack/test/cc.py68
-rw-r--r--lib/spack/spack/test/cmd/find.py2
-rw-r--r--lib/spack/spack/test/cmd/install.py (renamed from lib/spack/spack/test/cmd/test_install.py)71
-rw-r--r--lib/spack/spack/test/cmd/module.py2
-rw-r--r--lib/spack/spack/test/cmd/uninstall.py4
-rw-r--r--lib/spack/spack/test/concretize_preferences.py7
-rw-r--r--lib/spack/spack/test/config.py362
-rw-r--r--lib/spack/spack/test/data/sourceme_parameters.sh7
-rw-r--r--lib/spack/spack/test/database.py156
-rw-r--r--lib/spack/spack/test/directory_layout.py25
-rw-r--r--lib/spack/spack/test/environment.py44
-rw-r--r--lib/spack/spack/test/git_fetch.py13
-rw-r--r--lib/spack/spack/test/hg_fetch.py13
-rw-r--r--lib/spack/spack/test/install.py15
-rw-r--r--lib/spack/spack/test/library_list.py111
-rw-r--r--lib/spack/spack/test/lock.py223
-rw-r--r--lib/spack/spack/test/mirror.py7
-rw-r--r--lib/spack/spack/test/mock_database.py29
-rw-r--r--lib/spack/spack/test/mock_packages_test.py60
-rw-r--r--lib/spack/spack/test/modules.py432
-rw-r--r--lib/spack/spack/test/operating_system.py75
-rw-r--r--lib/spack/spack/test/provider_index.py2
-rw-r--r--lib/spack/spack/test/spack_yaml.py (renamed from lib/spack/spack/test/yaml.py)18
-rw-r--r--lib/spack/spack/test/spec_dag.py68
-rw-r--r--lib/spack/spack/test/spec_syntax.py54
-rw-r--r--lib/spack/spack/test/stage.py210
-rw-r--r--lib/spack/spack/test/svn_fetch.py13
-rw-r--r--lib/spack/spack/test/tally_plugin.py4
-rw-r--r--lib/spack/spack/test/versions.py4
-rw-r--r--lib/spack/spack/url.py6
-rw-r--r--lib/spack/spack/util/compression.py8
-rw-r--r--lib/spack/spack/util/crypto.py21
-rw-r--r--lib/spack/spack/util/environment.py18
-rw-r--r--lib/spack/spack/util/executable.py94
-rw-r--r--lib/spack/spack/util/path.py72
-rw-r--r--lib/spack/spack/util/pattern.py18
-rw-r--r--lib/spack/spack/util/spack_yaml.py15
-rw-r--r--lib/spack/spack/util/web.py11
-rw-r--r--lib/spack/spack/version.py128
157 files changed, 16433 insertions, 5488 deletions
diff --git a/lib/spack/docs/.gitignore b/lib/spack/docs/.gitignore
index 26c343d3eb..0bbf78cce0 100644
--- a/lib/spack/docs/.gitignore
+++ b/lib/spack/docs/.gitignore
@@ -1,4 +1,5 @@
package_list.rst
command_index.rst
spack*.rst
+modules.rst
_build
diff --git a/lib/spack/docs/Makefile b/lib/spack/docs/Makefile
index 00203b5b61..bcba423d94 100644
--- a/lib/spack/docs/Makefile
+++ b/lib/spack/docs/Makefile
@@ -2,12 +2,13 @@
#
# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
+SPHINXOPTS = -E
+JOBS ?= $(shell python -c 'import multiprocessing; print multiprocessing.cpu_count()')
+SPHINXBUILD = sphinx-build -j $(JOBS)
PAPER =
BUILDDIR = _build
-export PYTHONPATH = ../../spack
+export PYTHONPATH := ../../spack:$(PYTHONPATH)
APIDOC_FILES = spack*.rst
# Internal variables.
@@ -22,24 +23,6 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
all: html
#
-# This autogenerates a package list.
-#
-package_list:
- spack package-list > package_list.rst
-
-#
-# Generate a command index
-#
-command_index:
- cp command_index.in command_index.rst
- echo >> command_index.rst
- grep -ho '.. _spack-.*:' *rst \
- | perl -pe 's/.. _([^:]*):/ * :ref:`\1`/' \
- | sort >> command_index.rst
-
-custom_targets: package_list command_index
-
-#
# This creates a git repository and commits generated html docs.
# It them pushes the new branch into THIS repository as gh-pages.
#
@@ -58,9 +41,20 @@ gh-pages: _build/html
git push -f $$root master:gh-pages && \
rm -rf .git
+# This version makes gh-pages into a single page that redirects
+# to spack.readthedocs.io
+gh-pages-redirect:
+ root="$$(git rev-parse --show-toplevel)" && \
+ cd _gh_pages_redirect && \
+ rm -rf .git && \
+ git init && \
+ git add . && \
+ git commit -m "Spack Documentation" && \
+ git push -f $$root master:gh-pages && \
+ rm -rf .git
+
upload:
rsync -avz --rsh=ssh --delete _build/html/ cab:/usr/global/web-pages/lc/www/adept/docs/spack
- git push -f origin gh-pages
git push -f github gh-pages
apidoc:
@@ -89,10 +83,10 @@ help:
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
- -rm -f package_list.rst command_index.rst
+ -rm -f package_list.rst command_index.rst modules.rst
-rm -rf $(BUILDDIR)/* $(APIDOC_FILES)
-html: apidoc custom_targets
+html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
diff --git a/lib/spack/docs/_gh_pages_redirect/.nojekyll b/lib/spack/docs/_gh_pages_redirect/.nojekyll
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/spack/docs/_gh_pages_redirect/.nojekyll
diff --git a/lib/spack/docs/_gh_pages_redirect/index.html b/lib/spack/docs/_gh_pages_redirect/index.html
new file mode 100644
index 0000000000..9c456abdaa
--- /dev/null
+++ b/lib/spack/docs/_gh_pages_redirect/index.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <meta http-equiv="refresh" content="0; url=http://spack.readthedocs.io/" />
+ </head>
+ <body>
+ <p>
+ This page has moved to <a href="http://spack.readthedocs.io/">http://spack.readthedocs.io/</a>
+ </p>
+ </body>
+</html>
diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst
index a42d941791..03e6d581a4 100644
--- a/lib/spack/docs/basic_usage.rst
+++ b/lib/spack/docs/basic_usage.rst
@@ -1,57 +1,64 @@
.. _basic-usage:
-Basic usage
-=====================
+===========
+Basic Usage
+===========
The ``spack`` command has many *subcommands*. You'll only need a
small subset of them for typical usage.
+Note that Spack colorizes output. ``less -R`` should be used with
+Spack to maintain this colorization. E.g.:
+.. code-block:: console
+
+ $ spack find | less -R
+
+It is recommended that the following be put in your ``.bashrc`` file:
+
+.. code-block:: sh
+
+ alias less='less -R'
+
+--------------------------
Listing available packages
-------------------------------
+--------------------------
To install software with Spack, you need to know what software is
available. You can see a list of available package names at the
:ref:`package-list` webpage, or using the ``spack list`` command.
-.. _spack-list:
+.. _cmd-spack-list:
+^^^^^^^^^^^^^^
``spack list``
-~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
The ``spack list`` command prints out a list of all of the packages
Spack can install:
.. command-output:: spack list
-The packages are listed by name in alphabetical order. If you specify a
-pattern to match, it will follow this set of rules. A pattern with no
-wildcards, ``*`` or ``?``, will be treated as though it started and ended with
-``*``, so ``util`` is equivalent to ``*util*``. A pattern with no capital
-letters will be treated as case-insensitive. You can also add the ``-i`` flag
-to specify a case insensitive search, or ``-d`` to search the description of
+The packages are listed by name in alphabetical order.
+A pattern to match with no wildcards, ``*`` or ``?``,
+will be treated as though it started and ended with
+``*``, so ``util`` is equivalent to ``*util*``. All patterns will be treated
+as case-insensitive. You can also add the ``-d`` to search the description of
the package in addition to the name. Some examples:
-All packages whose names contain "sql" case insensitive:
+All packages whose names contain "sql":
.. command-output:: spack list sql
-All packages whose names start with a capital M:
-
-.. command-output:: spack list 'M*'
-
-All packages whose names or descriptions contain Documentation:
-
-.. command-output:: spack list -d Documentation
+All packages whose names or descriptions contain documentation:
-All packages whose names contain documentation case insensitive:
+.. command-output:: spack list --search-description documentation
-.. command-output:: spack list -d documentation
-
-.. _spack-info:
+.. _cmd-spack-info:
+^^^^^^^^^^^^^^
``spack info``
-~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
To get more information on a particular package from `spack list`, use
`spack info`. Just supply the name of a package:
@@ -66,10 +73,11 @@ viruses.
:ref:`Dependencies <sec-specs>` and :ref:`virtual dependencies
<sec-virtual-dependencies>` are described in more detail later.
-.. _spack-versions:
+.. _cmd-spack-versions:
+^^^^^^^^^^^^^^^^^^
``spack versions``
-~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^
To see *more* available versions of a package, run ``spack versions``.
For example:
@@ -86,31 +94,32 @@ by scraping it directly from package web pages. Depending on the
package and how its releases are organized, Spack may or may not be
able to find remote versions.
-
+---------------------------
Installing and uninstalling
-------------------------------
+---------------------------
-.. _spack-install:
+.. _cmd-spack-install:
+^^^^^^^^^^^^^^^^^
``spack install``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
``spack install`` will install any package shown by ``spack list``.
For example, To install the latest version of the ``mpileaks``
package, you might type this:
-.. code-block:: sh
+.. code-block:: console
$ spack install mpileaks
-If `mpileaks` depends on other packages, Spack will install the
+If ``mpileaks`` depends on other packages, Spack will install the
dependencies first. It then fetches the ``mpileaks`` tarball, expands
it, verifies that it was downloaded without errors, builds it, and
installs it in its own directory under ``$SPACK_ROOT/opt``. You'll see
a number of messages from spack, a lot of build output, and a message
that the packages is installed:
-.. code-block:: sh
+.. code-block:: console
$ spack install mpileaks
==> Installing mpileaks
@@ -133,13 +142,14 @@ that the packages is installed:
The last line, with the ``[+]``, indicates where the package is
installed.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
Building a specific version
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
Spack can also build *specific versions* of a package. To do this,
just add ``@`` after the package name, followed by a version:
-.. code-block:: sh
+.. code-block:: console
$ spack install mpich@3.0.4
@@ -158,66 +168,119 @@ two configurations of the same version of a package, one built with boost
This can all be done on the command line using the *spec* syntax.
Spack calls the descriptor used to refer to a particular package
configuration a **spec**. In the commands above, ``mpileaks`` and
-``mpileaks@3.0.4``. We'll talk more about how you can use them to
-customize an installation in :ref:`sec-specs`.
+``mpileaks@3.0.4`` are both valid *specs*. We'll talk more about how
+you can use them to customize an installation in :ref:`sec-specs`.
-.. _spack-uninstall:
+.. _cmd-spack-uninstall:
+^^^^^^^^^^^^^^^^^^^
``spack uninstall``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
-To uninstall a package, type ``spack uninstall <package>``. This will ask the user for
-confirmation, and in case will completely remove the directory in which the package was installed.
+To uninstall a package, type ``spack uninstall <package>``. This will ask
+the user for confirmation before completely removing the directory
+in which the package was installed.
-.. code-block:: sh
+.. code-block:: console
- spack uninstall mpich
+ $ spack uninstall mpich
If there are still installed packages that depend on the package to be
uninstalled, spack will refuse to uninstall it.
To uninstall a package and every package that depends on it, you may give the
-`--dependents` option.
+``--dependents`` option.
-.. code-block:: sh
+.. code-block:: console
- spack uninstall --dependents mpich
+ $ spack uninstall --dependents mpich
-will display a list of all the packages that depend on `mpich` and, upon confirmation,
-will uninstall them in the right order.
+will display a list of all the packages that depend on ``mpich`` and, upon
+confirmation, will uninstall them in the right order.
-A line like
+A command like
-.. code-block:: sh
+.. code-block:: console
- spack uninstall mpich
+ $ spack uninstall mpich
-may be ambiguous, if multiple ``mpich`` configurations are installed. For example, if both
-``mpich@3.0.2`` and ``mpich@3.1`` are installed, ``mpich`` could refer
-to either one. Because it cannot determine which one to uninstall,
-Spack will ask you either to provide a version number to remove the
-ambiguity or use the ``--all`` option to uninstall all of the matching packages.
+may be ambiguous if multiple ``mpich`` configurations are installed.
+For example, if both ``mpich@3.0.2`` and ``mpich@3.1`` are installed,
+``mpich`` could refer to either one. Because it cannot determine which
+one to uninstall, Spack will ask you either to provide a version number
+to remove the ambiguity or use the ``--all`` option to uninstall all of
+the matching packages.
-You may force uninstall a package with the `--force` option
+You may force uninstall a package with the ``--force`` option
-.. code-block:: sh
+.. code-block:: console
+
+ $ spack uninstall --force mpich
+
+but you risk breaking other installed packages. In general, it is safer to
+remove dependent packages *before* removing their dependencies or use the
+``--dependents`` option.
+
+
+.. _nondownloadable:
+
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Non-Downloadable Tarballs
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The tarballs for some packages cannot be automatically downloaded by
+Spack. This could be for a number of reasons:
+
+#. The author requires users to manually accept a license agreement
+ before downloading (``jdk`` and ``galahad``).
+
+#. The software is proprietary and cannot be downloaded on the open
+ Internet.
+
+To install these packages, one must create a mirror and manually add
+the tarballs in question to it (see :ref:`mirrors`):
+
+#. Create a directory for the mirror. You can create this directory
+ anywhere you like, it does not have to be inside ``~/.spack``:
+
+ .. code-block:: console
+
+ $ mkdir ~/.spack/manual_mirror
+
+#. Register the mirror with Spack by creating ``~/.spack/mirrors.yaml``:
+
+ .. code-block:: yaml
+
+ mirrors:
+ manual: file:///home/me/.spack/manual_mirror
+
+#. Put your tarballs in it. Tarballs should be named
+ ``<package>/<package>-<version>.tar.gz``. For example:
+
+ .. code-block:: console
+
+ $ ls -l manual_mirror/galahad
- spack uninstall --force mpich
+ -rw-------. 1 me me 11657206 Jun 21 19:25 galahad-2.60003.tar.gz
-but you risk breaking other installed packages. In general, it is safer to remove dependent
-packages *before* removing their dependencies or use the `--dependents` option.
+#. Install as usual:
+ .. code-block:: console
+ $ spack install galahad
+
+-------------------------
Seeing installed packages
------------------------------------
+-------------------------
We know that ``spack list`` shows you the names of available packages,
-but how do you figure out which are installed?
+but how do you figure out which are already installed?
-.. _spack-find:
+.. _cmd-spack-find:
+^^^^^^^^^^^^^^
``spack find``
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
``spack find`` shows the *specs* of installed packages. A spec is
like a name, but it has a version, compiler, architecture, and build
@@ -226,7 +289,7 @@ of the same package with different specs.
Running ``spack find`` with no arguments lists installed packages:
-.. code-block:: sh
+.. code-block:: console
$ spack find
==> 74 installed packages.
@@ -265,17 +328,18 @@ only shows the version of installed packages.
``spack find`` can filter the package list based on the package name, spec, or
a number of properties of their installation status. For example, missing
-dependencies of a spec can be shown with ``-m``, packages which were
+dependencies of a spec can be shown with ``--missing``, packages which were
explicitly installed with ``spack install <package>`` can be singled out with
-``-e`` and those which have been pulled in only as dependencies with ``-E``.
+``--explicit`` and those which have been pulled in only as dependencies with
+``--implicit``.
In some cases, there may be different configurations of the *same*
version of a package installed. For example, there are two
-installations of of ``libdwarf@20130729`` above. We can look at them
-in more detail using ``spack find -d``, and by asking only to show
+installations of ``libdwarf@20130729`` above. We can look at them
+in more detail using ``spack find --deps``, and by asking only to show
``libdwarf`` packages:
-.. code-block:: sh
+.. code-block:: console
$ spack find --deps libdwarf
==> 2 installed packages.
@@ -289,25 +353,25 @@ Now we see that the two instances of ``libdwarf`` depend on
*different* versions of ``libelf``: 0.8.12 and 0.8.13. This view can
become complicated for packages with many dependencies. If you just
want to know whether two packages' dependencies differ, you can use
-``spack find -l``:
+``spack find --long``:
-.. code-block:: sh
+.. code-block:: console
- $ spack find -l libdwarf
+ $ spack find --long libdwarf
==> 2 installed packages.
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
libdwarf@20130729-d9b90962 libdwarf@20130729-b52fac98
-Now the ``libwarf`` installs have hashes after their names. These are
+Now the ``libdwarf`` installs have hashes after their names. These are
hashes over all of the dependencies of each package. If the hashes
are the same, then the packages have the same dependency configuration.
If you want to know the path where each package is installed, you can
-use ``spack find -p``:
+use ``spack find --paths``:
-.. code-block:: sh
+.. code-block:: console
- $ spack find -p
+ $ spack find --paths
==> 74 installed packages.
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
ImageMagick@6.8.9-10 /home/gamblin2/spack/opt/linux-debian7-x86_64/gcc@4.4.7/ImageMagick@6.8.9-10-4df950dd
@@ -322,9 +386,9 @@ use ``spack find -p``:
And, finally, you can restrict your search to a particular package
by supplying its name:
-.. code-block:: sh
+.. code-block:: console
- $ spack find -p libelf
+ $ spack find --paths libelf
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
libelf@0.8.11 /home/gamblin2/spack/opt/linux-debian7-x86_64/gcc@4.4.7/libelf@0.8.11
libelf@0.8.12 /home/gamblin2/spack/opt/linux-debian7-x86_64/gcc@4.4.7/libelf@0.8.12
@@ -335,7 +399,7 @@ by supplying its name:
package. If you want to find only libelf versions greater than version
0.8.12, you could say:
-.. code-block:: sh
+.. code-block:: console
$ spack find libelf@0.8.12:
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
@@ -344,173 +408,24 @@ package. If you want to find only libelf versions greater than version
Finding just the versions of libdwarf built with a particular version
of libelf would look like this:
-.. code-block:: sh
+.. code-block:: console
- $ spack find -l libdwarf ^libelf@0.8.12
+ $ spack find --long libdwarf ^libelf@0.8.12
==> 1 installed packages.
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
libdwarf@20130729-d9b90962
We can also search for packages that have a certain attribute. For example,
-``spack find -l libdwarf +debug`` will show only installations of libdwarf
-with the 'debug' compile-time option enabled, while ``spack find -l +debug``
-will find every installed package with a 'debug' compile-time option enabled.
+``spack find libdwarf +debug`` will show only installations of libdwarf
+with the 'debug' compile-time option enabled.
The full spec syntax is discussed in detail in :ref:`sec-specs`.
-
-.. _compiler-config:
-
-Compiler configuration
------------------------------------
-
-Spack has the ability to build packages with multiple compilers and
-compiler versions. Spack searches for compilers on your machine
-automatically the first time it is run. It does this by inspecting
-your path.
-
-.. _spack-compilers:
-
-``spack compilers``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-You can see which compilers spack has found by running ``spack
-compilers`` or ``spack compiler list``::
-
- $ spack compilers
- ==> Available compilers
- -- gcc ---------------------------------------------------------
- gcc@4.9.0 gcc@4.8.0 gcc@4.7.0 gcc@4.6.2 gcc@4.4.7
- gcc@4.8.2 gcc@4.7.1 gcc@4.6.3 gcc@4.6.1 gcc@4.1.2
- -- intel -------------------------------------------------------
- intel@15.0.0 intel@14.0.0 intel@13.0.0 intel@12.1.0 intel@10.0
- intel@14.0.3 intel@13.1.1 intel@12.1.5 intel@12.0.4 intel@9.1
- intel@14.0.2 intel@13.1.0 intel@12.1.3 intel@11.1
- intel@14.0.1 intel@13.0.1 intel@12.1.2 intel@10.1
- -- clang -------------------------------------------------------
- clang@3.4 clang@3.3 clang@3.2 clang@3.1
- -- pgi ---------------------------------------------------------
- pgi@14.3-0 pgi@13.2-0 pgi@12.1-0 pgi@10.9-0 pgi@8.0-1
- pgi@13.10-0 pgi@13.1-1 pgi@11.10-0 pgi@10.2-0 pgi@7.1-3
- pgi@13.6-0 pgi@12.8-0 pgi@11.1-0 pgi@9.0-4 pgi@7.0-6
-
-Any of these compilers can be used to build Spack packages. More on
-how this is done is in :ref:`sec-specs`.
-
-.. _spack-compiler-add:
-
-``spack compiler add``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-An alias for ``spack compiler find``.
-
-.. _spack-compiler-find:
-
-``spack compiler find``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-If you do not see a compiler in this list, but you want to use it with
-Spack, you can simply run ``spack compiler find`` with the path to
-where the compiler is installed. For example::
-
- $ spack compiler find /usr/local/tools/ic-13.0.079
- ==> Added 1 new compiler to /Users/gamblin2/.spack/compilers.yaml
- intel@13.0.079
-
-Or you can run ``spack compiler find`` with no arguments to force
-auto-detection. This is useful if you do not know where compilers are
-installed, but you know that new compilers have been added to your
-``PATH``. For example, using dotkit, you might do this::
-
- $ module load gcc-4.9.0
- $ spack compiler find
- ==> 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 add it to
-``PATH``, and then it adds the compiler to Spack.
-
-.. _spack-compiler-info:
-
-``spack compiler info``
-~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to see specifics on a particular compiler, you can run
-``spack compiler info`` on it::
-
- $ spack compiler info intel@15
- intel@15.0.0:
- cc = /usr/local/bin/icc-15.0.090
- cxx = /usr/local/bin/icpc-15.0.090
- f77 = /usr/local/bin/ifort-15.0.090
- fc = /usr/local/bin/ifort-15.0.090
-
-This shows which C, C++, and Fortran compilers were detected by Spack.
-Notice also that we didn\'t have to be too specific about the
-version. We just said ``intel@15``, and information about the only
-matching Intel compiler was displayed.
-
-
-Manual compiler configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If auto-detection fails, you can manually configure a compiler by
-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::
-
- ...
- compilers:
- - compiler:
- modules = []
- operating_system: OS
- paths:
- 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
-
- spec: intel@15.0.0:
-
-For compilers, like ``clang``, that do not support Fortran, put
-``None`` for ``f77`` and ``fc``::
-
- 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``.
-
-You can also add compiler flags to manually configured compilers. The
-valid flags are ``cflags``, ``cxxflags``, ``fflags``, ``cppflags``,
-``ldflags``, and ``ldlibs``. For example::
-
- ...
- compilers:
- - 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
- cppflags: -O3 -fPIC
- ...
-
-These flags will be treated by spack as if they were enterred from
-the command line each time this compiler is used. The compiler wrappers
-then inject those flags into the compiler command. Compiler flags
-enterred from the command line will be discussed in more detail in the
-following section.
-
.. _sec-specs:
+--------------------
Specs & dependencies
--------------------------
+--------------------
We know that ``spack install``, ``spack uninstall``, and other
commands take a package name with an optional version specifier. In
@@ -518,10 +433,12 @@ Spack, that descriptor is called a *spec*. Spack uses specs to refer
to a particular build configuration (or configurations) of a package.
Specs are more than a package name and a version; you can use them to
specify the compiler, compiler version, architecture, compile options,
-and dependency options for a build. In this section, we\'ll go over
+and dependency options for a build. In this section, we'll go over
the full syntax of specs.
-Here is an example of a much longer spec than we\'ve seen thus far::
+Here is an example of a much longer spec than we've seen thus far:
+
+.. code-block:: none
mpileaks @1.2:1.4 %gcc@4.7.5 +debug -qt arch=bgq_os ^callpath @1.1 %gcc@4.7.2
@@ -600,7 +517,6 @@ some *other* version of ``mpich``. In general, such a configuration
would likely behave unexpectedly at runtime, and Spack enforces this
to ensure a consistent runtime environment.
-
The point of specs is to abstract this full DAG from Spack users. If
a user does not care about the DAG at all, she can refer to mpileaks
by simply writing ``mpileaks``. If she knows that ``mpileaks``
@@ -616,7 +532,7 @@ of dependencies within a spec is that they appear *after* the root
package. For example, these two specs represent exactly the same
configuration:
-.. code-block:: sh
+.. code-block:: none
mpileaks ^callpath@1.0 ^libelf@0.8.3
mpileaks ^libelf@0.8.3 ^callpath@1.0
@@ -637,8 +553,9 @@ could depend on ``mpich@1.2:`` if it can only build with version
Below are more details about the specifiers that you can add to specs.
+^^^^^^^^^^^^^^^^^
Version specifier
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
A version specifier comes somewhere after a package name and starts
with ``@``. It can be a single version, e.g. ``@1.0``, ``@3``, or
@@ -660,9 +577,9 @@ policies set for the particular Spack installation.
Details about how versions are compared and how Spack determines if
one version is less than another are discussed in the developer guide.
-
+^^^^^^^^^^^^^^^^^^
Compiler specifier
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^
A compiler specifier comes somewhere after a package name and starts
with ``%``. It tells Spack what compiler(s) a particular package
@@ -679,9 +596,9 @@ name or compiler specifier to their left in the spec.
If the compiler spec is omitted, Spack will choose a default compiler
based on site policies.
-
+^^^^^^^^
Variants
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^
Variants are named options associated with a particular package. They are
optional, as each package must provide default values for each variant it
@@ -689,7 +606,7 @@ makes available. Variants can be specified using
a flexible parameter syntax ``name=<value>``. For example,
``spack install libelf debug=True`` will install libelf build with debug
flags. The names of particular variants available for a package depend on
-what was provided by the package author. ``spack into <package>`` will
+what was provided by the package author. ``spack info <package>`` will
provide information on what build variants are available.
For compatibility with earlier versions, variants which happen to be
@@ -700,7 +617,7 @@ compile time option for the libelf package.
Depending on the package a variant may have any default value. For
``libelf`` here, ``debug`` is ``False`` by default, and we turned it on
-with ``debug=True`` or ``+debug``. If a package is ``True`` by default
+with ``debug=True`` or ``+debug``. If a variant is ``True`` by default
you can turn it off by either adding ``-name`` or ``~name`` to the spec.
There are two syntaxes here because, depending on context, ``~`` and
@@ -733,9 +650,9 @@ variants using the backwards compatibility syntax and uses only ``~``
for disabled boolean variants. We allow ``-`` and spaces on the command
line is provided for convenience and legibility.
-
+^^^^^^^^^^^^^^
Compiler Flags
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
Compiler flags are specified using the same syntax as non-boolean variants,
but fulfill a different purpose. While the function of a variant is set by
@@ -751,32 +668,61 @@ command line. From within python files, the same spec would be specified
handles the quote symbols.
The six compiler flags are injected in the order of implicit make commands
-in gnu autotools. If all flags are set, the order is
-``$cppflags $cflags|$cxxflags $ldflags command $ldlibs`` for C and C++ and
-``$fflags $cppflags $ldflags command $ldlibs`` for fortran.
+in GNU Autotools. If all flags are set, the order is
+``$cppflags $cflags|$cxxflags $ldflags <command> $ldlibs`` for C and C++ and
+``$fflags $cppflags $ldflags <command> $ldlibs`` for Fortran.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Compiler environment variables and additional RPATHs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In the exceptional case a compiler requires setting special environment
+variables, like an explicit library load path. These can bet set in an
+extra section in the compiler configuration. The user can also specify
+additional ``RPATHs`` that the compiler will add to all executables
+generated by that compiler. This is useful for forcing certain compilers
+to RPATH their own runtime libraries, so that executables will run
+without the need to set ``LD_LIBRARY_PATH``.
+.. code-block:: yaml
+ compilers:
+ - compiler:
+ spec: gcc@4.9.3
+ paths:
+ cc: /opt/gcc/bin/gcc
+ c++: /opt/gcc/bin/g++
+ f77: /opt/gcc/bin/gfortran
+ fc: /opt/gcc/bin/gfortran
+ environment:
+ set:
+ LD_LIBRARY_PATH : /opt/gcc/lib
+ extra_rpaths:
+ - /path/to/some/compiler/runtime/directory
+ - /path/to/some/other/compiler/runtime/directory
+
+^^^^^^^^^^^^^^^^^^^^^^^
Architecture specifiers
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^
The architecture can be specified by using the reserved
words ``target`` and/or ``os`` (``target=x86-64 os=debian7``). You can also
use the triplet form of platform, operating system and processor.
-.. code-block:: sh
+.. code-block:: console
- spack install libelf arch=cray_xc-CNL10-haswell
+ $ spack install libelf arch=cray-CNL10-haswell
Users on non-Cray systems won't have to worry about specifying the architecture.
Spack will autodetect what kind of operating system is on your machine as well
as the processor. For more information on how the architecture can be
-used on Cray machines, check here :ref:`spack-cray`
-
+used on Cray machines, see :ref:`cray-support`
.. _sec-virtual-dependencies:
+--------------------
Virtual dependencies
--------------------------
+--------------------
The dependence graph for ``mpileaks`` we saw above wasn't *quite*
accurate. ``mpileaks`` uses MPI, which is an interface that has many
@@ -819,27 +765,33 @@ version of a virtual package, e.g. if an application needs MPI-2
functions, it can depend on ``mpi@2:`` to indicate that it needs some
implementation that provides MPI-2 functions.
-
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Constraining virtual packages
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When installing a package that depends on a virtual package, you can
opt to specify the particular provider you want to use, or you can let
-Spack pick. For example, if you just type this::
+Spack pick. For example, if you just type this:
+
+.. code-block:: console
- spack install mpileaks
+ $ spack install mpileaks
Then spack will pick a provider for you according to site policies.
-If you really want a particular version, say mpich, then you could
-run this instead::
+If you really want a particular version, say ``mpich``, then you could
+run this instead:
+
+.. code-block:: console
- spack install mpileaks ^mpich
+ $ spack install mpileaks ^mpich
This forces spack to use some version of ``mpich`` for its
implementation. As always, you can be even more specific and require
-a particular ``mpich`` version::
+a particular ``mpich`` version:
+
+.. code-block:: console
- spack install mpileaks ^mpich@3
+ $ spack install mpileaks ^mpich@3
The ``mpileaks`` package in particular only needs MPI-1 commands, so
any MPI implementation will do. If another package depends on
@@ -848,8 +800,9 @@ any MPI implementation will do. If another package depends on
error. Likewise, if you try to plug in some package that doesn't
provide MPI, Spack will raise an error.
+^^^^^^^^^^^^^^^^^^^^^^^^
Specifying Specs by Hash
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^
Complicated specs can become cumbersome to enter on the command line,
especially when many of the qualifications are necessary to
@@ -863,12 +816,13 @@ to be unique, Spack will replace the reference with the spec to which
it refers. Otherwise, it will prompt for a more qualified hash.
Note that this will not work to reinstall a depencency uninstalled by
-``spack uninstall -f``.
+``spack uninstall --force``.
-.. _spack-providers:
+.. _cmd-spack-providers:
+^^^^^^^^^^^^^^^^^^^
``spack providers``
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
You can see what packages provide a particular virtual package using
``spack providers``. If you wanted to see what packages provide
@@ -886,653 +840,9 @@ versions are now filtered out.
.. _shell-support:
-Integration with module systems
--------------------------------
-
-.. note::
-
- Environment module support is currently experimental and should not
- be considered a stable feature of Spack. In particular, the
- interface and/or generated module names may change in future
- versions.
-
-Spack provides some integration with
-`Environment Modules <http://modules.sourceforge.net/>`_
-and `Dotkit <https://computing.llnl.gov/?set=jobs&page=dotkit>`_ to make
-it easier to use the packages it installed.
-
-
-
-Installing Environment Modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-In order to use Spack's generated environment modules, you must have
-installed the *Environment Modules* package. On many Linux
-distributions, this can be installed from the vendor's repository:
-
-.. code-block:: sh
-
- yum install environment-modules # (Fedora/RHEL/CentOS)
- apt-get install environment-modules # (Ubuntu/Debian)
-
-If your Linux distribution does not have
-Environment Modules, you can get it with Spack:
-
-.. code-block:: sh
-
- spack install environment-modules
-
-
-In this case to activate it automatically you need to add the following two
-lines to your ``.bashrc`` profile (or similar):
-
-.. code-block:: sh
-
- MODULES_HOME=`spack location -i environment-modules`
- source ${MODULES_HOME}/Modules/init/bash
-
-If you use a Unix shell other than ``bash``, modify the commands above
-accordingly and source the appropriate file in
-``${MODULES_HOME}/Modules/init/``.
-
-
-.. TODO : Add a similar section on how to install dotkit ?
-
-Spack and module systems
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-You can enable shell support by sourcing some files in the
-``/share/spack`` directory.
-
-For ``bash`` or ``ksh``, run:
-
-.. code-block:: sh
-
- . ${SPACK_ROOT}/share/spack/setup-env.sh
-
-For ``csh`` and ``tcsh`` run:
-
-.. code-block:: csh
-
- setenv SPACK_ROOT /path/to/spack
- source $SPACK_ROOT/share/spack/setup-env.csh
-
-You can put the above code in your ``.bashrc`` or ``.cshrc``, and
-Spack's shell support will be available on the command line.
-
-When you install a package with Spack, it automatically generates a module file
-that lets you add the package to your environment.
-
-Currently, Spack supports the generation of `Environment Modules
-<http://wiki.tcl.tk/12999>`_ and `Dotkit
-<https://computing.llnl.gov/?set=jobs&page=dotkit>`_. Generated
-module files for each of these systems can be found in these
-directories:
-
-.. code-block:: sh
-
- ${SPACK_ROOT}/share/spack/modules
- ${SPACK_ROOT}/share/spack/dotkit
-
-The directories are automatically added to your ``MODULEPATH`` and
-``DK_NODE`` environment variables when you enable Spack's `shell
-support <shell-support_>`_.
-
-
-Using Modules & Dotkits
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you have shell support enabled you should be able to run either
-``module avail`` or ``use -l spack`` to see what modules/dotkits have
-been installed. Here is sample output of those programs, showing lots
-of installed packages.
-
- .. code-block:: sh
-
- $ module avail
-
- ------- /home/gamblin2/spack/share/spack/modules/linux-debian7-x86_64 --------
- adept-utils@1.0%gcc@4.4.7-5adef8da libelf@0.8.13%gcc@4.4.7
- automaded@1.0%gcc@4.4.7-d9691bb0 libelf@0.8.13%intel@15.0.0
- boost@1.55.0%gcc@4.4.7 mpc@1.0.2%gcc@4.4.7-559607f5
- callpath@1.0.1%gcc@4.4.7-5dce4318 mpfr@3.1.2%gcc@4.4.7
- dyninst@8.1.2%gcc@4.4.7-b040c20e mpich@3.0.4%gcc@4.4.7
- gcc@4.9.1%gcc@4.4.7-93ab98c5 mpich@3.0.4%gcc@4.9.0
- gmp@6.0.0a%gcc@4.4.7 mrnet@4.1.0%gcc@4.4.7-72b7881d
- graphlib@2.0.0%gcc@4.4.7 netgauge@2.4.6%gcc@4.9.0-27912b7b
- launchmon@1.0.1%gcc@4.4.7 stat@2.1.0%gcc@4.4.7-51101207
- libNBC@1.1.1%gcc@4.9.0-27912b7b sundials@2.5.0%gcc@4.9.0-27912b7b
- libdwarf@20130729%gcc@4.4.7-b52fac98
-
- .. code-block:: sh
-
- $ use -l spack
-
- spack ----------
- adept-utils@1.0%gcc@4.4.7-5adef8da - adept-utils @1.0
- automaded@1.0%gcc@4.4.7-d9691bb0 - automaded @1.0
- boost@1.55.0%gcc@4.4.7 - boost @1.55.0
- callpath@1.0.1%gcc@4.4.7-5dce4318 - callpath @1.0.1
- dyninst@8.1.2%gcc@4.4.7-b040c20e - dyninst @8.1.2
- gmp@6.0.0a%gcc@4.4.7 - gmp @6.0.0a
- libNBC@1.1.1%gcc@4.9.0-27912b7b - libNBC @1.1.1
- libdwarf@20130729%gcc@4.4.7-b52fac98 - libdwarf @20130729
- libelf@0.8.13%gcc@4.4.7 - libelf @0.8.13
- libelf@0.8.13%intel@15.0.0 - libelf @0.8.13
- mpc@1.0.2%gcc@4.4.7-559607f5 - mpc @1.0.2
- mpfr@3.1.2%gcc@4.4.7 - mpfr @3.1.2
- mpich@3.0.4%gcc@4.4.7 - mpich @3.0.4
- mpich@3.0.4%gcc@4.9.0 - mpich @3.0.4
- netgauge@2.4.6%gcc@4.9.0-27912b7b - netgauge @2.4.6
- sundials@2.5.0%gcc@4.9.0-27912b7b - sundials @2.5.0
-
-The names here should look familiar, they're the same ones from
-``spack find``. You *can* use the names here directly. For example,
-you could type either of these commands to load the callpath module:
-
-.. code-block:: sh
-
- use callpath@1.0.1%gcc@4.4.7-5dce4318
-
-.. code-block:: sh
-
- module load callpath@1.0.1%gcc@4.4.7-5dce4318
-
-Neither of these is particularly pretty, easy to remember, or
-easy to type. Luckily, Spack has its own interface for using modules
-and dotkits. You can use the same spec syntax you're used to:
-
- ========================= ==========================
- Environment Modules Dotkit
- ========================= ==========================
- ``spack load <spec>`` ``spack use <spec>``
- ``spack unload <spec>`` ``spack unuse <spec>``
- ========================= ==========================
-
-And you can use the same shortened names you use everywhere else in
-Spack. For example, this will add the ``mpich`` package built with
-``gcc`` to your path:
-
-.. code-block:: sh
-
- $ spack install mpich %gcc@4.4.7
-
- # ... wait for install ...
-
- $ spack use mpich %gcc@4.4.7
- Prepending: mpich@3.0.4%gcc@4.4.7 (ok)
- $ which mpicc
- ~/src/spack/opt/linux-debian7-x86_64/gcc@4.4.7/mpich@3.0.4/bin/mpicc
-
-Or, similarly with modules, you could type:
-
-.. code-block:: sh
-
- $ spack load mpich %gcc@4.4.7
-
-These commands will add appropriate directories to your ``PATH``,
-``MANPATH``, ``CPATH``, and ``LD_LIBRARY_PATH``. When you no longer want to use
-a package, you can type unload or unuse similarly:
-
-.. code-block:: sh
-
- $ spack unload mpich %gcc@4.4.7 # modules
- $ spack unuse mpich %gcc@4.4.7 # dotkit
-
-.. note::
-
- These ``use``, ``unuse``, ``load``, and ``unload`` subcommands are
- only available if you have enabled Spack's shell support *and* you
- have dotkit or modules installed on your machine.
-
-Ambiguous module names
-~~~~~~~~~~~~~~~~~~~~~~~~
-
-If a spec used with load/unload or use/unuse is ambiguous (i.e. more
-than one installed package matches it), then Spack will warn you:
-
-.. code-block:: sh
-
- $ spack load libelf
- ==> Error: Multiple matches for spec libelf. Choose one:
- libelf@0.8.13%gcc@4.4.7 arch=linux-debian7-x86_64
- libelf@0.8.13%intel@15.0.0 arch=linux-debian7-x86_64
-
-You can either type the ``spack load`` command again with a fully
-qualified argument, or you can add just enough extra constraints to
-identify one package. For example, above, the key differentiator is
-that one ``libelf`` is built with the Intel compiler, while the other
-used ``gcc``. You could therefore just type:
-
-.. code-block:: sh
-
- $ spack load libelf %intel
-
-To identify just the one built with the Intel compiler.
-
-Module files generation and customization
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Environment Modules and Dotkit files are generated when packages are installed,
-and are placed in the following directories under the Spack root:
-
-.. code-block:: sh
-
- ${SPACK_ROOT}/share/spack/modules
- ${SPACK_ROOT}/share/spack/dotkit
-
-The content that gets written in each module file can be customized in two ways:
-
- 1. overriding part of the ``spack.Package`` API within a ``package.py``
- 2. writing dedicated configuration files
-
-Override ``Package`` API
-^^^^^^^^^^^^^^^^^^^^^^^^
-There are currently two methods in ``spack.Package`` that may affect the content
-of module files:
-
-.. code-block:: python
-
- def setup_environment(self, spack_env, run_env):
- """Set up the compile and runtime environments for a package."""
- pass
-
-
-Recursive Modules
-``````````````````
-
-In some cases, it is desirable to load not just a module, but also all
-the modules it depends on. This is not required for most modules
-because Spack builds binaries with RPATH support. However, not all
-packages use RPATH to find their dependencies: this can be true in
-particular for Python extensions, which are currently *not* built with
-RPATH.
-
-Modules may be loaded recursively with the ``load`` command's
-``--dependencies`` or ``-r`` argument:
-
-.. code-block:: sh
-
- $ spack load --dependencies <spec> ...
-
-More than one spec may be placed on the command line here.
-
-Module Commands for Shell Scripts
-``````````````````````````````````
-
-Although Spack is flexible, the ``module`` command is much faster.
-This could become an issue when emitting a series of ``spack load``
-commands inside a shell script. By adding the ``--shell`` flag,
-``spack module find`` may also be used to generate code that can be
-cut-and-pasted into a shell script. For example:
-
-.. code-block:: sh
-
- $ spack module find tcl --dependencies --shell py-numpy git
- # bzip2@1.0.6%gcc@4.9.3=linux-x86_64
- module load bzip2-1.0.6-gcc-4.9.3-ktnrhkrmbbtlvnagfatrarzjojmkvzsx
- # ncurses@6.0%gcc@4.9.3=linux-x86_64
- module load ncurses-6.0-gcc-4.9.3-kaazyneh3bjkfnalunchyqtygoe2mncv
- # zlib@1.2.8%gcc@4.9.3=linux-x86_64
- module load zlib-1.2.8-gcc-4.9.3-v3ufwaahjnviyvgjcelo36nywx2ufj7z
- # sqlite@3.8.5%gcc@4.9.3=linux-x86_64
- module load sqlite-3.8.5-gcc-4.9.3-a3eediswgd5f3rmto7g3szoew5nhehbr
- # readline@6.3%gcc@4.9.3=linux-x86_64
- module load readline-6.3-gcc-4.9.3-se6r3lsycrwxyhreg4lqirp6xixxejh3
- # python@3.5.1%gcc@4.9.3=linux-x86_64
- module load python-3.5.1-gcc-4.9.3-5q5rsrtjld4u6jiicuvtnx52m7tfhegi
- # py-setuptools@20.5%gcc@4.9.3=linux-x86_64
- module load py-setuptools-20.5-gcc-4.9.3-4qr2suj6p6glepnedmwhl4f62x64wxw2
- # py-nose@1.3.7%gcc@4.9.3=linux-x86_64
- module load py-nose-1.3.7-gcc-4.9.3-pwhtjw2dvdvfzjwuuztkzr7b4l6zepli
- # openblas@0.2.17%gcc@4.9.3+shared=linux-x86_64
- module load openblas-0.2.17-gcc-4.9.3-pw6rmlom7apfsnjtzfttyayzc7nx5e7y
- # py-numpy@1.11.0%gcc@4.9.3+blas+lapack=linux-x86_64
- module load py-numpy-1.11.0-gcc-4.9.3-mulodttw5pcyjufva4htsktwty4qd52r
- # curl@7.47.1%gcc@4.9.3=linux-x86_64
- module load curl-7.47.1-gcc-4.9.3-ohz3fwsepm3b462p5lnaquv7op7naqbi
- # autoconf@2.69%gcc@4.9.3=linux-x86_64
- module load autoconf-2.69-gcc-4.9.3-bkibjqhgqm5e3o423ogfv2y3o6h2uoq4
- # cmake@3.5.0%gcc@4.9.3~doc+ncurses+openssl~qt=linux-x86_64
- module load cmake-3.5.0-gcc-4.9.3-x7xnsklmgwla3ubfgzppamtbqk5rwn7t
- # expat@2.1.0%gcc@4.9.3=linux-x86_64
- module load expat-2.1.0-gcc-4.9.3-6pkz2ucnk2e62imwakejjvbv6egncppd
- # git@2.8.0-rc2%gcc@4.9.3+curl+expat=linux-x86_64
- module load git-2.8.0-rc2-gcc-4.9.3-3bib4hqtnv5xjjoq5ugt3inblt4xrgkd
-
-The script may be further edited by removing unnecessary modules.
-This script may be directly executed in bash via
-
-.. code-block :: sh
-
- source <( spack module find tcl --dependencies --shell py-numpy git )
-
-
-Regenerating Module files
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. code-block:: python
-
- def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
- """Set up the environment of packages that depend on this one"""
- pass
-
-As briefly stated in the comments, the first method lets you customize the
-module file content for the package you are currently writing, the second
-allows for modifications to your dependees module file. In both cases one
-needs to fill ``run_env`` with the desired list of environment modifications.
-
-Example : ``builtin/packages/python/package.py``
-""""""""""""""""""""""""""""""""""""""""""""""""
-
-The ``python`` package that comes with the ``builtin`` Spack repository
-overrides ``setup_dependent_environment`` in the following way:
-
-.. code-block:: python
-
- def setup_dependent_environment(self, spack_env, run_env, extension_spec):
- # ...
- if extension_spec.package.extends(self.spec):
- run_env.prepend_path('PYTHONPATH', os.path.join(extension_spec.prefix, self.site_packages_dir))
-
-to insert the appropriate ``PYTHONPATH`` modifications in the module
-files of python packages.
-
-Configuration files
-^^^^^^^^^^^^^^^^^^^
-
-Another way of modifying the content of module files is writing a
-``modules.yaml`` configuration file. Following usual Spack conventions, this
-file can be placed either at *site* or *user* scope.
-
-The default site configuration reads:
-
- .. literalinclude:: ../../../etc/spack/modules.yaml
- :language: yaml
-
-It basically inspects the installation prefixes for the
-existence of a few folders and, if they exist, it prepends a path to a given
-list of environment variables.
-
-For each module system that can be enabled a finer configuration is possible:
-
-.. code-block:: yaml
-
- modules:
- tcl:
- # contains environment modules specific customizations
- dotkit:
- # contains dotkit specific customizations
-
-The structure under the ``tcl`` and ``dotkit`` keys is almost equal, and will
-be showcased in the following by some examples.
-
-Select module files by spec constraints
-"""""""""""""""""""""""""""""""""""""""
-Using spec syntax it's possible to have different customizations for different
-groups of module files.
-
-Considering :
-
-.. code-block:: yaml
-
- modules:
- tcl:
- all: # Default addition for every package
- environment:
- set:
- BAR: 'bar'
- ^openmpi:: # A double ':' overrides previous rules
- environment:
- set:
- BAR: 'baz'
- zlib:
- environment:
- prepend_path:
- LD_LIBRARY_PATH: 'foo'
- zlib%gcc@4.8:
- environment:
- unset:
- - FOOBAR
-
-what will happen is that:
-
- - every module file will set ``BAR=bar``
- - unless the associated spec satisfies ``^openmpi`` in which case ``BAR=baz``
- - any spec that satisfies ``zlib`` will additionally prepend ``foo`` to ``LD_LIBRARY_PATH``
- - any spec that satisfies ``zlib%gcc@4.8`` will additionally unset ``FOOBAR``
-
-.. note::
- Order does matter
- The modifications associated with the ``all`` keyword are always evaluated
- first, no matter where they appear in the configuration file. All the other
- spec constraints are instead evaluated top to bottom.
-
-Filter modifications out of module files
-""""""""""""""""""""""""""""""""""""""""
-
-Modifications to certain environment variables in module files are generated by
-default. Suppose you would like to avoid having ``CPATH`` and ``LIBRARY_PATH``
-modified by your dotkit modules. Then :
-
-.. code-block:: yaml
-
- modules:
- dotkit:
- all:
- filter:
- environment_blacklist: ['CPATH', 'LIBRARY_PATH'] # Exclude changes to any of these variables
-
-will generate dotkit module files that will not contain modifications to either
-``CPATH`` or ``LIBRARY_PATH`` and environment module files that instead will
-contain those modifications.
-
-Autoload dependencies
-"""""""""""""""""""""
-
-The following lines in ``modules.yaml``:
-
-.. code-block:: yaml
-
- modules:
- tcl:
- all:
- autoload: 'direct'
-
-will produce environment module files that will automatically load their direct
-dependencies.
-
-.. note::
- Allowed values for ``autoload`` statements
- Allowed values for ``autoload`` statements are either ``none``, ``direct``
- or ``all``. In ``tcl`` configuration it is possible to use the option
- ``prerequisites`` that accepts the same values and will add ``prereq``
- statements instead of automatically loading other modules.
-
-Blacklist or whitelist the generation of specific module files
-""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
-
-Sometimes it is desirable not to generate module files, a common use case being
-not providing the users with software built using the system compiler.
-
-A configuration file like:
-
-.. code-block:: yaml
-
- modules:
- tcl:
- whitelist: ['gcc', 'llvm'] # Whitelist will have precedence over blacklist
- blacklist: ['%gcc@4.4.7'] # Assuming gcc@4.4.7 is the system compiler
-
-will skip module file generation for anything that satisfies ``%gcc@4.4.7``,
-with the exception of specs that satisfy ``gcc`` or ``llvm``.
-
-Customize the naming scheme and insert conflicts
-""""""""""""""""""""""""""""""""""""""""""""""""
-
-A configuration file like:
-
-.. code-block:: yaml
-
- modules:
- tcl:
- naming_scheme: '{name}/{version}-{compiler.name}-{compiler.version}'
- all:
- conflict: ['{name}', 'intel/14.0.1']
-
-will create module files that will conflict with ``intel/14.0.1`` and with the
-base directory of the same module, effectively preventing the possibility to
-load two or more versions of the same software at the same time.
-
-.. note::
- Tokens available for the naming scheme
- currently only the tokens shown in the example are available to construct
- the naming scheme
-
-.. note::
- The ``conflict`` option is ``tcl`` specific
-
-Regenerating module files
-^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Sometimes you may need to regenerate the modules files. For example,
-if newer, fancier module support is added to Spack at some later date,
-you may want to regenerate all the modules to take advantage of these
-new features.
-
-.. _spack-module:
-
-``spack module refresh``
-""""""""""""""""""""""""
-
-Running ``spack module refresh`` will remove the
-``share/spack/modules`` and ``share/spack/dotkit`` directories, then
-regenerate all module and dotkit files from scratch:
-
-.. code-block:: sh
-
- $ spack module refresh
- ==> Regenerating tcl module files.
- ==> Regenerating dotkit module files.
-
-
-.. _extensions:
-
-Filesystem Views
--------------------------------
-
-.. Maybe this is not the right location for this documentation.
-
-The Spack installation area allows for many package installation trees
-to coexist and gives the user choices as to what versions and variants
-of packages to use. To use them, the user must rely on a way to
-aggregate a subset of those packages. The section on Environment
-Modules gives one good way to do that which relies on setting various
-environment variables. An alternative way to aggregate is through
-**filesystem views**.
-
-A filesystem view is a single directory tree which is the union of the
-directory hierarchies of the individual package installation trees
-that have been included. The files of the view's installed packages
-are brought into the view by symbolic or hard links back to their
-location in the original Spack installation area. As the view is
-formed, any clashes due to a file having the exact same path in its
-package installation tree are handled in a first-come-first-served
-basis and a warning is printed. Packages and their dependencies can
-be both added and removed. During removal, empty directories will be
-purged. These operations can be limited to pertain to just the
-packages listed by the user or to exclude specific dependencies and
-they allow for software installed outside of Spack to coexist inside
-the filesystem view tree.
-
-By its nature, a filesystem view represents a particular choice of one
-set of packages among all the versions and variants that are available
-in the Spack installation area. It is thus equivalent to the
-directory hiearchy that might exist under ``/usr/local``. While this
-limits a view to including only one version/variant of any package, it
-provides the benefits of having a simpler and traditional layout which
-may be used without any particular knowledge that its packages were
-built by Spack.
-
-Views can be used for a variety of purposes including:
-
-- A central installation in a traditional layout, eg ``/usr/local`` maintained over time by the sysadmin.
-- A self-contained installation area which may for the basis of a top-level atomic versioning scheme, eg ``/opt/pro`` vs ``/opt/dev``.
-- Providing an atomic and monolithic binary distribution, eg for delivery as a single tarball.
-- Producing ephemeral testing or developing environments.
-
-Using Filesystem Views
-~~~~~~~~~~~~~~~~~~~~~~
-
-A filesystem view is created and packages are linked in by the ``spack
-view`` command's ``symlink`` and ``hardlink`` sub-commands. The
-``spack view remove`` command can be used to unlink some or all of the
-filesystem view.
-
-The following example creates a filesystem view based
-on an installed ``cmake`` package and then removes from the view the
-files in the ``cmake`` package while retaining its dependencies.
-
-.. code-block:: sh
-
-
- $ spack view -v symlink myview cmake@3.5.2
- ==> Linking package: "ncurses"
- ==> Linking package: "zlib"
- ==> Linking package: "openssl"
- ==> Linking package: "cmake"
-
- $ ls myview/
- bin doc etc include lib share
-
- $ ls myview/bin/
- captoinfo clear cpack ctest infotocap openssl tabs toe tset
- ccmake cmake c_rehash infocmp ncurses6-config reset tic tput
-
- $ spack view -v -d false rm myview cmake@3.5.2
- ==> Removing package: "cmake"
-
- $ ls myview/bin/
- captoinfo c_rehash infotocap openssl tabs toe tset
- clear infocmp ncurses6-config reset tic tput
-
-
-Limitations of Filesystem Views
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-This section describes some limitations that should be considered in
-using filesystems views.
-
-Filesystem views are merely organizational. The binary executable
-programs, shared libraries and other build products found in a view
-are mere links into the "real" Spack installation area. If a view is
-built with symbolic links it requires the Spack-installed package to
-be kept in place. Building a view with hardlinks removes this
-requirement but any internal paths (eg, rpath or ``#!`` interpreter
-specifications) will still require the Spack-installed package files
-to be in place.
-
-.. FIXME: reference the relocation work of Hegner and Gartung.
-
-As described above, when a view is built only a single instance of a
-file may exist in the unified filesystem tree. If more than one
-package provides a file at the same path (relative to its own root)
-then it is the first package added to the view that "wins". A warning
-is printed and it is up to the user to determine if the conflict
-matters.
-
-It is up to the user to assure a consistent view is produced. In
-particular if the user excludes packages, limits the following of
-dependencies or removes packages the view may become inconsistent. In
-particular, if two packages require the same sub-tree of dependencies,
-removing one package (recursively) will remove its dependencies and
-leave the other package broken.
-
-
-
-
-
+---------------------------
Extensions & Python support
-------------------------------------
+---------------------------
Spack's installation model assumes that each package will live in its
own install prefix. However, certain packages are typically installed
@@ -1545,21 +855,22 @@ Spack has support for this type of installation as well. In Spack,
a package that can live inside the prefix of another package is called
an *extension*. Suppose you have Python installed like so:
-.. code-block:: sh
+.. code-block:: console
$ spack find python
==> 1 installed packages.
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
python@2.7.8
-.. _spack-extensions:
+.. _cmd-spack-extensions:
+^^^^^^^^^^^^^^^^^^^^
``spack extensions``
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
You can find extensions for your Python installation like this:
-.. code-block:: sh
+.. code-block:: console
$ spack extensions python
==> python@2.7.8%gcc@4.4.7 arch=linux-debian7-x86_64-703c7a96
@@ -1584,11 +895,11 @@ You can find extensions for your Python installation like this:
The extensions are a subset of what's returned by ``spack list``, and
they are packages like any other. They are installed into their own
-prefixes, and you can see this with ``spack find -p``:
+prefixes, and you can see this with ``spack find --paths``:
-.. code-block:: sh
+.. code-block:: console
- $ spack find -p py-numpy
+ $ spack find --paths py-numpy
==> 1 installed packages.
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
py-numpy@1.9.1 /g/g21/gamblin2/src/spack/opt/linux-debian7-x86_64/gcc@4.4.7/py-numpy@1.9.1-66733244
@@ -1596,7 +907,7 @@ prefixes, and you can see this with ``spack find -p``:
However, even though this package is installed, you cannot use it
directly when you run ``python``:
-.. code-block:: sh
+.. code-block:: console
$ spack load python
$ python
@@ -1609,8 +920,9 @@ directly when you run ``python``:
ImportError: No module named numpy
>>>
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Extensions & Environment Modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are two ways to get ``numpy`` working in Python. The first is
to use :ref:`shell-support`. You can simply ``use`` or ``load`` the
@@ -1619,14 +931,14 @@ in your current shell.
For tcl modules:
-.. code-block:: sh
+.. code-block:: console
$ spack load python
$ spack load py-numpy
or, for dotkit:
-.. code-block:: sh
+.. code-block:: console
$ spack use python
$ spack use py-numpy
@@ -1634,9 +946,9 @@ or, for dotkit:
Now ``import numpy`` will succeed for as long as you keep your current
session open.
-
+^^^^^^^^^^^^^^^^^^^^^
Activating Extensions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^
It is often desirable to have certain packages *always* available as
part of a Python installation. Spack offers a more permanent solution
@@ -1644,12 +956,13 @@ for this case. Instead of requiring users to load particular
environment modules, you can *activate* the package within the Python
installation:
-.. _spack-activate:
+.. _cmd-spack-activate:
+^^^^^^^^^^^^^^^^^^
``spack activate``
-^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
-.. code-block:: sh
+.. code-block:: console
$ spack activate py-numpy
==> Activated extension py-setuptools@11.3.1%gcc@4.4.7 arch=linux-debian7-x86_64-3c74eb69 for python@2.7.8%gcc@4.4.7.
@@ -1666,7 +979,7 @@ once all dependencies were activated in the ``python`` installation,
If we run ``spack extensions`` again, we now see the three new
packages listed as activated:
-.. code-block:: sh
+.. code-block:: console
$ spack extensions python
==> python@2.7.8%gcc@4.4.7 arch=linux-debian7-x86_64-703c7a96
@@ -1691,7 +1004,6 @@ packages listed as activated:
-- linux-debian7-x86_64 / gcc@4.4.7 --------------------------------
py-nose@1.3.4 py-numpy@1.9.1 py-setuptools@11.3.1
-
Now, when a user runs python, ``numpy`` will be available for import
*without* the user having to explicitly loaded. ``python@2.7.8`` now
acts like a system Python installation with ``numpy`` installed inside
@@ -1709,50 +1021,56 @@ into the same prefix. Users who want a different version of a package
can still get it by using environment modules, but they will have to
explicitly load their preferred version.
-``spack activate -f``
-^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+``spack activate --force``
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
If, for some reason, you want to activate a package *without* its
-dependencies, you can use ``spack activate -f``:
+dependencies, you can use ``spack activate --force``:
-.. code-block:: sh
+.. code-block:: console
- $ spack activate -f py-numpy
+ $ spack activate --force py-numpy
==> Activated extension py-numpy@1.9.1%gcc@4.4.7 arch=linux-debian7-x86_64-66733244 for python@2.7.8%gcc@4.4.7.
-.. _spack-deactivate:
+.. _cmd-spack-deactivate:
+^^^^^^^^^^^^^^^^^^^^
``spack deactivate``
-^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^
We've seen how activating an extension can be used to set up a default
version of a Python module. Obviously, you may want to change that at
some point. ``spack deactivate`` is the command for this. There are
several variants:
- * ``spack deactivate <extension>`` will deactivate a single
- extension. If another activated extension depends on this one,
- Spack will warn you and exit with an error.
- * ``spack deactivate -f <extension>`` deactivates an extension
- regardless of packages that depend on it.
- * ``spack deactivate -a <extension>`` deactivates an extension and
- all of its dependencies. Use ``-f`` to disregard dependents.
- * ``spack deactivate -a <extendee>`` deactivates *all* activated
- extensions of a package. For example, to deactivate *all* python
- extensions, use::
+* ``spack deactivate <extension>`` will deactivate a single
+ extension. If another activated extension depends on this one,
+ Spack will warn you and exit with an error.
+* ``spack deactivate --force <extension>`` deactivates an extension
+ regardless of packages that depend on it.
+* ``spack deactivate --all <extension>`` deactivates an extension and
+ all of its dependencies. Use ``--force`` to disregard dependents.
+* ``spack deactivate --all <extendee>`` deactivates *all* activated
+ extensions of a package. For example, to deactivate *all* python
+ extensions, use:
+
+ .. code-block:: console
- spack deactivate -a python
+ $ spack deactivate --all python
+-----------------------
Filesystem requirements
---------------------------
+-----------------------
Spack currently needs to be run from a filesystem that supports
``flock`` locking semantics. Nearly all local filesystems and recent
versions of NFS support this, but parallel filesystems may be mounted
without ``flock`` support enabled. You can determine how your
- filesystems are mounted with ``mount -p``. The output for a Lustre
+filesystems are mounted with ``mount -p``. The output for a Lustre
filesystem might look like this:
-.. code-block:: sh
+.. code-block:: console
$ mount -l | grep lscratch
pilsner-mds1-lnet0@o2ib100:/lsd on /p/lscratchd type lustre (rw,nosuid,noauto,_netdev,lazystatfs,flock)
@@ -1764,7 +1082,7 @@ system administrator to enable ``flock``.
This issue typically manifests with the error below:
-.. code-block:: sh
+.. code-block:: console
$ ./spack find
Traceback (most recent call last):
@@ -1773,7 +1091,7 @@ This issue typically manifests with the error below:
File "./spack", line 154,' in main
return_val = command(parser, args)
File "./spack/lib/spack/spack/cmd/find.py", line 170, in find
- specs = set(spack.installed_db.query(**q_args))
+ specs = set(spack.installed_db.query(\**q_args))
File "./spack/lib/spack/spack/database.py", line 551, in query
with self.read_transaction():
File "./spack/lib/spack/spack/database.py", line 598, in __enter__
@@ -1789,155 +1107,18 @@ This issue typically manifests with the error below:
A nicer error message is TBD in future versions of Spack.
-.. _spack-cray:
-
-Spack on Cray
------------------------------
-
-Spack differs slightly when used on a Cray system. The architecture spec
-can differentiate between the front-end and back-end processor and operating system.
-For example, on Edison at NERSC, the back-end target processor
-is \"Ivy Bridge\", so you can specify to use the back-end this way:
-
-.. code-block:: sh
-
- spack install zlib target=ivybridge
-
-You can also use the operating system to build against the back-end:
-
-.. code-block:: sh
-
- spack install zlib os=CNL10
-
-Notice that the name includes both the operating system name and the major
-version number concatenated together.
-
-Alternatively, if you want to build something for the front-end,
-you can specify the front-end target processor. The processor for a login node
-on Edison is \"Sandy bridge\" so we specify on the command line like so:
-
-.. code-block:: sh
-
- spack install zlib target=sandybridge
-
-And the front-end operating system is:
-
-.. code-block:: sh
-
- spack install zlib os=SuSE11
-
-
-
-Cray compiler detection
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Spack can detect compilers using two methods. For the front-end, we treat
-everything the same. The difference lies in back-end compiler detection.
-Back-end compiler detection is made via the Tcl module avail command.
-Once it detects the compiler it writes the appropriate PrgEnv and compiler
-module name to compilers.yaml and sets the paths to each compiler with Cray\'s
-compiler wrapper names (i.e. cc, CC, ftn). During build time, Spack will load
-the correct PrgEnv and compiler module and will call appropriate wrapper.
-
-The compilers.yaml config file will also differ. There is a
-modules section that is filled with the compiler\'s Programming Environment
-and module name. On other systems, this field is empty []::
-
- ...
- - compiler:
- modules:
- - PrgEnv-intel
- - intel/15.0.109
- ...
-
-As mentioned earlier, the compiler paths will look different on a Cray system.
-Since most compilers are invoked using cc, CC and ftn, the paths for each
-compiler are replaced with their respective Cray compiler wrapper names::
-
- ...
- paths:
- cc: cc
- cxx: CC
- f77: ftn
- fc: ftn
- ...
-
-As opposed to an explicit path to the compiler executable. This allows Spack
-to call the Cray compiler wrappers during build time.
-
-For more on compiler configuration, check out :ref:`compiler-config`.
-
-Spack sets the default Cray link type to dynamic, to better match other
-other platforms. Individual packages can enable static linking (which is the
-default outside of Spack on cray systems) using the -static flag.
-
-Setting defaults and using Cray modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-If you want to use default compilers for each PrgEnv and also be able
-to load cray external modules, you will need to set up a packages.yaml.
-
-Here\'s an example of an external configuration for cray modules:
-
-.. code-block:: yaml
-
- packages:
- mpi:
- modules:
- mpich@7.3.1%gcc@5.2.0 arch=cray_xc-haswell-CNL10: cray-mpich
- mpich@7.3.1%intel@16.0.0.109 arch=cray_xc-haswell-CNL10: cray-mpich
-
-This tells Spack that for whatever package that depends on mpi, load the
-cray-mpich module into the environment. You can then be able to use whatever
-environment variables, libraries, etc, that are brought into the environment
-via module load.
-
-You can set the default compiler that Spack can use for each compiler type.
-If you want to use the Cray defaults, then set them under *all:* in packages.yaml.
-In the compiler field, set the compiler specs in your order of preference.
-Whenever you build with that compiler type, Spack will concretize to that version.
-
-Here is an example of a full packages.yaml used at NERSC
-
-.. code-block:: yaml
-
- packages:
- mpi:
- modules:
- mpich@7.3.1%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-mpich
- mpich@7.3.1%intel@16.0.0.109 arch=cray_xc-SuSE11-ivybridge: cray-mpich
- buildable: False
- netcdf:
- modules:
- netcdf@4.3.3.1%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-netcdf
- netcdf@4.3.3.1%intel@16.0.0.109 arch=cray_xc-CNL10-ivybridge: cray-netcdf
- buildable: False
- hdf5:
- modules:
- hdf5@1.8.14%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-hdf5
- hdf5@1.8.14%intel@16.0.0.109 arch=cray_xc-CNL10-ivybridge: cray-hdf5
- buildable: False
- all:
- compiler: [gcc@5.2.0, intel@16.0.0.109]
-
-Here we tell spack that whenever we want to build with gcc use version 5.2.0 or
-if we want to build with intel compilers, use version 16.0.0.109. We add a spec
-for each compiler type for each cray modules. This ensures that for each
-compiler on our system we can use that external module.
-
-
-For more on external packages check out the section :ref:`sec-external_packages`.
-
+------------
Getting Help
------------------------
+------------
-.. _spack-help:
+.. _cmd-spack-help:
+^^^^^^^^^^^^^^
``spack help``
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
If you don't find what you need here, the ``help`` subcommand will
-print out out a list of *all* of ``spack``'s options and subcommands:
+print out out a list of *all* of spack's options and subcommands:
.. command-output:: spack help
@@ -1946,5 +1127,5 @@ usage information for a particular subcommand:
.. command-output:: spack help install
-Alternately, you can use ``spack -h`` in place of ``spack help``, or
-``spack <subcommand> -h`` to get help on a particular subcommand.
+Alternately, you can use ``spack --help`` in place of ``spack help``, or
+``spack <subcommand> --help`` to get help on a particular subcommand.
diff --git a/lib/spack/docs/basic_usage_tutorial.rst b/lib/spack/docs/basic_usage_tutorial.rst
new file mode 100644
index 0000000000..cf8c654c0f
--- /dev/null
+++ b/lib/spack/docs/basic_usage_tutorial.rst
@@ -0,0 +1,1253 @@
+.. _basics_tutorial:
+
+=========================================
+Basic Installation Tutorial
+=========================================
+
+This tutorial will guide you through the process of installing software
+using Spack. We will first cover the `spack install` command, focusing on
+the power of the spec syntax and the flexibility it gives to users. We
+will also cover the `spack find` command for viewing installed packages
+and the `spack uninstall` command. Finally, we will touch on how Spack
+manages compilers, especially as it relates to using Spack-built
+compilers within Spack. We will include full output from all of the
+commands demonstrated, although we will frequently call attention to only
+small portions of that output (or merely to the fact that it
+succeeded). The provided output is all from a cluster running Red Hat
+Enterprise Linux.
+
+.. _basics-tutorial-install:
+
+----------------
+Installing Spack
+----------------
+
+Spack works out of the box. Simply clone spack and get going.
+
+.. code-block:: console
+
+ becker33:~$ git clone https://github.com/LLNL/spack.git
+ Initialized empty Git repository in /g/g0/becker33/testclone/spack/.git/
+ remote: Counting objects: 47125, done.
+ remote: Compressing objects: 100% (68/68), done.
+ remote: Total 47125 (delta 16), reused 2 (delta 2), pack-reused 47047
+ Receiving objects: 100% (47125/47125), 12.02 MiB | 2.11 MiB/s, done.
+ Resolving deltas: 100% (23044/23044), done.
+ becker33:~$ cd spack
+
+Then add Spack to your path.
+
+.. code-block:: console
+
+ becker33:spack$ export PATH=~/spack/bin/spack:$PATH
+
+You're good to go!
+
+-----------------
+What is in Spack?
+-----------------
+
+The ``spack list`` command shows available packages.
+
+.. code-block:: console
+
+ becker33:spack$ spack list
+ ==> 1016 packages.
+ abinit hwloc piranha r-rjava
+ ack hydra pixman r-rjson
+ activeharmony hypre pkg-config r-rjsonio
+ ...
+
+The ``spack list`` command can also take a query string. Spack
+automatically adds wildcards to both ends of the string. For example,
+we can view all available python packages.
+
+.. code-block:: console
+
+ becker33:spack$ spack list py
+ ==> 129 packages.
+ py-3to2 py-epydoc py-nestle py-pycparser py-six
+ py-alabaster py-flake8 py-netcdf py-pydatalog py-sncosmo
+ py-argcomplete py-funcsigs py-networkx py-pyelftools py-snowballstemmer
+ ...
+
+-------------------
+Installing Packages
+-------------------
+
+Installing a package with Spack is very simple. To install a piece of
+software, simply type ``spack install <package_name>``
+
+.. code-block:: console
+
+ $ spack install libelf
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.13.tar.gz
+ curl: (37) Couldn't open file /g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.13.tar.gz
+ ==> Fetching from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.13.tar.gz failed.
+ ==> Trying to fetch from http://www.mr511.de/software/libelf-0.8.13.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-csrt4qxfkhjgn5xg3zjpkir7xdnszl2a/libelf-0.8.13.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-csrt4qxfkhjgn5xg3zjpkir7xdnszl2a
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 1.21s. Build: 8.42s. Total: 9.62s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.13-csrt4qxfkhjgn5xg3zjpkir7xdnszl2a
+
+
+Spack's spec syntax is the interface by which we can request specific
+configurations of the package. The ``%`` sigil is used to specify
+compilers.
+
+.. code-block:: console
+
+ $ spack install libelf %intel
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.13.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-7wgp32xksatkvw2tbssmehw2t5tnxndj/libelf-0.8.13.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-7wgp32xksatkvw2tbssmehw2t5tnxndj
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 0.09s. Build: 50.64s. Total: 50.72s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/intel-16.0.3/libelf-0.8.13-7wgp32xksatkvw2tbssmehw2t5tnxndj
+
+Note that this installation is located separately from the previous
+one. We will discuss this in more detail later, but this is part of what
+allows Spack to support arbitrarily versioned software.
+
+You can check for particular versions before requesting them. We will
+use the ``spack versions`` command to see the available versions, and then
+install a different version of ``libelf``.
+
+.. code-block:: console
+
+ $ spack versions libelf
+ ==> Safe versions (already checksummed):
+ 0.8.13
+ 0.8.12
+ ==> Remote versions (not yet checksummed):
+ 0.8.11
+ 0.8.10
+ 0.8.9
+ 0.8.8
+ 0.8.7
+ 0.8.6
+ 0.8.5
+ 0.8.4
+ 0.8.3
+ 0.8.2
+ 0.8.0
+ 0.7.0
+ 0.6.4
+ 0.5.2
+
+
+The ``@`` sigil is used to specify versions, both of packages and of
+compilers.
+
+.. code-block:: console
+
+ $ spack install libelf @0.8.12
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.12.tar.gz
+ curl: (37) Couldn't open file /g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.12.tar.gz
+ ==> Fetching from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.12.tar.gz failed.
+ ==> Trying to fetch from http://www.mr511.de/software/libelf-0.8.12.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-ipggckv6i7h44iryzfa4dwdela32a7fy/libelf-0.8.12.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-ipggckv6i7h44iryzfa4dwdela32a7fy
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 1.12s. Build: 7.88s. Total: 9.00s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.12-ipggckv6i7h44iryzfa4dwdela32a7fy
+
+
+
+ $ spack install libelf %intel@15.0.4
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.13.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-w33hrejdyqu2j2gggdswitls2zv6kdsi/libelf-0.8.13.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.13-w33hrejdyqu2j2gggdswitls2zv6kdsi
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 0.09s. Build: 55.51s. Total: 55.60s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/intel-15.0.4/libelf-0.8.13-w33hrejdyqu2j2gggdswitls2zv6kdsi
+
+
+The spec syntax also includes compiler flags. Spack accepts ``cppflags``,
+``cflags``, ``cxxflags``, ``fflags``, ``ldflags``, and ``ldlibs``
+parameters. The values of these fields must be escape-quoted with ``\"``
+on the command line. These values are injected into the compile line
+automatically by the Spack compiler wrappers.
+
+.. code-block:: console
+
+ $ spack install libelf @0.8.12 cppflags=\"-O3\"
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.12.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-vrv2ttbd34xlfoxy4jwt6qsjrcbalmmw/libelf-0.8.12.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-vrv2ttbd34xlfoxy4jwt6qsjrcbalmmw
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 0.04s. Build: 7.95s. Total: 7.99s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.12-vrv2ttbd34xlfoxy4jwt6qsjrcbalmmw
+
+
+The ``spack find`` command is used to query installed packages. Note that
+some packages appear identical with the default output. The ``-l`` flag
+shows the hash of each package, and the ``-f`` flag shows any non-empty
+compiler flags of those packages.
+
+.. code-block:: console
+
+ $ spack find
+ ==> 5 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ libelf@0.8.12
+ libelf@0.8.12
+ libelf@0.8.13
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ libelf@0.8.13
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ libelf@0.8.13
+
+
+
+ $ spack find -lf
+ ==> 5 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ ipggckv libelf@0.8.12%gcc
+
+ vrv2ttb libelf@0.8.12%gcc cppflags="-O3"
+
+ csrt4qx libelf@0.8.13%gcc
+
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ w33hrej libelf@0.8.13%intel
+
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ 7wgp32x libelf@0.8.13%intel
+
+
+Spack generates a hash for each spec. This hash is a function of the full
+provenance of the package, so any change to the spec affects the
+hash. Spack uses this value to compare specs and to generate unique
+installation directories for every combinatorial version. As we move into
+more complicated packages with software dependencies, we can see that
+Spack reuses existing packages to satisfy a dependency only when the
+existing package's hash matches the desired spec.
+
+.. code-block:: console
+
+ $ spack install libdwarf
+ ==> Installing libdwarf
+ ==> libelf is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.13-csrt4qxfkhjgn5xg3zjpkir7xdnszl2a
+ ==> Can not find version 20160507 in url_list
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libdwarf/libdwarf-20160507.tar.gz
+ curl: (37) Couldn't open file /g/g0/becker33/SC16-spack/spack/var/spack/cache/libdwarf/libdwarf-20160507.tar.gz
+ ==> Fetching from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libdwarf/libdwarf-20160507.tar.gz failed.
+ ==> Trying to fetch from http://www.prevanders.net/libdwarf-20160507.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-yfx6p3g3rkmqvcqbmtb34o6pln7pqvcz/libdwarf-20160507.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-yfx6p3g3rkmqvcqbmtb34o6pln7pqvcz
+ ==> No patches needed for libdwarf
+ ==> Building libdwarf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libdwarf
+ Fetch: 1.56s. Build: 33.59s. Total: 35.15s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libdwarf-20160507-yfx6p3g3rkmqvcqbmtb34o6pln7pqvcz
+
+
+Dependencies can be explicitly requested using the ``^`` sigil. Note that
+the spec syntax is recursive. Anything we could specify about the
+top-level package, we can also specify about a dependency using ``^``.
+
+.. code-block:: console
+
+ $ spack install libdwarf ^libelf @0.8.12 %intel
+ ==> Installing libdwarf
+ ==> Installing libelf
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libelf/libelf-0.8.12.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-4blbe3qxqct3ymrfoxxnxysmybvbxay7/libelf-0.8.12.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libelf-0.8.12-4blbe3qxqct3ymrfoxxnxysmybvbxay7
+ ==> No patches needed for libelf
+ ==> Building libelf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libelf
+ Fetch: 0.04s. Build: 52.16s. Total: 52.19s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/intel-16.0.3/libelf-0.8.12-4blbe3qxqct3ymrfoxxnxysmybvbxay7
+ ==> Can not find version 20160507 in url_list
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libdwarf/libdwarf-20160507.tar.gz
+ ################################################################################################################################################################################# 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-csruprgucaujkfkrcywhwou7nbeis5fo/libdwarf-20160507.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-csruprgucaujkfkrcywhwou7nbeis5fo
+ ==> No patches needed for libdwarf
+ ==> Building libdwarf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libdwarf
+ Fetch: 0.40s. Build: 2m 17.29s. Total: 2m 17.69s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/intel-16.0.3/libdwarf-20160507-csruprgucaujkfkrcywhwou7nbeis5fo
+
+
+Packages can also be referred to from the command line by their package
+hash. Using the ``spack find -lf`` command earlier we saw that the hash
+of our optimized installation of libelf (``cppflags=\"-O3\"``) began with
+``vrv2ttb``. We can now explicitly build with that package without typing
+the entire spec, by using the ``/`` sigil to refer to it by hash. As with
+other tools like git, you do not need to specify an *entire* hash on the
+command line. You can specify just enough digits to identify a hash
+uniquely. If a hash prefix is ambiguous (i.e., two or more installed
+packages share the prefix) then spack will report an error.
+
+.. code-block:: console
+
+ $ spack install libdwarf ^/vrv2ttb
+ ==> Installing libdwarf
+ ==> libelf is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.12-vrv2ttbd34xlfoxy4jwt6qsjrcbalmmw
+ ==> Can not find version 20160507 in url_list
+ ==> Trying to fetch from file:///g/g0/becker33/SC16-spack/spack/var/spack/cache/libdwarf/libdwarf-20160507.tar.gz
+ #################################################################################################################################################################################################################################################### 100.0%
+ ==> Staging archive: /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-dtg3tgnp7htccoly26gduqlrgvnwcp5t/libdwarf-20160507.tar.gz
+ ==> Created stage in /g/g0/becker33/SC16-spack/spack/var/spack/stage/libdwarf-20160507-dtg3tgnp7htccoly26gduqlrgvnwcp5t
+ ==> No patches needed for libdwarf
+ ==> Building libdwarf [Package]
+ ==> Executing phase : 'install'
+ ==> Successfully installed libdwarf
+ Fetch: 0.96s. Build: 24.03s. Total: 24.99s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libdwarf-20160507-dtg3tgnp7htccoly26gduqlrgvnwcp5t
+
+
+The ``spack find`` command can also take a ``-d`` flag, which can show
+dependency information. Note that each package has a top-level entry,
+even if it also appears as a dependency.
+
+.. code-block:: console
+
+ $ spack find -ldf
+ ==> 9 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ dtg3tgn libdwarf@20160507%gcc
+ vrv2ttb ^libelf@0.8.12%gcc cppflags="-O3"
+
+ yfx6p3g libdwarf@20160507%gcc
+ csrt4qx ^libelf@0.8.13%gcc
+
+ ipggckv libelf@0.8.12%gcc
+
+ vrv2ttb libelf@0.8.12%gcc cppflags="-O3"
+
+ csrt4qx libelf@0.8.13%gcc
+
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ w33hrej libelf@0.8.13%intel
+
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ csruprg libdwarf@20160507%intel
+ 4blbe3q ^libelf@0.8.12%intel
+
+ 4blbe3q libelf@0.8.12%intel
+
+ 7wgp32x libelf@0.8.13%intel
+
+
+As we get to more complex packages, full installs will take too long to
+build in the time allotted for this tutorial. Our collaborators at CERN
+have been working on binary caching for Spack, which would allow for very
+fast installs of previously built packages. We are still working out the
+security ramifications of the feature, but it is coming soon.
+
+For now, we will switch to doing "fake" installs. When supplied with the
+``--fake`` flag (primarily used for debugging), Spack computes build
+metadata the same way it normally would, but it does not download the
+source or run the install script for a pacakge. We can use this to
+quickly demonstrate some of the more advanced Spack features in our
+limited tutorial time.
+
+``HDF5`` is an example of a more complicated package, with an MPI
+dependency. If we install it "out of the box," it will build with
+``openmpi``.
+
+.. code-block:: console
+
+ $ spack install --fake hdf5
+ ==> Installing hdf5
+ ==> Installing zlib
+ ==> Building zlib [Package]
+ ==> Successfully installed zlib
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Installing openmpi
+ ==> Installing hwloc
+ ==> Installing libpciaccess
+ ==> Installing util-macros
+ ==> Building util-macros [Package]
+ ==> Successfully installed util-macros
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/util-macros-1.19.0-pc6zhs4cnkmg2cv4et4fizsp6scuvacg
+ ==> Installing libtool
+ ==> Installing m4
+ ==> Installing libsigsegv
+ ==> Building libsigsegv [Package]
+ ==> Successfully installed libsigsegv
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libsigsegv-2.10-q4cok3yber7lhf3jswg6mysg7oi53unh
+ ==> Building m4 [Package]
+ ==> Successfully installed m4
+ Fetch: . Build: 0.23s. Total: 0.23s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> Building libtool [Package]
+ ==> Successfully installed libtool
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libtool-2.4.6-rdx5nkfjwlvcanz5il3ys2pe34j4vxx5
+ ==> Installing pkg-config
+ ==> Building pkg-config [Package]
+ ==> Successfully installed pkg-config
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/pkg-config-0.29.1-wpjnlzahdw6ahkrgmqyeugkj2zhv4tui
+ ==> Building libpciaccess [Package]
+ ==> Successfully installed libpciaccess
+ Fetch: . Build: 0.10s. Total: 0.10s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libpciaccess-0.13.4-m2f6fpm22rpprq2ihkmfx6llf363264m
+ ==> Building hwloc [Package]
+ ==> Successfully installed hwloc
+ Fetch: . Build: 0.23s. Total: 0.23s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hwloc-1.11.4-xpb6hbl2hsze25cgdgfnoppn6rchhzaz
+ ==> Building openmpi [Package]
+ ==> Successfully installed openmpi
+ Fetch: . Build: 0.35s. Total: 0.35s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> Building hdf5 [AutotoolsPackage]
+ ==> Successfully installed hdf5
+ Fetch: . Build: 0.61s. Total: 0.61s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-ezvtnox35albuaxqryuondweyjgeo6es
+
+
+Spack packages can also have variants. Boolean variants can be specified
+using the ``+`` and ``~`` or ``-`` sigils. There are two sigils for
+``False`` to avoid conflicts with shell parsing in different
+situations. Variants (boolean or otherwise) can also be specified using
+the same syntax as compiler flags. Here we can install HDF5 without MPI
+support.
+
+.. code-block:: console
+
+ $ spack install --fake hdf5~mpi
+ ==> Installing hdf5
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Building hdf5 [AutotoolsPackage]
+ ==> Successfully installed hdf5
+ Fetch: . Build: 0.22s. Total: 0.22s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-twppaioxqn6lti4grgopnmhwcq3h2rpw
+
+
+We might also want to install HDF5 with a different MPI
+implementation. While MPI is not a package itself, packages can depend on
+abstract interfaces like MPI. Spack handles these through "virtual
+dependencies." A package, such as HDF5, can depend on the MPI
+interface. Other packages (``openmpi``, ``mpich``, ``mvapich``, etc.)
+provide the MPI interface. Any of these providers can be requested for
+an MPI dependency. For example, we can build HDF5 with MPI support
+provided by mpich by specifying a dependency on ``mpich``. Spack also
+supports versioning of virtual dependencies. A package can depend on the
+MPI interface at version 3, and provider packages specify what version of
+the interface *they* provide. The partial spec ``^mpi@3`` can be safisfied
+by any of several providers.
+
+.. code-block:: console
+
+ $ spack install --fake hdf5+mpi ^mpich
+ ==> Installing hdf5
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Building hdf5 [AutotoolsPackage]
+ ==> Successfully installed hdf5
+ Fetch: . Build: 0.38s. Total: 0.38s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-j36yfw25i6gdd3q4vwlupgkpwic4ua6m
+
+
+We'll do a quick check in on what we have installed so far.
+
+.. code-block:: console
+
+ $ spack find -ldf
+ ==> 22 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ twppaio hdf5@1.10.0-patch1%gcc
+ ayc4jq7 ^zlib@1.2.8%gcc
+
+ j36yfw2 hdf5@1.10.0-patch1%gcc
+ 5jlp2nd ^mpich@3.2%gcc
+ ayc4jq7 ^zlib@1.2.8%gcc
+
+ ezvtnox hdf5@1.10.0-patch1%gcc
+ j4cgoq4 ^openmpi@2.0.1%gcc
+ xpb6hbl ^hwloc@1.11.4%gcc
+ m2f6fpm ^libpciaccess@0.13.4%gcc
+ ayc4jq7 ^zlib@1.2.8%gcc
+
+ xpb6hbl hwloc@1.11.4%gcc
+ m2f6fpm ^libpciaccess@0.13.4%gcc
+
+ dtg3tgn libdwarf@20160507%gcc
+ vrv2ttb ^libelf@0.8.12%gcc cppflags="-O3"
+
+ yfx6p3g libdwarf@20160507%gcc
+ csrt4qx ^libelf@0.8.13%gcc
+
+ ipggckv libelf@0.8.12%gcc
+
+ vrv2ttb libelf@0.8.12%gcc cppflags="-O3"
+
+ csrt4qx libelf@0.8.13%gcc
+
+ m2f6fpm libpciaccess@0.13.4%gcc
+
+ q4cok3y libsigsegv@2.10%gcc
+
+ rdx5nkf libtool@2.4.6%gcc
+
+ qijdzvh m4@1.4.17%gcc
+ q4cok3y ^libsigsegv@2.10%gcc
+
+ 5jlp2nd mpich@3.2%gcc
+
+ j4cgoq4 openmpi@2.0.1%gcc
+ xpb6hbl ^hwloc@1.11.4%gcc
+ m2f6fpm ^libpciaccess@0.13.4%gcc
+
+ wpjnlza pkg-config@0.29.1%gcc
+
+ pc6zhs4 util-macros@1.19.0%gcc
+
+ ayc4jq7 zlib@1.2.8%gcc
+
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ w33hrej libelf@0.8.13%intel
+
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ csruprg libdwarf@20160507%intel
+ 4blbe3q ^libelf@0.8.12%intel
+
+ 4blbe3q libelf@0.8.12%intel
+
+ 7wgp32x libelf@0.8.13%intel
+
+
+Spack models the dependencies of packages as a directed acyclic graph
+(DAG). The ``spack find -d`` command shows the tree representation of
+that graph. We can also use the ``spack graph`` command to view the entire
+DAG as a graph.
+
+.. code-block:: console
+
+ $ spack graph hdf5+mpi ^mpich
+ o hdf5
+ |\
+ o | zlib
+ /
+ o mpich
+
+You may also have noticed that there are some packages shown in the
+``spack find -d`` output that we didn't install explicitly. These are
+dependencies that were installed implicitly. A few packages installed
+implicitly are not shown as dependencies in the ``spack find -d``
+output. These are build dependencies. For example, ``libpciaccess`` is a
+dependency of openmpi and requires m4 to build. Spack will build `m4`` as
+part of the installation of ``openmpi``, but it does not become a part of
+the DAG because it is not linked in at run time. Spack handles build
+dependencies differently because of their different (less strict)
+consistency requirements. It is entirely possible to have two packages
+using different versions of a dependency to build, which obviously cannot
+be done with linked dependencies.
+
+``HDF5`` is more complicated than our basic example of libelf and
+libdwarf, but it's still within the realm of software that an experienced
+HPC user could reasonably expect to install given a bit of time. Now
+let's look at a more complicated package.
+
+.. code-block:: console
+
+ $ spack install --fake trilinos
+ ==> Installing trilinos
+ ==> Installing superlu-dist
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> Installing parmetis
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> Installing cmake
+ ==> Installing bzip2
+ ==> Building bzip2 [Package]
+ ==> Successfully installed bzip2
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/bzip2-1.0.6-gll2xsahysy7ji5gkmfxwkofdt3mwjhs
+ ==> expat is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/expat-2.2.0-mg5kwd3kluxdgorj32vzbp7aksg3vqej
+ ==> Installing ncurses
+ ==> Building ncurses [Package]
+ ==> Successfully installed ncurses
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/ncurses-6.0-fttg4astvrtq2buey4wq66tnyu7bgj2c
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Installing openssl
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Building openssl [Package]
+ ==> Successfully installed openssl
+ Fetch: . Build: 0.23s. Total: 0.23s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openssl-1.0.2j-kt5xyk2dkho6tzadnqlbnbujmljprylg
+ ==> Installing libarchive
+ ==> Installing lzma
+ ==> Building lzma [Package]
+ ==> Successfully installed lzma
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/lzma-4.32.7-hah2cdo3zbulz6yg5do6dvnfn6en5v5c
+ ==> Installing nettle
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> Installing gmp
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> Building gmp [AutotoolsPackage]
+ ==> Successfully installed gmp
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gmp-6.1.1-uwn4gfdtq3sywy5uf4f7znrh66oybikf
+ ==> Building nettle [Package]
+ ==> Successfully installed nettle
+ Fetch: . Build: 0.18s. Total: 0.18s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/nettle-3.2-w4ieitifcmrldo4ra7as63apagzf56ja
+ ==> bzip2 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/bzip2-1.0.6-gll2xsahysy7ji5gkmfxwkofdt3mwjhs
+ ==> expat is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/expat-2.2.0-mg5kwd3kluxdgorj32vzbp7aksg3vqej
+ ==> Installing libxml2
+ ==> Installing xz
+ ==> Building xz [Package]
+ ==> Successfully installed xz
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/xz-5.2.2-bxh6cpyqqozazm5okvjqk23sww3gccnf
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Building libxml2 [Package]
+ ==> Successfully installed libxml2
+ Fetch: . Build: 0.35s. Total: 0.35s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libxml2-2.9.4-un323rppyu5qipkegyf7flmymvtmunrx
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Installing lz4
+ ==> Building lz4 [Package]
+ ==> Successfully installed lz4
+ Fetch: . Build: 0.12s. Total: 0.12s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/lz4-131-ivy2fcaw7ywujx74weebdi5bsm7q4vkc
+ ==> openssl is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openssl-1.0.2j-kt5xyk2dkho6tzadnqlbnbujmljprylg
+ ==> xz is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/xz-5.2.2-bxh6cpyqqozazm5okvjqk23sww3gccnf
+ ==> Installing lzo
+ ==> Building lzo [AutotoolsPackage]
+ ==> Successfully installed lzo
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/lzo-2.09-dlgnm74ozo6baactkft5oah2jre2ri2i
+ ==> Building libarchive [Package]
+ ==> Successfully installed libarchive
+ Fetch: . Build: 1.35s. Total: 1.35s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libarchive-3.2.1-biq3kebw7vel7njusill7vv7mjldkqjv
+ ==> xz is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/xz-5.2.2-bxh6cpyqqozazm5okvjqk23sww3gccnf
+ ==> Installing curl
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> openssl is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openssl-1.0.2j-kt5xyk2dkho6tzadnqlbnbujmljprylg
+ ==> Building curl [Package]
+ ==> Successfully installed curl
+ Fetch: . Build: 0.36s. Total: 0.36s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/curl-7.50.3-oze4gqutj4x2isbkcn5ob2bhhxbskod4
+ ==> Building cmake [Package]
+ ==> Successfully installed cmake
+ Fetch: . Build: 1.64s. Total: 1.64s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> Installing metis
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> Building metis [Package]
+ ==> Successfully installed metis
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Building parmetis [Package]
+ ==> Successfully installed parmetis
+ Fetch: . Build: 0.62s. Total: 0.62s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/parmetis-4.0.3-rtg6hml5t6acdcnxomn3l5zfiful4d2t
+ ==> Installing openblas
+ ==> Building openblas [Package]
+ ==> Successfully installed openblas
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Building superlu-dist [Package]
+ ==> Successfully installed superlu-dist
+ Fetch: . Build: 0.85s. Total: 0.85s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/superlu-dist-5.1.1-25r6jlvkpjnkiuwt2rtbzhk3l3htuxs7
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> Installing glm
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> Building glm [Package]
+ ==> Successfully installed glm
+ Fetch: . Build: 0.12s. Total: 0.12s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/glm-0.9.7.1-7a6oho4aerz7vftxd5ur7lywscht2iry
+ ==> Installing hypre
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building hypre [Package]
+ ==> Successfully installed hypre
+ Fetch: . Build: 0.61s. Total: 0.61s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hypre-2.11.1-lf7hcejiiww5peesh57quda72z67veit
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Installing netlib-scalapack
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building netlib-scalapack [Package]
+ ==> Successfully installed netlib-scalapack
+ Fetch: . Build: 0.61s. Total: 0.61s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netlib-scalapack-2.0.2-dvcanz2qq4dfcexznbhbmzbxfj43uz4q
+ ==> Installing suite-sparse
+ ==> Installing tbb
+ ==> Building tbb [Package]
+ ==> Successfully installed tbb
+ Fetch: . Build: 0.12s. Total: 0.12s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/tbb-4.4.4-zawzkkhrmdonbjpj3a5bb6gkgnqlrjeu
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Building suite-sparse [Package]
+ ==> Successfully installed suite-sparse
+ Fetch: . Build: 0.49s. Total: 0.49s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/suite-sparse-4.5.3-lvur6hriy2j7xfjwh5punp3exwpynzm6
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> Installing netcdf
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> curl is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/curl-7.50.3-oze4gqutj4x2isbkcn5ob2bhhxbskod4
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> hdf5 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-ezvtnox35albuaxqryuondweyjgeo6es
+ ==> Building netcdf [Package]
+ ==> Successfully installed netcdf
+ Fetch: . Build: 0.90s. Total: 0.90s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netcdf-4.4.1-tcl4zbrmdfrit2cqlaxig6xieu5h552j
+ ==> Installing mumps
+ ==> netlib-scalapack is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netlib-scalapack-2.0.2-dvcanz2qq4dfcexznbhbmzbxfj43uz4q
+ ==> openmpi is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openmpi-2.0.1-j4cgoq4furxvr73pq72r2qgywgksw3qn
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building mumps [Package]
+ ==> Successfully installed mumps
+ Fetch: . Build: 0.74s. Total: 0.74s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mumps-5.0.2-kr5r4nnx5tfcacxnk3ii5dsxbe6pu5fy
+ ==> Installing matio
+ ==> Building matio [Package]
+ ==> Successfully installed matio
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/matio-1.5.2-4zrozucookychlvc4q53omp2zyfk2bed
+ ==> Installing boost
+ ==> bzip2 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/bzip2-1.0.6-gll2xsahysy7ji5gkmfxwkofdt3mwjhs
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> Building boost [Package]
+ ==> Successfully installed boost
+ Fetch: . Build: 0.35s. Total: 0.35s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/boost-1.62.0-je7eqvzt74kezwhh55y5lwt5dy6pnali
+ ==> parmetis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/parmetis-4.0.3-rtg6hml5t6acdcnxomn3l5zfiful4d2t
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> hdf5 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-ezvtnox35albuaxqryuondweyjgeo6es
+ ==> Building trilinos [Package]
+ ==> Successfully installed trilinos
+ Fetch: . Build: 2.63s. Total: 2.63s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/trilinos-12.8.1-uvd6dfd7x4uyvck4awo3r3frudihn4ar
+
+
+Now we're starting to see the power of Spack. Trilinos has 11 top level
+dependecies, many of which have dependencies of their own. Installing
+more complex packages can take days or weeks even for an experienced
+user. Although we've done a fake installation for the tutorial, a real
+installation of trilinos using Spack takes about 3 hours (depending on
+the system), but only 20 seconds of programmer time.
+
+Spack manages constistency of the entire DAG. Every MPI dependency will
+be satisfied by the same configuration of MPI, etc. If we install
+``trilinos`` again specifying a dependency on our previous HDF5 built
+with ``mpich``:
+
+.. code-block:: console
+
+ $ spack install --fake trilinos ^hdf5+mpi ^mpich
+ ==> Installing trilinos
+ ==> Installing superlu-dist
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Installing parmetis
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> Building parmetis [Package]
+ ==> Successfully installed parmetis
+ Fetch: . Build: 0.38s. Total: 0.38s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/parmetis-4.0.3-43kbtni6p5y446c6qdkybq4htj7ot4zn
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building superlu-dist [Package]
+ ==> Successfully installed superlu-dist
+ Fetch: . Build: 0.61s. Total: 0.61s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/superlu-dist-5.1.1-46uuupehmonx5jicc6xnegnud2n5jqyl
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> glm is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/glm-0.9.7.1-7a6oho4aerz7vftxd5ur7lywscht2iry
+ ==> Installing hypre
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building hypre [Package]
+ ==> Successfully installed hypre
+ Fetch: . Build: 0.37s. Total: 0.37s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hypre-2.11.1-6ajnyymoivs5apajd7thjisae36jv4lz
+ ==> metis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/metis-5.1.0-ithifyl4xvqbn76js23wsb4tjnztrbdv
+ ==> Installing netlib-scalapack
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> cmake is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/cmake-3.6.1-n2nkknrku6dvuneo3rjumim7axt7n36e
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building netlib-scalapack [Package]
+ ==> Successfully installed netlib-scalapack
+ Fetch: . Build: 0.37s. Total: 0.37s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netlib-scalapack-2.0.2-dayeep27omm26wksd3iqvbu3gezc2eoh
+ ==> suite-sparse is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/suite-sparse-4.5.3-lvur6hriy2j7xfjwh5punp3exwpynzm6
+ ==> Installing netcdf
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> curl is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/curl-7.50.3-oze4gqutj4x2isbkcn5ob2bhhxbskod4
+ ==> zlib is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/zlib-1.2.8-ayc4jq7vxuzge5n444gutvskeytfdruh
+ ==> hdf5 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-j36yfw25i6gdd3q4vwlupgkpwic4ua6m
+ ==> Building netcdf [Package]
+ ==> Successfully installed netcdf
+ Fetch: . Build: 0.67s. Total: 0.67s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netcdf-4.4.1-gfemi4jk4qltvp33xhtpkam7dozbqvhq
+ ==> Installing mumps
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> netlib-scalapack is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/netlib-scalapack-2.0.2-dayeep27omm26wksd3iqvbu3gezc2eoh
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> Building mumps [Package]
+ ==> Successfully installed mumps
+ Fetch: . Build: 0.49s. Total: 0.49s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mumps-5.0.2-w7t5pl3jhhwitfiyer63zj6zv7idkt3m
+ ==> mpich is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpich-3.2-5jlp2ndnsb67txggraglu47vjmayx5za
+ ==> matio is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/matio-1.5.2-4zrozucookychlvc4q53omp2zyfk2bed
+ ==> boost is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/boost-1.62.0-je7eqvzt74kezwhh55y5lwt5dy6pnali
+ ==> parmetis is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/parmetis-4.0.3-43kbtni6p5y446c6qdkybq4htj7ot4zn
+ ==> openblas is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/openblas-0.2.19-bwofa7fhff6od5zn56vy3j4eeyupsqgt
+ ==> hdf5 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-j36yfw25i6gdd3q4vwlupgkpwic4ua6m
+ ==> Building trilinos [Package]
+ ==> Successfully installed trilinos
+ Fetch: . Build: 2.42s. Total: 2.42s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/trilinos-12.8.1-ffwrpxnq7lhiw2abxn2u7ffr4jjsdwep
+
+We see that every package in the trilinos DAG that depends on MPI now
+uses ``mpich``.
+
+.. code-block:: console
+
+ $ spack find -d trilinos
+ ==> 2 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ trilinos@12.8.1
+ ^boost@1.62.0
+ ^bzip2@1.0.6
+ ^zlib@1.2.8
+ ^glm@0.9.7.1
+ ^hdf5@1.10.0-patch1
+ ^mpich@3.2
+ ^hypre@2.11.1
+ ^openblas@0.2.19
+ ^matio@1.5.2
+ ^metis@5.1.0
+ ^mumps@5.0.2
+ ^netlib-scalapack@2.0.2
+ ^netcdf@4.4.1
+ ^curl@7.50.3
+ ^openssl@1.0.2j
+ ^parmetis@4.0.3
+ ^suite-sparse@4.5.3
+ ^tbb@4.4.4
+ ^superlu-dist@5.1.1
+
+ trilinos@12.8.1
+ ^boost@1.62.0
+ ^bzip2@1.0.6
+ ^zlib@1.2.8
+ ^glm@0.9.7.1
+ ^hdf5@1.10.0-patch1
+ ^openmpi@2.0.1
+ ^hwloc@1.11.4
+ ^libpciaccess@0.13.4
+ ^hypre@2.11.1
+ ^openblas@0.2.19
+ ^matio@1.5.2
+ ^metis@5.1.0
+ ^mumps@5.0.2
+ ^netlib-scalapack@2.0.2
+ ^netcdf@4.4.1
+ ^curl@7.50.3
+ ^openssl@1.0.2j
+ ^parmetis@4.0.3
+ ^suite-sparse@4.5.3
+ ^tbb@4.4.4
+ ^superlu-dist@5.1.1
+
+
+As we discussed before, the ``spack find -d`` command shows the
+dependency information as a tree. While that is often sufficient, many
+complicated packages, including trilinos, have dependencies that
+cannot be fully represented as a tree. Again, the ``spack graph``
+command shows the full DAG of the dependency information.
+
+.. code-block:: console
+
+ $ spack graph trilinos
+ o trilinos
+ |\
+ | |\
+ | | |\
+ | | | |\
+ | | | | |\
+ | | | | | |\
+ | | | | | | |\
+ | o | | | | | | netcdf
+ | |\ \ \ \ \ \ \
+ | | |\ \ \ \ \ \ \
+ | | | o | | | | | | curl
+ | | |/| | | | | | |
+ | |/| | | | | | | |
+ | | | o | | | | | | openssl
+ | | |/ / / / / / /
+ | |/| | | | | | |
+ | | o | | | | | | hdf5
+ | |/| | | | | | |
+ | | |/ / / / / /
+ | o | | | | | | zlib
+ | / / / / / /
+ o | | | | | | swig
+ o | | | | | | pcre
+ / / / / / /
+ o | | | | | mpi
+ / / / / /
+ o | | | | matio
+ / / / /
+ o | | | lapack
+ / / /
+ o | | glm
+ / /
+ o | boost
+ /
+ o blas
+
+
+You can control how the output is displayed with a number of options.
+
+The ASCII output from ``spack graph`` can be difficult to parse for
+complicated packages. The output can be changed to the ``graphviz``
+``.dot`` format using the `--dot` flag.
+
+.. code-block:: console
+
+ $ spack graph --dot trilinos | dot -Tpdf trilinos_graph.pdf
+
+.. _basics-tutorial-uninstall:
+
+---------------------
+Uninstalling Packages
+---------------------
+
+Earlier we installed many configurations each of libelf and
+libdwarf. Now we will go through and uninstall some of those packages
+that we didn't really need.
+
+.. code-block:: console
+
+ $ spack find -d libdwarf
+ ==> 3 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ libdwarf@20160507
+ ^libelf@0.8.12
+
+ libdwarf@20160507
+ ^libelf@0.8.13
+
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ libdwarf@20160507
+ ^libelf@0.8.12
+
+ $ spack find libelf
+ ==> 6 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ libelf@0.8.12 libelf@0.8.12 libelf@0.8.13
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ libelf@0.8.13
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ libelf@0.8.12 libelf@0.8.13
+
+
+We can uninstall packages by spec using the same syntax as install.
+
+.. code-block:: console
+
+ $ spack uninstall libelf%intel@15.0.4
+ ==> The following packages will be uninstalled :
+
+ -- linux-redhat6-x86_64 / intel@15.0.4 --------------------------
+ w33hrej libelf@0.8.13%intel
+
+
+ ==> Do you want to proceed ? [y/n]
+ y
+ ==> Successfully uninstalled libelf@0.8.13%intel@15.0.4 arch=linux-redhat6-x86_64-w33hrej
+
+
+
+ $ spack find -lf libelf
+ ==> 5 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ ipggckv libelf@0.8.12%gcc
+
+ vrv2ttb libelf@0.8.12%gcc cppflags="-O3"
+
+ csrt4qx libelf@0.8.13%gcc
+
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ 4blbe3q libelf@0.8.12%intel
+
+ 7wgp32x libelf@0.8.13%intel
+
+
+We can uninstall packages by referring only to their hash.
+
+
+We can use either ``-f`` (force) or ``-d`` (remove dependents as well) to
+remove packages that are required by another installed package.
+
+
+ $ spack uninstall /4blb
+ ==> Error: Will not uninstall libelf@0.8.12%intel@16.0.3-4blbe3q
+
+ The following packages depend on it:
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ csruprg libdwarf@20160507%intel
+
+
+ ==> Error: You can use spack uninstall --dependents to uninstall these dependencies as well
+ $ spack uninstall -d /4blb
+ ==> The following packages will be uninstalled :
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ csruprg libdwarf@20160507%intel
+
+ 4blbe3q libelf@0.8.12%intel
+
+
+ ==> Do you want to proceed ? [y/n]
+ y
+ ==> Successfully uninstalled libdwarf@20160507%intel@16.0.3 arch=linux-redhat6-x86_64-csruprg
+ ==> Successfully uninstalled libelf@0.8.12%intel@16.0.3 arch=linux-redhat6-x86_64-4blbe3q
+
+
+Spack will not uninstall packages that are not sufficiently
+specified. The ``-a`` (all) flag can be used to uninstall multiple
+packages at once.
+
+
+ $ spack uninstall trilinos
+ ==> Error: trilinos matches multiple packages:
+
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ ffwrpxn trilinos@12.8.1%gcc+boost~debug+hdf5+hypre+metis+mumps~python+shared+suite-sparse+superlu-dist
+
+ uvd6dfd trilinos@12.8.1%gcc+boost~debug+hdf5+hypre+metis+mumps~python+shared+suite-sparse+superlu-dist
+
+
+ ==> Error: You can either:
+ a) Use a more specific spec, or
+ b) use spack uninstall -a to uninstall ALL matching specs.
+
+
+
+ $ spack uninstall /ffwr
+ ==> The following packages will be uninstalled :
+
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ ffwrpxn trilinos@12.8.1%gcc+boost~debug+hdf5+hypre+metis+mumps~python+shared+suite-sparse+superlu-dist
+
+
+ ==> Do you want to proceed ? [y/n]
+ y
+ ==> Successfully uninstalled trilinos@12.8.1%gcc@4.4.7+boost~debug+hdf5+hypre+metis+mumps~python+shared+suite-sparse+superlu-dist arch=linux-redhat6-x86_64-ffwrpxn
+
+-----------------------------
+Advanced ``spack find`` Usage
+-----------------------------
+
+We will go over some additional uses for the `spack find` command not
+already covered in the :ref:`basics-tutorial-install` and
+:ref:`basics-tutorial-uninstall` sections.
+
+The ``spack find`` command can accept what we call "anonymous specs."
+These are expressions in spec syntax that do not contain a package
+name. For example, `spack find %intel` will return every package built
+with the intel compiler, and ``spack find cppflags=\\"-O3\\"`` will
+return every package which was built with ``cppflags=\\"-O3\\"``.
+
+.. code-block:: console
+
+ $ spack find %intel
+ ==> 1 installed packages.
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ libelf@0.8.13
+
+
+
+ $ spack find cppflags=\"-O3\"
+ ==> 1 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ libelf@0.8.12
+
+
+The ``find`` command can also show which packages were installed
+explicitly (rather than pulled in as a dependency) using the ``-e``
+flag. The ``-E`` flag shows implicit installs only. The ``find`` command can
+also show the path to which a spack package was installed using the ``-p``
+command.
+
+.. code-block:: console
+
+ $ spack find -pe
+ ==> 10 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ hdf5@1.10.0-patch1 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-twppaioxqn6lti4grgopnmhwcq3h2rpw
+ hdf5@1.10.0-patch1 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-j36yfw25i6gdd3q4vwlupgkpwic4ua6m
+ hdf5@1.10.0-patch1 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/hdf5-1.10.0-patch1-ezvtnox35albuaxqryuondweyjgeo6es
+ libdwarf@20160507 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libdwarf-20160507-dtg3tgnp7htccoly26gduqlrgvnwcp5t
+ libdwarf@20160507 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libdwarf-20160507-yfx6p3g3rkmqvcqbmtb34o6pln7pqvcz
+ libelf@0.8.12 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.12-ipggckv6i7h44iryzfa4dwdela32a7fy
+ libelf@0.8.12 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.12-vrv2ttbd34xlfoxy4jwt6qsjrcbalmmw
+ libelf@0.8.13 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/libelf-0.8.13-csrt4qxfkhjgn5xg3zjpkir7xdnszl2a
+ trilinos@12.8.1 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/trilinos-12.8.1-uvd6dfd7x4uyvck4awo3r3frudihn4ar
+
+ -- linux-redhat6-x86_64 / intel@16.0.3 --------------------------
+ libelf@0.8.13 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/intel-16.0.3/libelf-0.8.13-7wgp32xksatkvw2tbssmehw2t5tnxndj
+
+
+---------------------
+Customizing Compilers
+---------------------
+
+
+Spack manages a list of available compilers on the system, detected
+automatically from from the user's ``PATH`` variable. The ``spack
+compilers`` command is an alias for the command ``spack compiler list``.
+
+.. code-block:: console
+
+ $ spack compilers
+ ==> Available compilers
+ -- gcc ----------------------------------------------------------
+ gcc@4.4.7
+
+ -- intel --------------------------------------------------------
+ intel@16.0.3 intel@15.0.1 intel@14.0.0 intel@12.1.3 intel@10.0
+ intel@16.0.2 intel@15.0.0 intel@13.1.1 intel@12.1.2 intel@9.1
+ intel@16.0.1 intel@14.0.4 intel@13.1.0 intel@12.1.0
+ intel@16.0.0 intel@14.0.3 intel@13.0.1 intel@12.0.4
+ intel@15.0.4 intel@14.0.2 intel@13.0.0 intel@11.1
+ intel@15.0.3 intel@14.0.1 intel@12.1.5 intel@10.1
+
+ -- pgi ----------------------------------------------------------
+ pgi@16.5-0 pgi@15.7-0 pgi@14.7-0 pgi@13.2-0 pgi@11.10-0 pgi@9.0-4
+ pgi@16.3-0 pgi@15.5-0 pgi@14.3-0 pgi@13.1-1 pgi@11.1-0 pgi@8.0-1
+ pgi@16.1-0 pgi@15.1-0 pgi@13.10-0 pgi@12.8-0 pgi@10.9-0 pgi@7.1-3
+ pgi@15.10-0 pgi@14.10-0 pgi@13.6-0 pgi@12.1-0 pgi@10.2-0 pgi@7.0-6
+
+The compilers are maintained in a YAML file that can be hand-edited
+for special cases. Spack also has tools to add compilers, and
+compilers built with Spack can be added to the configuration.
+
+.. code-block:: console
+
+ $ spack install --fake gcc@6.1.0
+ ==> Installing gcc
+ ==> gmp is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gmp-6.1.1-uwn4gfdtq3sywy5uf4f7znrh66oybikf
+ ==> Installing isl
+ ==> gmp is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gmp-6.1.1-uwn4gfdtq3sywy5uf4f7znrh66oybikf
+ ==> Building isl [Package]
+ ==> Successfully installed isl
+ Fetch: . Build: 0.19s. Total: 0.19s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/isl-0.14-hs2w7mjjjaakkmbbv5yvfqf7yyzhorl6
+ ==> Installing mpc
+ ==> gmp is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gmp-6.1.1-uwn4gfdtq3sywy5uf4f7znrh66oybikf
+ ==> Installing mpfr
+ ==> gmp is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gmp-6.1.1-uwn4gfdtq3sywy5uf4f7znrh66oybikf
+ ==> Building mpfr [Package]
+ ==> Successfully installed mpfr
+ Fetch: . Build: 0.17s. Total: 0.17s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpfr-3.1.4-7kt5ij437khredfq4bvnyu22t3fmtfvt
+ ==> Building mpc [Package]
+ ==> Successfully installed mpc
+ Fetch: . Build: 0.28s. Total: 0.28s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpc-1.0.3-g5taq6lt3zuy5l2jtggi5lctxnl4la5u
+ ==> Installing binutils
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> Installing bison
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> Building bison [Package]
+ ==> Successfully installed bison
+ Fetch: . Build: 0.12s. Total: 0.12s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/bison-3.0.4-hkhfysfvq5l6rsns67g2htmkpxauvnwa
+ ==> Installing flex
+ ==> m4 is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/m4-1.4.17-qijdzvhjyybrtwbqm73vykhmkaqro3je
+ ==> bison is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/bison-3.0.4-hkhfysfvq5l6rsns67g2htmkpxauvnwa
+ ==> Building flex [Package]
+ ==> Successfully installed flex
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/flex-2.6.0-qd6d73rdfrozdrsdpimvl4tj7d5ps7qg
+ ==> Building binutils [Package]
+ ==> Successfully installed binutils
+ Fetch: . Build: 0.11s. Total: 0.11s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/binutils-2.27-iq2hry3gvaxszmwwbnll7njgdgaek56o
+ ==> mpfr is already installed in /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/mpfr-3.1.4-7kt5ij437khredfq4bvnyu22t3fmtfvt
+ ==> Building gcc [Package]
+ ==> Successfully installed gcc
+ Fetch: . Build: 0.66s. Total: 0.66s.
+ [+] /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gcc-6.1.0-j5576zbsot2ydljlthjzhsirsesnogvh
+
+
+
+ $ spack find -p gcc
+ ==> 1 installed packages.
+ -- linux-redhat6-x86_64 / gcc@4.4.7 -----------------------------
+ gcc@6.1.0 /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gcc-6.1.0-j5576zbsot2ydljlthjzhsirsesnogvh
+
+
+If we had done a "real" install of gcc, we could add it to our
+configuration now using the `spack compiler add` command, but we would
+also be waiting for it to install. If we run the command now, it will
+return no new compilers.
+
+.. code-block:: console
+
+ $ spack compiler add /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gcc-6.1.0-j5576zbsot2ydljlthjzhsirsesnogvh/bin
+ ==> Found no new compilers
+
+If we had done a real install, the output would have been as follows:
+
+.. code-block:: console
+
+ $ spack compiler add /g/g0/becker33/SC16-spack/spack/opt/spack/linux-redhat6-x86_64/gcc-4.4.7/gcc-6.1.0-j5576zbsot2ydljlthjzhsirsesnogvh/bin
+ ==> Added 1 new compiler to /g/g0/becker33/.spack/linux/compilers.yaml
+ gcc@6.1.0
diff --git a/lib/spack/docs/build_settings.rst b/lib/spack/docs/build_settings.rst
new file mode 100644
index 0000000000..60ff26f26f
--- /dev/null
+++ b/lib/spack/docs/build_settings.rst
@@ -0,0 +1,168 @@
+.. _build-settings:
+
+======================================
+Build customization
+======================================
+
+Spack allows you to customize how your software is built through the
+``packages.yaml`` file. Using it, you can make Spack prefer particular
+implementations of virtual dependencies (e.g., compilers, MPI, or BLAS),
+or you can make it prefer to build with particular compilers. You can
+also tell Spack to use *external* installations of certain software.
+
+At a high level, the ``packages.yaml`` file is structured like this:
+
+.. code-block:: yaml
+
+ packages:
+ package1:
+ # settings for package1
+ package2:
+ # settings for package2
+ # ...
+ all:
+ # settings that apply to all packages.
+
+So you can either set build preferences *specifically* for one package,
+or you can specify that certain settings should apply to all packages.
+The types of settings you can customize are described in detail below.
+
+Spack's build defaults are in the default
+``etc/spack/defaults/packages.yaml`` file. You can override them in
+``~/.spack/packages.yaml`` or ``etc/spack/packages.yaml``. For more
+details on how this works, see :ref:`configuration-scopes`
+
+.. _sec-external-packages:
+
+-----------------
+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 arch=linux-x86_64-debian7: /opt/openmpi-1.4.3
+ openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7+debug: /opt/openmpi-1.4.3-debug
+ openmpi@1.6.5%intel@10.1 arch=linux-x86_64-debian7: /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. Packages.yaml can also be used to specify modules
+
+Each ``packages.yaml`` begins with a ``packages:`` token, followed
+by a list of package names. To specify externals, add a ``paths`` or ``modules``
+token under the package name, which lists externals in a
+``spec: /path`` or ``spec: module-name`` 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 arch=linux-x86_64-debian7: /opt/openmpi-1.4.3
+ openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7+debug: /opt/openmpi-1.4.3-debug
+ openmpi@1.6.5%intel@10.1 arch=linux-x86_64-debian7: /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.
+
+If an external module is specified as not buildable, then Spack will load the
+external module into the build environment which can be used for linking.
+
+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.
+
+
+.. _concretization-preferences:
+
+--------------------------
+Concretization Preferences
+--------------------------
+
+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:: yaml
+
+ packages:
+ opencv:
+ 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 opencv 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 opencv, 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.
diff --git a/lib/spack/docs/command_index.in b/lib/spack/docs/command_index.in
index 94cdf38109..6520352b42 100644
--- a/lib/spack/docs/command_index.in
+++ b/lib/spack/docs/command_index.in
@@ -1,7 +1,6 @@
-.. _command_index:
-
-Command index
-=================
+=============
+Command Index
+=============
This is an alphabetical list of commands with links to the places they
appear in the documentation.
diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py
index f3cb268177..237a062c14 100644
--- a/lib/spack/docs/conf.py
+++ b/lib/spack/docs/conf.py
@@ -1,9 +1,7 @@
+# flake8: noqa
##############################################################################
-# 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.
+# Created by Todd Gamblin, tgamblin@llnl.gov.
# LLNL-CODE-647188
#
# For details, see https://github.com/llnl/spack
@@ -37,26 +35,85 @@
import sys
import os
+import re
+import shutil
import subprocess
+from glob import glob
+from sphinx.apidoc import main as sphinx_apidoc
+
+# -- Spack customizations -----------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('exts'))
sys.path.insert(0, os.path.abspath('../external'))
+sys.path.append(os.path.abspath('..'))
# Add the Spack bin directory to the path so that we can use its output in docs.
spack_root = '../../..'
os.environ['SPACK_ROOT'] = spack_root
-os.environ['PATH'] += os.pathsep + '$SPACK_ROOT/bin'
+os.environ['PATH'] += '%s%s/bin' % (os.pathsep, spack_root)
+# Get the spack version for use in the docs
spack_version = subprocess.Popen(
[spack_root + '/bin/spack', '-V'],
stderr=subprocess.PIPE).communicate()[1].strip().split('.')
# Set an environment variable so that colify will print output like it would to
# a terminal.
-os.environ['COLIFY_SIZE'] = '25x80'
+os.environ['COLIFY_SIZE'] = '25x120'
+
+#
+# Generate package list using spack command
+#
+with open('package_list.rst', 'w') as plist_file:
+ subprocess.Popen(
+ [spack_root + '/bin/spack', 'list', '--format=rst'], stdout=plist_file)
+
+#
+# Find all the `cmd-spack-*` references and add them to a command index
+#
+command_names = []
+for filename in glob('*rst'):
+ with open(filename) as f:
+ for line in f:
+ match = re.match('.. _(cmd-spack-.*):', line)
+ if match:
+ command_names.append(match.group(1).strip())
+
+shutil.copy('command_index.in', 'command_index.rst')
+with open('command_index.rst', 'a') as index:
+ index.write('\n')
+ for cmd in sorted(command_names):
+ index.write(' * :ref:`%s`\n' % cmd)
+
+
+# Run sphinx-apidoc
+sphinx_apidoc(['-T', '-o', '.', '../spack'])
+os.remove('modules.rst')
+
+#
+# Exclude everything in spack.__all__ from indexing. All of these
+# symbols are imported from elsewhere in spack; their inclusion in
+# __all__ simply allows package authors to use `from spack import *`.
+# Excluding them ensures they're only documented in their "real" module.
+#
+# This also avoids issues where some of these symbols shadow core spack
+# modules. Sphinx will complain about duplicate docs when this happens.
+#
+import fileinput, spack
+handling_spack = False
+for line in fileinput.input('spack.rst', inplace=1):
+ if handling_spack:
+ if not line.startswith(' :noindex:'):
+ print ' :noindex: %s' % ' '.join(spack.__all__)
+ handling_spack = False
+
+ if line.startswith('.. automodule::'):
+ handling_spack = (line == '.. automodule:: spack\n')
+
+ print line,
# Enable todo items
todo_include_todos = True
diff --git a/lib/spack/docs/config_yaml.rst b/lib/spack/docs/config_yaml.rst
new file mode 100644
index 0000000000..7b1a89bc8a
--- /dev/null
+++ b/lib/spack/docs/config_yaml.rst
@@ -0,0 +1,149 @@
+.. _config-yaml:
+
+====================================
+Basics settings
+====================================
+
+Spack's basic configuration options are set in ``config.yaml``. You can
+see the default settings by looking at
+``etc/spack/defaults/config.yaml``:
+
+.. literalinclude:: ../../../etc/spack/defaults/config.yaml
+ :language: yaml
+
+These settings can be overridden in ``etc/spack/config.yaml`` or
+``~/.spack/config.yaml``. See :ref:`configuration-scopes` for details.
+
+.. _config-file-variables:
+
+------------------------------
+Config file variables
+------------------------------
+
+You may notice some variables prefixed with ``$`` in the settings above.
+Spack understands several variables that can be used in values of
+configuration parameters. They are:
+
+ * ``$spack``: path to the prefix of this spack installation
+ * ``$tempdir``: default system temporary directory (as specified in
+ Python's `tempfile.tempdir
+ <https://docs.python.org/2/library/tempfile.html#tempfile.tempdir>`_
+ variable.
+ * ``$user``: name of the current user
+
+Note that, as with shell variables, you can write these as ``$varname``
+or with braces to distinguish the variable from surrounding characters:
+``${varname}``.
+
+--------------------
+``install_tree``
+--------------------
+
+The location where Spack will install packages and their dependencies.
+Default is ``$spack/opt/spack``.
+
+--------------------
+``module_roots``
+--------------------
+
+Controls where Spack installs generated module files. You can customize
+the location for each type of module. e.g.:
+
+.. code-block:: yaml
+
+ module_roots:
+ tcl: $spack/share/spack/modules
+ lmod: $spack/share/spack/lmod
+ dotkit: $spack/share/spack/dotkit
+
+See :ref:`modules` for details.
+
+--------------------
+``build_stage``
+--------------------
+
+Spack is designed to run out of a user home directories, and on many
+systems the home directory a (slow) network filesystem. On most systems,
+building in a temporary filesystem results in faster builds than building
+in the home directory. Usually, there is also more space available in
+the temporary location than in the home directory. So, Spack tries to
+create build stages in temporary space.
+
+By default, Spack's ``build_stage`` is configured like this:
+
+.. code-block:: yaml
+
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+ - $spack/var/spack/stage
+
+This is an ordered list of paths that Spack should search when trying to
+find a temporary directory for the build stage. The list is searched in
+order, and Spack will use the first directory to which it has write access.
+See :ref:`config-file-variables` for more on ``$tempdir`` and ``$spack``.
+
+When Spack builds a package, it creates a temporary directory within the
+``build_stage``, and it creates a symbolic link to that directory in
+``$spack/var/spack/stage``. This is used totrack the stage.
+
+After a package is successfully installed, Spack deletes the temporary
+directory it used to build. Unsuccessful builds are not deleted, but you
+can manually purge them with :ref:`spack purge --stage
+<cmd-spack-purge>`.
+
+.. note::
+
+ The last item in the list is ``$spack/var/spack/stage``. If this is the
+ only writable directory in the ``build_stage`` list, Spack will build
+ *directly* in ``$spack/var/spack/stage`` and will not link to temporary
+ space.
+
+--------------------
+``source_cache``
+--------------------
+
+Location to cache downloaded tarballs and repositories. By default these
+are stored in ``$spack/var/spack/cache``. These are stored indefinitely
+by default. Can be purged with :ref:`spack purge --downloads
+<cmd-spack-purge>`.
+
+--------------------
+``misc_cache``
+--------------------
+
+Temporary directory to store long-lived cache files, such as indices of
+packages available in repositories. Defaults to ``~/.spack/cache``. Can
+be purged with :ref:`spack purge --misc-cache <cmd-spack-purge>`.
+
+--------------------
+``verify_ssl``
+--------------------
+
+When set to ``true`` (default) Spack will verify certificates of remote
+hosts when making ``ssl`` connections. Set to ``false`` to disable, and
+tools like ``curl`` will use their ``--insecure`` options. Disabling
+this can expose you to attacks. Use at your own risk.
+
+--------------------
+``checksum``
+--------------------
+
+When set to ``true``, Spack verifies downloaded source code using a
+checksum, and will refuse to build packages that it cannot verify. Set
+to ``false`` to disable these checks. Disabling this can expose you to
+attacks. Use at your own risk.
+
+--------------------
+``dirty``
+--------------------
+
+By default, Spack unsets variables in your environment that can change
+the way packages build. This includes ``LD_LIBRARY_PATH``, ``CPATH``,
+``LIBRARY_PATH``, ``DYLD_LIBRARY_PATH``, and others.
+
+By default, builds are ``clean``, but on some machines, compilers and
+other tools may need custom ``LD_LIBRARY_PATH`` setings to run. You can
+set ``dirty`` to ``true`` to skip the cleaning step and make all builds
+"dirty" by default. Be aware that this will reduce the reproducibility
+of builds.
diff --git a/lib/spack/docs/configuration.rst b/lib/spack/docs/configuration.rst
index d39c932021..48a4686b84 100644
--- a/lib/spack/docs/configuration.rst
+++ b/lib/spack/docs/configuration.rst
@@ -1,236 +1,252 @@
.. _configuration:
-Configuration
-===================================
-
-.. _temp-space:
-
-Temporary space
-----------------------------
-
-.. warning:: Temporary space configuration will eventually be moved to
- configuration files, but currently these settings are in
- ``lib/spack/spack/__init__.py``
-
-By default, Spack will try to do all of its building in temporary
-space. There are two main reasons for this. First, Spack is designed
-to run out of a user's home directory, and on may systems the home
-directory is network mounted and potentially not a very fast
-filesystem. We create build stages in a temporary directory to avoid
-this. Second, many systems impose quotas on home directories, and
-``/tmp`` or similar directories often have more available space. This
-helps conserve space for installations in users' home directories.
-
-You can customize temporary directories by editing
-``lib/spack/spack/__init__.py``. Specifically, find this part of the file:
-
-.. code-block:: python
-
- # Whether to build in tmp space or directly in the stage_path.
- # If this is true, then spack will make stage directories in
- # a tmp filesystem, and it will symlink them into stage_path.
- use_tmp_stage = True
-
- # Locations to use for staging and building, in order of preference
- # Use a %u to add a username to the stage paths here, in case this
- # is a shared filesystem. Spack will use the first of these paths
- # that it can create.
- tmp_dirs = ['/nfs/tmp2/%u/spack-stage',
- '/var/tmp/%u/spack-stage',
- '/tmp/%u/spack-stage']
-
-The ``use_tmp_stage`` variable controls whether Spack builds
-**directly** inside the ``var/spack/`` directory. Normally, Spack
-will try to find a temporary directory for a build, then it *symlinks*
-that temporary directory into ``var/spack/`` so that you can keep
-track of what temporary directories Spack is using.
-
-The ``tmp_dirs`` variable is a list of paths Spack should search when
-trying to find a temporary directory. They can optionally contain a
-``%u``, which will substitute the current user's name into the path.
-The list is searched in order, and Spack will create a temporary stage
-in the first directory it finds to which it has write access. Add
-more elements to the list to indicate where your own site's temporary
-directory is.
-
-.. _sec-external_packages:
-
-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:
+==============================
+Configuration Files in Spack
+==============================
+
+Spack has many configuration files. Here is a quick list of them, in
+case you want to skip directly to specific docs:
+
+* :ref:`compilers.yaml <compiler-config>`
+* :ref:`config.yaml <config-yaml>`
+* :ref:`mirrors.yaml <mirrors>`
+* :ref:`modules.yaml <modules>`
+* :ref:`packages.yaml <build-settings>`
+
+-------------------------
+YAML Format
+-------------------------
+
+Spack configuration files are written in YAML. We chose YAML because
+it's human readable, but also versatile in that it supports dictionaries,
+lists, and nested sections. For more details on the format, see `yaml.org
+<http://yaml.org>`_ and `libyaml <http://pyyaml.org/wiki/LibYAML>`_.
+Here is an example ``config.yaml`` file:
+
+.. code-block:: yaml
+
+ config:
+ install_tree: $spack/opt/spack
+ module_roots:
+ lmod: $spack/share/spack/lmod
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+
+Each spack configuration files is nested under a top-level section
+corresponding to its name. So, ``config.yaml`` starts with ``config:``,
+and ``mirrors.yaml`` starts with ``mirrors:``, etc.
+
+.. _configuration-scopes:
+
+-------------------------
+Configuration Scopes
+-------------------------
+
+Spack pulls configuration data from files in several directories. There
+are three configuration scopes. From lowest to highest:
+
+1. **defaults**: Stored in ``$(prefix)/etc/spack/defaults/``. These are
+ the "factory" settings. Users should generally not modify the settings
+ here, but should override them in other configuration scopes. The
+ defaults here will change from version to version of Spack.
+
+2. **site**: Stored in ``$(prefix)/etc/spack/``. Settings here affect
+ only *this instance* of Spack, and they override defaults. The site
+ scope can can be used for per-project settings (one spack instance per
+ project) or for site-wide settings on a multi-user machine (e.g., for
+ a common spack instance).
+
+3. **user**: Stored in the home directory: ``~/.spack/``. These settings
+ affect all instances of Spack and take the highest precedence.
+
+Each configuration directory may contain several configuration files,
+such as ``config.yaml``, ``compilers.yaml``, or ``mirrors.yaml``. When
+configurations conflict, settings from higher-precedence scopes override
+lower-precedence settings.
+
+Commands that modify scopes (e.g., ``spack compilers``, ``spack repo``,
+etc.) take a ``--scope=<name>`` parameter that you can use to control
+which scope is modified. By default they modify the highest-precedence
+scope.
+
+.. _platform-scopes:
+
+-------------------------
+Platform-specific scopes
+-------------------------
+
+For each scope above, there can *also* be platform-specific settings.
+For example, on Blue Gene/Q machines, Spack needs to know the location of
+cross-compilers for the compute nodes. This configuration is in
+``etc/spack/defaults/bgq/compilers.yaml``. It will take precedence over
+settings in the ``defaults`` scope, but can still be overridden by
+settings in ``site``, ``site/bgq``, ``user``, or ``user/bgq``. So, the
+full scope precedence is:
+
+1. ``defaults``
+2. ``defaults/<platform>``
+3. ``site``
+4. ``site/<platform>``
+5. ``user``
+6. ``user/<platform>``
+
+You can get the name to use for ``<platform>`` by running ``spack arch
+--platform``.
+
+-------------------------
+Scope precedence
+-------------------------
+
+When spack queries for configuration parameters, it searches in
+higher-precedence scopes first. So, settings in a higher-precedence file
+can override those with the same key in a lower-precedence one. For
+list-valued settings, Spack *prepends* higher-precedence settings to
+lower-precedence settings. Completely ignoring higher-level configuration
+options is supported with the ``::`` notation for keys (see
+:ref:`config-overrides` below).
+
+^^^^^^^^^^^^^^^^^^^^^^^^
+Simple keys
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Let's look at an example of overriding a single key in a Spack file. If
+your configurations look like this:
+
+**defaults** scope:
+
+.. code-block:: yaml
+
+ config:
+ install_tree: $spack/opt/spack
+ module_roots:
+ lmod: $spack/share/spack/lmod
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+
+**site** scope:
+
+.. code-block:: yaml
+
+ config:
+ install_tree: /some/other/directory
+
+Spack will only override ``install_tree`` in the ``config`` section, and
+will take the site preferences for other settings. You can see the
+final, combined configuration with the ``spack config get <configtype>``
+command:
+
+.. code-block:: console
+ :emphasize-lines: 3
+
+ $ spack config get config
+ config:
+ install_tree: /some/other/directory
+ module_roots:
+ lmod: $spack/share/spack/lmod
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+ $ _
+
+.. _config-overrides:
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+Overriding entire sections
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Above, the site ``config.yaml`` only overrides specific settings in the
+default ``config.yaml``. Sometimes, it is useful to *completely*
+override lower-precedence settings. To do this, you can use *two* colons
+at the end of a key in a configuration file. For example, if the
+**site** ``config.yaml`` above looks like this:
+
+.. code-block:: yaml
+ :emphasize-lines: 1
+
+ config::
+ install_tree: /some/other/directory
+
+Spack will ignore all lower-precedence configuration under the
+``config::`` section:
+
+.. code-block:: console
+
+ $ spack config get config
+ config:
+ install_tree: /some/other/directory
+
+^^^^^^^^^^^^^^^^^^^^^^
+List-valued settings
+^^^^^^^^^^^^^^^^^^^^^^
+
+Let's revisit the ``config.yaml`` example one more time. The
+``build_stage`` setting's value is an ordered list of directories:
+
+**defaults**
.. code-block:: yaml
- packages:
- openmpi:
- paths:
- openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7: /opt/openmpi-1.4.3
- openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7+debug: /opt/openmpi-1.4.3-debug
- openmpi@1.6.5%intel@10.1 arch=linux-x86_64-debian7: /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. Packages.yaml can also be used to specify modules
-
-Each ``packages.yaml`` begins with a ``packages:`` token, followed
-by a list of package names. To specify externals, add a ``paths`` or ``modules``
-token under the package name, which lists externals in a
-``spec: /path`` or ``spec: module-name`` 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:
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+
+Suppose the user configuration adds its *own* list of ``build_stage``
+paths:
+
+**user**
.. code-block:: yaml
- packages:
- openmpi:
- paths:
- openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7: /opt/openmpi-1.4.3
- openmpi@1.4.3%gcc@4.4.7 arch=linux-x86_64-debian7+debug: /opt/openmpi-1.4.3-debug
- openmpi@1.6.5%intel@10.1 arch=linux-x86_64-debian7: /opt/openmpi-1.6.5-intel
- buildable: False
+ build_stage:
+ - /lustre-scratch/$user
+ - ~/mystage
+
+Spack will first look at the paths in the site ``config.yaml``, then the
+paths in the user's ``~/.spack/config.yaml``. The list in the
+higher-precedence scope is *prepended* to the defaults. ``spack config
+get config`` shows the result:
-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.
+.. code-block:: console
+ :emphasize-lines: 7-10
-If an external module is specified as not buildable, then Spack will load the
-external module into the build environment which can be used for linking.
-
-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.
-
-
-Concretization Preferences
---------------------------------
-
-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:
- opencv:
- 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 opencv 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 opencv, 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.
-
-
-Profiling
-------------------
-
-Spack has some limited built-in support for profiling, and can report
-statistics using standard Python timing tools. To use this feature,
-supply ``-p`` to Spack on the command line, before any subcommands.
-
-.. _spack-p:
-
-``spack -p``
-~~~~~~~~~~~~~~~~~
-
-``spack -p`` output looks like this:
-
-.. code-block:: sh
-
- $ spack -p graph dyninst
- o dyninst
- |\
- | |\
- | o | libdwarf
- |/ /
- o | libelf
- /
- o boost
-
- 307670 function calls (305943 primitive calls) in 0.127 seconds
-
- Ordered by: internal time
-
- ncalls tottime percall cumtime percall filename:lineno(function)
- 853 0.021 0.000 0.066 0.000 inspect.py:472(getmodule)
- 51197 0.011 0.000 0.018 0.000 inspect.py:51(ismodule)
- 73961 0.010 0.000 0.010 0.000 {isinstance}
- 1762 0.006 0.000 0.053 0.000 inspect.py:440(getsourcefile)
- 32075 0.006 0.000 0.006 0.000 {hasattr}
- 1760 0.004 0.000 0.004 0.000 {posix.stat}
- 2240 0.004 0.000 0.004 0.000 {posix.lstat}
- 2602 0.004 0.000 0.011 0.000 inspect.py:398(getfile)
- 771 0.004 0.000 0.077 0.000 inspect.py:518(findsource)
- 2656 0.004 0.000 0.004 0.000 {method 'match' of '_sre.SRE_Pattern' objects}
- 30772 0.003 0.000 0.003 0.000 {method 'get' of 'dict' objects}
- ...
-
-The bottom of the output shows the top most time consuming functions,
-slowest on top. The profiling support is from Python's built-in tool,
-`cProfile
-<https://docs.python.org/2/library/profile.html#module-cProfile>`_.
+ $ spack config get config
+ config:
+ install_tree: /some/other/directory
+ module_roots:
+ lmod: $spack/share/spack/lmod
+ build_stage:
+ - /lustre-scratch/$user
+ - ~/mystage
+ - $tempdir
+ - /nfs/tmp2/$user
+ $ _
+
+As in :ref:`config-overrides`, the higher-precedence scope can
+*completely* override the lower-precedence scope using `::`. So if the
+user config looked like this:
+
+**user**
+
+.. code-block:: yaml
+ :emphasize-lines: 1
+
+ build_stage::
+ - /lustre-scratch/$user
+ - ~/mystage
+
+The merged configuration would look like this:
+
+.. code-block:: console
+ :emphasize-lines: 7-8
+
+ $ spack config get config
+ config:
+ install_tree: /some/other/directory
+ module_roots:
+ lmod: $spack/share/spack/lmod
+ build_stage:
+ - /lustre-scratch/$user
+ - ~/mystage
+ $ _
diff --git a/lib/spack/docs/contribution_guide.rst b/lib/spack/docs/contribution_guide.rst
new file mode 100644
index 0000000000..49595fecf8
--- /dev/null
+++ b/lib/spack/docs/contribution_guide.rst
@@ -0,0 +1,522 @@
+.. _contribution-guide:
+
+==================
+Contribution Guide
+==================
+
+This guide is intended for developers or administrators who want to
+contribute a new package, feature, or bugfix to Spack.
+It assumes that you have at least some familiarity with Git VCS and Github.
+The guide will show a few examples of contributing workflows and discuss
+the granularity of pull-requests (PRs). It will also discuss the tests your
+PR must pass in order to be accepted into Spack.
+
+First, what is a PR? Quoting `Bitbucket's tutorials <https://www.atlassian.com/git/tutorials/making-a-pull-request/>`_:
+
+ Pull requests are a mechanism for a developer to notify team members that
+ they have **completed a feature**. The pull request is more than just a
+ notification—it’s a dedicated forum for discussing the proposed feature.
+
+Important is **completed feature**. The changes one proposes in a PR should
+correspond to one feature/bugfix/extension/etc. One can create PRs with
+changes relevant to different ideas, however reviewing such PRs becomes tedious
+and error prone. If possible, try to follow the **one-PR-one-package/feature** rule.
+
+Spack uses a rough approximation of the `Git Flow <http://nvie.com/posts/a-successful-git-branching-model/>`_
+branching model. The develop branch contains the latest contributions, and
+master is always tagged and points to the latest stable release. Therefore, when
+you send your request, make ``develop`` the destination branch on the
+`Spack repository <https://github.com/LLNL/spack>`_.
+
+----------------------
+Continuous Integration
+----------------------
+
+Spack uses `Travis CI <https://travis-ci.org/LLNL/spack>`_ for Continuous Integration
+testing. This means that every time you submit a pull request, a series of tests will
+be run to make sure you didn't accidentally introduce any bugs into Spack. Your PR
+will not be accepted until it passes all of these tests. While you can certainly wait
+for the results of these tests after submitting a PR, we recommend that you run them
+locally to speed up the review process.
+
+If you take a look in ``$SPACK_ROOT/.travis.yml``, you'll notice that we test
+against Python 2.6 and 2.7. We currently perform 3 types of tests:
+
+^^^^^^^^^^
+Unit Tests
+^^^^^^^^^^
+
+Unit tests ensure that core Spack features like fetching or spec resolution are
+working as expected. If your PR only adds new packages or modifies existing ones,
+there's very little chance that your changes could cause the unit tests to fail.
+However, if you make changes to Spack's core libraries, you should run the unit
+tests to make sure you didn't break anything.
+
+Since they test things like fetching from VCS repos, the unit tests require
+`git <https://git-scm.com/>`_, `mercurial <https://www.mercurial-scm.org/>`_,
+and `subversion <https://subversion.apache.org/>`_ to run. Make sure these are
+installed on your system and can be found in your ``PATH``. All of these can be
+installed with Spack or with your system package manager.
+
+To run *all* of the unit tests, use:
+
+.. code-block:: console
+
+ $ spack test
+
+These tests may take several minutes to complete. If you know you are only
+modifying a single Spack feature, you can run a single unit test at a time:
+
+.. code-block:: console
+
+ $ spack test architecture
+
+This allows you to develop iteratively: make a change, test that change, make
+another change, test that change, etc. To get a list of all available unit
+tests, run:
+
+.. command-output:: spack test --list
+
+Unit tests are crucial to making sure bugs aren't introduced into Spack. If you
+are modifying core Spack libraries or adding new functionality, please consider
+adding new unit tests or strengthening existing tests.
+
+.. note::
+
+ There is also a ``run-unit-tests`` script in ``share/spack/qa`` that
+ runs the unit tests. Afterwards, it reports back to Coverage with the
+ percentage of Spack that is covered by unit tests. This script is
+ designed for Travis CI. If you want to run the unit tests yourself, we
+ suggest you use ``spack test``.
+
+^^^^^^^^^^^^
+Flake8 Tests
+^^^^^^^^^^^^
+
+Spack uses `Flake8 <http://flake8.pycqa.org/en/latest/>`_ to test for
+`PEP 8 <https://www.python.org/dev/peps/pep-0008/>`_ conformance. PEP 8 is
+a series of style guides for Python that provide suggestions for everything
+from variable naming to indentation. In order to limit the number of PRs that
+were mostly style changes, we decided to enforce PEP 8 conformance. Your PR
+needs to comply with PEP 8 in order to be accepted.
+
+Testing for PEP 8 compliance is easy. Simply run the ``spack flake8``
+command:
+
+.. code-block:: console
+
+ $ spack flake8
+
+``spack flake8`` has a couple advantages over running ``flake8`` by hand:
+
+#. It only tests files that you have modified since branching off of
+ ``develop``.
+
+#. It works regardless of what directory you are in.
+
+#. It automatically adds approved exemptions from the ``flake8``
+ checks. For example, URLs are often longer than 80 characters, so we
+ exempt them from line length checks. We also exempt lines that start
+ with "homepage", "url", "version", "variant", "depends_on", and
+ "extends" in ``package.py`` files.
+
+More approved flake8 exemptions can be found
+`here <https://github.com/LLNL/spack/blob/develop/.flake8>`_.
+
+If all is well, you'll see something like this:
+
+.. code-block:: console
+
+ $ run-flake8-tests
+ Dependencies found.
+ =======================================================
+ flake8: running flake8 code checks on spack.
+
+ Modified files:
+
+ var/spack/repos/builtin/packages/hdf5/package.py
+ var/spack/repos/builtin/packages/hdf/package.py
+ var/spack/repos/builtin/packages/netcdf/package.py
+ =======================================================
+ Flake8 checks were clean.
+
+However, if you aren't compliant with PEP 8, flake8 will complain:
+
+.. code-block:: console
+
+ var/spack/repos/builtin/packages/netcdf/package.py:26: [F401] 'os' imported but unused
+ var/spack/repos/builtin/packages/netcdf/package.py:61: [E303] too many blank lines (2)
+ var/spack/repos/builtin/packages/netcdf/package.py:106: [E501] line too long (92 > 79 characters)
+ Flake8 found errors.
+
+Most of the error messages are straightforward, but if you don't understand what
+they mean, just ask questions about them when you submit your PR. The line numbers
+will change if you add or delete lines, so simply run ``run-flake8-tests`` again
+to update them.
+
+.. tip::
+
+ Try fixing flake8 errors in reverse order. This eliminates the need for
+ multiple runs of ``flake8`` just to re-compute line numbers and makes it
+ much easier to fix errors directly off of the Travis output.
+
+.. warning::
+
+ Flake8 requires setuptools in order to run. If you installed ``py-flake8``
+ with Spack, make sure to add ``py-setuptools`` to your ``PYTHONPATH``.
+ Otherwise, you will get an error message like:
+
+ .. code-block:: console
+
+ Traceback (most recent call last):
+ File: "/usr/bin/flake8", line 5, in <module>
+ from pkg_resources import load_entry_point
+ ImportError: No module named pkg_resources
+
+^^^^^^^^^^^^^^^^^^^
+Documentation Tests
+^^^^^^^^^^^^^^^^^^^
+
+Spack uses `Sphinx <http://www.sphinx-doc.org/en/stable/>`_ to build its
+documentation. In order to prevent things like broken links and missing imports,
+we added documentation tests that build the documentation and fail if there
+are any warning or error messages.
+
+Building the documentation requires several dependencies, all of which can be
+installed with Spack:
+
+* sphinx
+* graphviz
+* git
+* mercurial
+* subversion
+
+.. warning::
+
+ Sphinx has `several required dependencies <https://github.com/LLNL/spack/blob/develop/var/spack/repos/builtin/packages/py-sphinx/package.py>`_.
+ If you installed ``py-sphinx`` with Spack, make sure to add all of these
+ dependencies to your ``PYTHONPATH``. The easiest way to do this is to run
+ ``spack activate py-sphinx`` so that all of the dependencies are symlinked
+ to a central location. If you see an error message like:
+
+ .. code-block:: console
+
+ Traceback (most recent call last):
+ File: "/usr/bin/flake8", line 5, in <module>
+ from pkg_resources import load_entry_point
+ ImportError: No module named pkg_resources
+
+ that means Sphinx couldn't find setuptools in your ``PYTHONPATH``.
+
+Once all of the dependencies are installed, you can try building the documentation:
+
+.. code-block:: console
+
+ $ cd "$SPACK_ROOT/lib/spack/docs"
+ $ make clean
+ $ make
+
+If you see any warning or error messages, you will have to correct those before
+your PR is accepted.
+
+.. note::
+
+ There is also a ``run-doc-tests`` script in the Quality Assurance directory.
+ The only difference between running this script and running ``make`` by hand
+ is that the script will exit immediately if it encounters an error or warning.
+ This is necessary for Travis CI. If you made a lot of documentation tests, it
+ is much quicker to run ``make`` by hand so that you can see all of the warnings
+ at once.
+
+If you are editing the documentation, you should obviously be running the
+documentation tests. But even if you are simply adding a new package, your
+changes could cause the documentation tests to fail:
+
+.. code-block:: console
+
+ package_list.rst:8745: WARNING: Block quote ends without a blank line; unexpected unindent.
+
+At first, this error message will mean nothing to you, since you didn't edit
+that file. Until you look at line 8745 of the file in question:
+
+.. code-block:: rst
+
+ Description:
+ NetCDF is a set of software libraries and self-describing, machine-
+ independent data formats that support the creation, access, and sharing
+ of array-oriented scientific data.
+
+Our documentation includes :ref:`a list of all Spack packages <package-list>`.
+If you add a new package, its docstring is added to this page. The problem in
+this case was that the docstring looked like:
+
+.. code-block:: python
+
+ class Netcdf(Package):
+ """
+ NetCDF is a set of software libraries and self-describing,
+ machine-independent data formats that support the creation,
+ access, and sharing of array-oriented scientific data.
+ """
+
+Docstrings cannot start with a newline character, or else Sphinx will complain.
+Instead, they should look like:
+
+.. code-block:: python
+
+ class Netcdf(Package):
+ """NetCDF is a set of software libraries and self-describing,
+ machine-independent data formats that support the creation,
+ access, and sharing of array-oriented scientific data."""
+
+Documentation changes can result in much more obfuscated warning messages.
+If you don't understand what they mean, feel free to ask when you submit
+your PR.
+
+-------------
+Git Workflows
+-------------
+
+Spack is still in the beta stages of development. Most of our users run off of
+the develop branch, and fixes and new features are constantly being merged. So
+how do you keep up-to-date with upstream while maintaining your own local
+differences and contributing PRs to Spack?
+
+^^^^^^^^^
+Branching
+^^^^^^^^^
+
+The easiest way to contribute a pull request is to make all of your changes on
+new branches. Make sure your ``develop`` is up-to-date and create a new branch
+off of it:
+
+.. code-block:: console
+
+ $ git checkout develop
+ $ git pull upstream develop
+ $ git branch <descriptive_branch_name>
+ $ git checkout <descriptive_branch_name>
+
+Here we assume that the local ``develop`` branch tracks the upstream develop
+branch of Spack. This is not a requirement and you could also do the same with
+remote branches. But for some it is more convenient to have a local branch that
+tracks upstream.
+
+Normally we prefer that commits pertaining to a package ``<package-name>`` have
+a message ``<package-name>: descriptive message``. It is important to add
+descriptive message so that others, who might be looking at your changes later
+(in a year or maybe two), would understand the rationale behind them.
+
+Now, you can make your changes while keeping the ``develop`` branch pure.
+Edit a few files and commit them by running:
+
+.. code-block:: console
+
+ $ git add <files_to_be_part_of_the_commit>
+ $ git commit --message <descriptive_message_of_this_particular_commit>
+
+Next, push it to your remote fork and create a PR:
+
+.. code-block:: console
+
+ $ git push origin <descriptive_branch_name> --set-upstream
+
+GitHub provides a `tutorial <https://help.github.com/articles/about-pull-requests/>`_
+on how to file a pull request. When you send the request, make ``develop`` the
+destination branch.
+
+If you need this change immediately and don't have time to wait for your PR to
+be merged, you can always work on this branch. But if you have multiple PRs,
+another option is to maintain a Frankenstein branch that combines all of your
+other branches:
+
+.. code-block:: console
+
+ $ git co develop
+ $ git branch <your_modified_develop_branch>
+ $ git checkout <your_modified_develop_branch>
+ $ git merge <descriptive_branch_name>
+
+This can be done with each new PR you submit. Just make sure to keep this local
+branch up-to-date with upstream ``develop`` too.
+
+^^^^^^^^^^^^^^
+Cherry-Picking
+^^^^^^^^^^^^^^
+
+What if you made some changes to your local modified develop branch and already
+committed them, but later decided to contribute them to Spack? You can use
+cherry-picking to create a new branch with only these commits.
+
+First, check out your local modified develop branch:
+
+.. code-block:: console
+
+ $ git checkout <your_modified_develop_branch>
+
+Now, get the hashes of the commits you want from the output of:
+
+.. code-block:: console
+
+ $ git log
+
+Next, create a new branch off of upstream ``develop`` and copy the commits
+that you want in your PR:
+
+.. code-block:: console
+
+ $ git checkout develop
+ $ git pull upstream develop
+ $ git branch <descriptive_branch_name>
+ $ git checkout <descriptive_branch_name>
+ $ git cherry-pick <hash>
+ $ git push origin <descriptive_branch_name> --set-upstream
+
+Now you can create a PR from the web-interface of GitHub. The net result is as
+follows:
+
+#. You patched your local version of Spack and can use it further.
+#. You "cherry-picked" these changes in a stand-alone branch and submitted it
+ as a PR upstream.
+
+Should you have several commits to contribute, you could follow the same
+procedure by getting hashes of all of them and cherry-picking to the PR branch.
+
+.. note::
+
+ It is important that whenever you change something that might be of
+ importance upstream, create a pull request as soon as possible. Do not wait
+ for weeks/months to do this, because:
+
+ #. you might forget why you modified certain files
+ #. it could get difficult to isolate this change into a stand-alone clean PR.
+
+^^^^^^^^
+Rebasing
+^^^^^^^^
+
+Other developers are constantly making contributions to Spack, possibly on the
+same files that your PR changed. If their PR is merged before yours, it can
+create a merge conflict. This means that your PR can no longer be automatically
+merged without a chance of breaking your changes. In this case, you will be
+asked to rebase on top of the latest upstream ``develop``.
+
+First, make sure your develop branch is up-to-date:
+
+.. code-block:: console
+
+ $ git checkout develop
+ $ git pull upstream develop
+
+Now, we need to switch to the branch you submitted for your PR and rebase it
+on top of develop:
+
+.. code-block:: console
+
+ $ git checkout <descriptive_branch_name>
+ $ git rebase develop
+
+Git will likely ask you to resolve conflicts. Edit the file that it says can't
+be merged automatically and resolve the conflict. Then, run:
+
+.. code-block:: console
+
+ $ git add <file_that_could_not_be_merged>
+ $ git rebase --continue
+
+You may have to repeat this process multiple times until all conflicts are resolved.
+Once this is done, simply force push your rebased branch to your remote fork:
+
+.. code-block:: console
+
+ $ git push --force origin <descriptive_branch_name>
+
+^^^^^^^^^^^^^^^^^^^^^^^^^
+Rebasing with cherry-pick
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You can also perform a rebase using ``cherry-pick``. First, create a temporary
+backup branch:
+
+.. code-block:: console
+
+ $ git checkout <descriptive_branch_name>
+ $ git branch tmp
+
+If anything goes wrong, you can always go back to your ``tmp`` branch.
+Now, look at the logs and save the hashes of any commits you would like to keep:
+
+.. code-block:: console
+
+ $ git log
+
+Next, go back to the original branch and reset it to ``develop``.
+Before doing so, make sure that you local ``develop`` branch is up-to-date
+with upstream:
+
+.. code-block:: console
+
+ $ git checkout develop
+ $ git pull upstream develop
+ $ git checkout <descriptive_branch_name>
+ $ git reset --hard develop
+
+Now you can cherry-pick relevant commits:
+
+.. code-block:: console
+
+ $ git cherry-pick <hash1>
+ $ git cherry-pick <hash2>
+
+Push the modified branch to your fork:
+
+.. code-block:: console
+
+ $ git push --force origin <descriptive_branch_name>
+
+If everything looks good, delete the backup branch:
+
+.. code-block:: console
+
+ $ git branch --delete --force tmp
+
+^^^^^^^^^^^^^^^^^^
+Re-writing History
+^^^^^^^^^^^^^^^^^^
+
+Sometimes you may end up on a branch that has diverged so much from develop
+that it cannot easily be rebased. If the current commits history is more of
+an experimental nature and only the net result is important, you may rewrite
+the history.
+
+First, merge upstream ``develop`` and reset you branch to it. On the branch
+in question, run:
+
+.. code-block:: console
+
+ $ git merge develop
+ $ git reset develop
+
+At this point your branch will point to the same commit as develop and
+thereby the two are indistinguishable. However, all the files that were
+previously modified will stay as such. In other words, you do not lose the
+changes you made. Changes can be reviewed by looking at diffs:
+
+.. code-block:: console
+
+ $ git status
+ $ git diff
+
+The next step is to rewrite the history by adding files and creating commits:
+
+.. code-block:: console
+
+ $ git add <files_to_be_part_of_commit>
+ $ git commit --message <descriptive_message>
+
+After all changed files are committed, you can push the branch to your fork
+and create a PR:
+
+.. code-block:: console
+
+ $ git push origin --set-upstream
diff --git a/lib/spack/docs/developer_guide.rst b/lib/spack/docs/developer_guide.rst
index d28fe4b2a5..5ddbaf2478 100644
--- a/lib/spack/docs/developer_guide.rst
+++ b/lib/spack/docs/developer_guide.rst
@@ -1,7 +1,8 @@
.. _developer_guide:
+===============
Developer Guide
-=====================
+===============
This guide is intended for people who want to work on Spack itself.
If you just want to develop packages, see the :ref:`packaging-guide`.
@@ -11,17 +12,18 @@ It is assumed that you've read the :ref:`basic-usage` and
concepts discussed there. If you're not, we recommend reading those
first.
+--------
Overview
------------------------
+--------
Spack is designed with three separate roles in mind:
- #. **Users**, who need to install software *without* knowing all the
- details about how it is built.
- #. **Packagers** who know how a particular software package is
- built and encode this information in package files.
- #. **Developers** who work on Spack, add new features, and try to
- make the jobs of packagers and users easier.
+#. **Users**, who need to install software *without* knowing all the
+ details about how it is built.
+#. **Packagers** who know how a particular software package is
+ built and encode this information in package files.
+#. **Developers** who work on Spack, add new features, and try to
+ make the jobs of packagers and users easier.
Users could be end users installing software in their home directory,
or administrators installing software to a shared directory on a
@@ -41,9 +43,9 @@ specification.
This gets us to the two key concepts in Spack's software design:
- #. **Specs**: expressions for describing builds of software, and
- #. **Packages**: Python modules that build software according to a
- spec.
+#. **Specs**: expressions for describing builds of software, and
+#. **Packages**: Python modules that build software according to a
+ spec.
A package is a template for building particular software, and a spec
as a descriptor for one or more instances of that template. Users
@@ -63,75 +65,75 @@ building the software off to the package object. The rest of this
document describes all the pieces that come together to make that
happen.
-
+-------------------
Directory Structure
--------------------------
+-------------------
So that you can familiarize yourself with the project, we'll start
-with a high level view of Spack's directory structure::
+with a high level view of Spack's directory structure:
+
+.. code-block:: none
- spack/ <- installation root
- bin/
- spack <- main spack executable
+ spack/ <- installation root
+ bin/
+ spack <- main spack executable
- etc/
- spack/ <- Spack config files.
- Can be overridden by files in ~/.spack.
+ etc/
+ spack/ <- Spack config files.
+ Can be overridden by files in ~/.spack.
- var/
- spack/ <- build & stage directories
- repos/ <- contains package repositories
- builtin/ <- pkg repository that comes with Spack
- repo.yaml <- descriptor for the builtin repository
- packages/ <- directories under here contain packages
- cache/ <- saves resources downloaded during installs
+ var/
+ spack/ <- build & stage directories
+ repos/ <- contains package repositories
+ builtin/ <- pkg repository that comes with Spack
+ repo.yaml <- descriptor for the builtin repository
+ packages/ <- directories under here contain packages
+ cache/ <- saves resources downloaded during installs
- opt/
- spack/ <- packages are installed here
+ opt/
+ spack/ <- packages are installed here
- lib/
- spack/
- docs/ <- source for this documentation
- env/ <- compiler wrappers for build environment
+ lib/
+ spack/
+ docs/ <- source for this documentation
+ env/ <- compiler wrappers for build environment
- external/ <- external libs included in Spack distro
- llnl/ <- some general-use libraries
+ external/ <- external libs included in Spack distro
+ llnl/ <- some general-use libraries
- spack/ <- spack module; contains Python code
- cmd/ <- each file in here is a spack subcommand
- compilers/ <- compiler description files
- test/ <- unit test modules
- util/ <- common code
+ spack/ <- spack module; contains Python code
+ cmd/ <- each file in here is a spack subcommand
+ compilers/ <- compiler description files
+ test/ <- unit test modules
+ util/ <- common code
Spack is designed so that it could live within a `standard UNIX
directory hierarchy <http://linux.die.net/man/7/hier>`_, so ``lib``,
``var``, and ``opt`` all contain a ``spack`` subdirectory in case
Spack is installed alongside other software. Most of the interesting
-parts of Spack live in ``lib/spack``. Files under ``var`` are created
-as needed, so there is no ``var`` directory when you initially clone
-Spack from the repository.
+parts of Spack live in ``lib/spack``.
Spack has *one* directory layout and there is no install process.
-version and the source code. Most Python programs don't look like
-this (they use distutils, ``setup.py``, etc.) but we wanted to make
-Spack *very* easy to use. The simple layout spares users from the
-need to install Spack into a Python environment. Many users don't
-have write access to a Python installation, and installing an entire
-new instance of Python to bootstrap Spack would be very complicated.
+Most Python programs don't look like this (they use distutils, ``setup.py``,
+etc.) but we wanted to make Spack *very* easy to use. The simple layout
+spares users from the need to install Spack into a Python environment.
+Many users don't have write access to a Python installation, and installing
+an entire new instance of Python to bootstrap Spack would be very complicated.
Users should not have to install install a big, complicated package to
use the thing that's supposed to spare them from the details of big,
complicated packages. The end result is that Spack works out of the
box: clone it and add ``bin`` to your PATH and you're ready to go.
-
+--------------
Code Structure
--------------------------
+--------------
This section gives an overview of the various Python modules in Spack,
grouped by functionality.
+^^^^^^^^^^^^^^^^^^^^^^^
Package-related modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^
:mod:`spack.package`
Contains the :class:`Package <spack.package.Package>` class, which
@@ -158,9 +160,9 @@ Package-related modules
decorator, which allows :ref:`multimethods <multimethods>` in
packages.
-
+^^^^^^^^^^^^^^^^^^^^
Spec-related modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
:mod:`spack.spec`
Contains :class:`Spec <spack.spec.Spec>` and :class:`SpecParser
@@ -208,9 +210,9 @@ Spec-related modules
Not yet implemented. Should eventually have architecture
descriptions for cross-compiling.
-
+^^^^^^^^^^^^^^^^^
Build environment
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
:mod:`spack.stage`
Handles creating temporary directories for builds.
@@ -224,15 +226,17 @@ Build environment
Create more implementations of this to change the hierarchy and
naming scheme in ``$spack_prefix/opt``
+^^^^^^^^^^^^^^^^^
Spack Subcommands
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
:mod:`spack.cmd`
Each module in this package implements a Spack subcommand. See
:ref:`writing commands <writing-commands>` for details.
+^^^^^^^^^^
Unit tests
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^
:mod:`spack.test`
Implements Spack's test suite. Add a module and put its name in
@@ -242,78 +246,100 @@ Unit tests
This is a fake package hierarchy used to mock up packages for
Spack's test suite.
+^^^^^^^^^^^^^
Other Modules
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-:mod:`spack.globals`
- Includes global settings for Spack. the default policy classes for
- things like :ref:`temporary space <temp-space>` and
- :ref:`concretization <concretization-policies>`.
-
-:mod:`spack.tty`
- Basic output functions for all of the messages Spack writes to the
- terminal.
-
-:mod:`spack.color`
- Implements a color formatting syntax used by ``spack.tty``.
+^^^^^^^^^^^^^
:mod:`spack.url`
URL parsing, for deducing names and versions of packages from
tarball URLs.
-:mod:`spack.util`
- In this package are a number of utility modules for the rest of
- Spack.
-
:mod:`spack.error`
:class:`SpackError <spack.error.SpackError>`, the base class for
Spack's exception hierarchy.
+:mod:`llnl.util.tty`
+ Basic output functions for all of the messages Spack writes to the
+ terminal.
-Spec objects
--------------------------
-
-Package objects
--------------------------
+:mod:`llnl.util.tty.color`
+ Implements a color formatting syntax used by ``spack.tty``.
+:mod:`llnl.util`
+ In this package are a number of utility modules for the rest of
+ Spack.
-Most spack commands
-look something like this:
+------------
+Spec objects
+------------
- #. Parse an abstract spec (or specs) from the command line,
- #. *Normalize* the spec based on information in package files,
- #. *Concretize* the spec according to some customizable policies,
- #. Instantiate a package based on the spec, and
- #. Call methods (e.g., ``install()``) on the package object.
+---------------
+Package objects
+---------------
+Most spack commands look something like this:
+#. Parse an abstract spec (or specs) from the command line,
+#. *Normalize* the spec based on information in package files,
+#. *Concretize* the spec according to some customizable policies,
+#. Instantiate a package based on the spec, and
+#. Call methods (e.g., ``install()``) on the package object.
The information in Package files is used at all stages in this
process.
+Conceptually, packages are overloaded. They contain:
-Conceptually, packages are overloaded. They contain
-
+-------------
Stage objects
--------------------------
+-------------
.. _writing-commands:
+----------------
Writing commands
--------------------------
+----------------
+----------
Unit tests
--------------------------
+----------
+------------
Unit testing
--------------------------
-
+------------
+------------------
Developer commands
--------------------------
+------------------
+^^^^^^^^^^^^^
``spack doc``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^
+^^^^^^^^^^^^^^
``spack test``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
+
+---------
+Profiling
+---------
+
+Spack has some limited built-in support for profiling, and can report
+statistics using standard Python timing tools. To use this feature,
+supply ``--profile`` to Spack on the command line, before any subcommands.
+
+.. _spack-p:
+
+^^^^^^^^^^^^^^^^^^^
+``spack --profile``
+^^^^^^^^^^^^^^^^^^^
+
+``spack --profile`` output looks like this:
+
+.. command-output:: spack --profile graph dyninst
+ :ellipsis: 25
+
+The bottom of the output shows the top most time consuming functions,
+slowest on top. The profiling support is from Python's built-in tool,
+`cProfile
+<https://docs.python.org/2/library/profile.html#module-cProfile>`_.
diff --git a/lib/spack/docs/features.rst b/lib/spack/docs/features.rst
index 27a3b4b435..2df1d182d6 100644
--- a/lib/spack/docs/features.rst
+++ b/lib/spack/docs/features.rst
@@ -1,29 +1,32 @@
-Feature overview
-==================
+================
+Feature Overview
+================
This is a high-level overview of features that make Spack different
from other `package managers
<http://en.wikipedia.org/wiki/Package_management_system>`_ and `port
systems <http://en.wikipedia.org/wiki/Ports_collection>`_.
+---------------------------
Simple package installation
-----------------------------
+---------------------------
Installing the default version of a package is simple. This will install
the latest version of the ``mpileaks`` package and all of its dependencies:
-.. code-block:: sh
+.. code-block:: console
$ spack install mpileaks
+--------------------------------
Custom versions & configurations
--------------------------------------------
+--------------------------------
Spack allows installation to be customized. Users can specify the
version, build compiler, compile-time options, and cross-compile
platform, all on the command line.
-.. code-block:: sh
+.. code-block:: console
# Install a particular version by appending @
$ spack install mpileaks@1.1.2
@@ -47,37 +50,39 @@ Users can specify as many or few options as they care about. Spack
will fill in the unspecified values with sensible defaults. The two listed
syntaxes for variants are identical when the value is boolean.
-
+----------------------
Customize dependencies
--------------------------------------
+----------------------
Spack allows *dependencies* of a particular installation to be
customized extensively. Suppose that ``mpileaks`` depends indirectly
on ``libelf`` and ``libdwarf``. Using ``^``, users can add custom
configurations for the dependencies:
-.. code-block:: sh
+.. code-block:: console
# Install mpileaks and link it with specific versions of libelf and libdwarf
$ spack install mpileaks@1.1.2 %gcc@4.7.3 +debug ^libelf@0.8.12 ^libdwarf@20130729+debug
-
+------------------------
Non-destructive installs
--------------------------------------
+------------------------
Spack installs every unique package/dependency configuration into its
own prefix, so new installs will not break existing ones.
+-------------------------------
Packages can peacefully coexist
--------------------------------------
+-------------------------------
Spack avoids library misconfiguration by using ``RPATH`` to link
dependencies. When a user links a library or runs a program, it is
tied to the dependencies it was built with, so there is no need to
manipulate ``LD_LIBRARY_PATH`` at runtime.
+-------------------------
Creating packages is easy
--------------------------------------
+-------------------------
To create a new packages, all Spack needs is a URL for the source
archive. The ``spack create`` command will create a boilerplate
@@ -86,7 +91,7 @@ in pure Python.
For example, this command:
-.. code-block:: sh
+.. code-block:: console
$ spack create http://www.mr511.de/software/libelf-0.8.13.tar.gz
@@ -96,16 +101,26 @@ creates a simple python file:
from spack import *
+
class Libelf(Package):
- homepage = "http://www.example.com/"
+ """FIXME: Put a proper description of your package here."""
+
+ # FIXME: Add a proper url for your package's homepage here.
+ homepage = "http://www.example.com"
url = "http://www.mr511.de/software/libelf-0.8.13.tar.gz"
version('0.8.13', '4136d7b4c04df68b686570afa26988ac')
- def install(self, prefix):
- configure("--prefix=%s" % prefix)
+ # FIXME: Add dependencies if required.
+ # depends_on('foo')
+
+ def install(self, spec, prefix):
+ # FIXME: Modify the configure line to suit your build system here.
+ configure('--prefix={0}'.format(prefix))
+
+ # FIXME: Add logic to build and install here.
make()
- make("install")
+ make('install')
It doesn't take much python coding to get from there to a working
package:
diff --git a/lib/spack/docs/getting_started.rst b/lib/spack/docs/getting_started.rst
index 2c5b68ea65..34cbf453e1 100644
--- a/lib/spack/docs/getting_started.rst
+++ b/lib/spack/docs/getting_started.rst
@@ -1,22 +1,48 @@
+.. _getting_started:
+
+===============
Getting Started
-====================
+===============
+
+-------------
+Prerequisites
+-------------
+
+Spack has the following minimum requirements, which must be installed
+before Spack is run:
+
+1. Python 2.6 or 2.7
+2. A C/C++ compiler
+3. The ``git`` and ``curl`` commands.
+
+These requirements can be easily installed on most modern Linux systems;
+on Macintosh, XCode is required. Spack is designed to run on HPC
+platforms like Cray and BlueGene/Q. Not all packages should be expected
+to work on all platforms. A build matrix showing which packages are
+working on which systems is planned but not yet available.
-Download
---------------------
+------------
+Installation
+------------
-Getting spack is easy. You can clone it from the `github repository
+Getting Spack is easy. You can clone it from the `github repository
<https://github.com/llnl/spack>`_ using this command:
-.. code-block:: sh
+.. code-block:: console
$ git clone https://github.com/llnl/spack.git
-This will create a directory called ``spack``. We'll assume that the
-full path to this directory is in the ``SPACK_ROOT`` environment
-variable. Add ``$SPACK_ROOT/bin`` to your path and you're ready to
-go:
+This will create a directory called ``spack``.
+
+^^^^^^^^^^^^^^^^^^^^^^^^
+Add Spack to the Shell
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+We'll assume that the full path to your downloaded Spack directory is
+in the ``SPACK_ROOT`` environment variable. Add ``$SPACK_ROOT/bin``
+to your path and you're ready to go:
-.. code-block:: sh
+.. code-block:: console
$ export PATH=$SPACK_ROOT/bin:$PATH
$ spack install libelf
@@ -24,9 +50,10 @@ go:
For a richer experience, use Spack's `shell support
<http://software.llnl.gov/spack/basic_usage.html#environment-modules>`_:
-.. code-block:: sh
+.. code-block:: console
# For bash users
+ $ export SPACK_ROOT=/path/to/spack
$ . $SPACK_ROOT/share/spack/setup-env.sh
# For tcsh or csh users (note you must set SPACK_ROOT)
@@ -35,21 +62,1075 @@ For a richer experience, use Spack's `shell support
This automatically adds Spack to your ``PATH``.
-Installation
---------------------
+^^^^^^^^^^^^^^^^^
+Clean Environment
+^^^^^^^^^^^^^^^^^
+
+Many packages' installs can be broken by changing environment
+variables. For example, a package might pick up the wrong build-time
+dependencies (most of them not specified) depending on the setting of
+``PATH``. ``GCC`` seems to be particularly vulnerable to these issues.
-You don't need to install Spack; it's ready to run as soon as you
-clone it from git.
+Therefore, it is recommended that Spack users run with a *clean
+environment*, especially for ``PATH``. Only software that comes with
+the system, or that you know you wish to use with Spack, should be
+included. This procedure will avoid many strange build errors.
-You may want to run it out of a prefix other than the git repository
+
+^^^^^^^^^^^^^^^^^^
+Check Installation
+^^^^^^^^^^^^^^^^^^
+
+With Spack installed, you should be able to run some basic Spack
+commands. For example:
+
+.. code-block:: console
+
+ $ spack spec netcdf
+ ...
+ netcdf@4.4.1%gcc@5.3.0~hdf4+mpi arch=linux-SuSE11-x86_64
+ ^curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^openssl@system%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^hdf5@1.10.0-patch1%gcc@5.3.0+cxx~debug+fortran+mpi+shared~szip~threadsafe arch=linux-SuSE11-x86_64
+ ^openmpi@1.10.1%gcc@5.3.0~mxm~pmi~psm~psm2~slurm~sqlite3~thread_multiple~tm+verbs+vt arch=linux-SuSE11-x86_64
+ ^m4@1.4.17%gcc@5.3.0+sigsegv arch=linux-SuSE11-x86_64
+ ^libsigsegv@2.10%gcc@5.3.0 arch=linux-SuSE11-x86_64
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+Optional: Alternate Prefix
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+You may want to run Spack out of a prefix other than the git repository
you cloned. The ``spack bootstrap`` command provides this
functionality. To install spack in a new directory, simply type:
-.. code-block:: sh
+.. code-block:: console
- $ spack bootstrap /my/favorite/prefix
+ $ spack bootstrap /my/favorite/prefix
This will install a new spack script in ``/my/favorite/prefix/bin``,
which you can use just like you would the regular spack script. Each
copy of spack installs packages into its own ``$PREFIX/opt``
directory.
+
+
+^^^^^^^^^^
+Next Steps
+^^^^^^^^^^
+
+In theory, Spack doesn't need any additional installation; just
+download and run! But in real life, additional steps are usually
+required before Spack can work in a practical sense. Read on...
+
+
+.. _compiler-config:
+
+----------------------
+Compiler configuration
+----------------------
+
+Spack has the ability to build packages with multiple compilers and
+compiler versions. Spack searches for compilers on your machine
+automatically the first time it is run. It does this by inspecting
+your ``PATH``.
+
+.. _cmd-spack-compilers:
+
+^^^^^^^^^^^^^^^^^^^
+``spack compilers``
+^^^^^^^^^^^^^^^^^^^
+
+You can see which compilers spack has found by running ``spack
+compilers`` or ``spack compiler list``:
+
+.. code-block:: console
+
+ $ spack compilers
+ ==> Available compilers
+ -- gcc ---------------------------------------------------------
+ gcc@4.9.0 gcc@4.8.0 gcc@4.7.0 gcc@4.6.2 gcc@4.4.7
+ gcc@4.8.2 gcc@4.7.1 gcc@4.6.3 gcc@4.6.1 gcc@4.1.2
+ -- intel -------------------------------------------------------
+ intel@15.0.0 intel@14.0.0 intel@13.0.0 intel@12.1.0 intel@10.0
+ intel@14.0.3 intel@13.1.1 intel@12.1.5 intel@12.0.4 intel@9.1
+ intel@14.0.2 intel@13.1.0 intel@12.1.3 intel@11.1
+ intel@14.0.1 intel@13.0.1 intel@12.1.2 intel@10.1
+ -- clang -------------------------------------------------------
+ clang@3.4 clang@3.3 clang@3.2 clang@3.1
+ -- pgi ---------------------------------------------------------
+ pgi@14.3-0 pgi@13.2-0 pgi@12.1-0 pgi@10.9-0 pgi@8.0-1
+ pgi@13.10-0 pgi@13.1-1 pgi@11.10-0 pgi@10.2-0 pgi@7.1-3
+ pgi@13.6-0 pgi@12.8-0 pgi@11.1-0 pgi@9.0-4 pgi@7.0-6
+
+Any of these compilers can be used to build Spack packages. More on
+how this is done is in :ref:`sec-specs`.
+
+.. _spack-compiler-add:
+
+^^^^^^^^^^^^^^^^^^^^^^
+``spack compiler add``
+^^^^^^^^^^^^^^^^^^^^^^
+
+An alias for ``spack compiler find``.
+
+.. _spack-compiler-find:
+
+^^^^^^^^^^^^^^^^^^^^^^^
+``spack compiler find``
+^^^^^^^^^^^^^^^^^^^^^^^
+
+If you do not see a compiler in this list, but you want to use it with
+Spack, you can simply run ``spack compiler find`` with the path to
+where the compiler is installed. For example:
+
+.. code-block:: console
+
+ $ spack compiler find /usr/local/tools/ic-13.0.079
+ ==> Added 1 new compiler to /Users/gamblin2/.spack/compilers.yaml
+ intel@13.0.079
+
+Or you can run ``spack compiler find`` with no arguments to force
+auto-detection. This is useful if you do not know where compilers are
+installed, but you know that new compilers have been added to your
+``PATH``. For example, you might load a module, like this:
+
+.. code-block:: console
+
+ $ module load gcc-4.9.0
+ $ spack compiler find
+ ==> 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 add it to
+``PATH``, and then it adds the compiler to Spack.
+
+.. _spack-compiler-info:
+
+^^^^^^^^^^^^^^^^^^^^^^^
+``spack compiler info``
+^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to see specifics on a particular compiler, you can run
+``spack compiler info`` on it:
+
+.. code-block:: console
+
+ $ spack compiler info intel@15
+ intel@15.0.0:
+ paths:
+ cc = /usr/local/bin/icc-15.0.090
+ cxx = /usr/local/bin/icpc-15.0.090
+ f77 = /usr/local/bin/ifort-15.0.090
+ fc = /usr/local/bin/ifort-15.0.090
+ modules = []
+ operating system = centos6
+ ...
+
+This shows which C, C++, and Fortran compilers were detected by Spack.
+Notice also that we didn't have to be too specific about the
+version. We just said ``intel@15``, and information about the only
+matching Intel compiler was displayed.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Manual compiler configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If auto-detection fails, you can manually configure a compiler by
+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:
+
+.. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: []
+ operating_system: centos6
+ paths:
+ 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
+ spec: intel@15.0.0:
+
+For compilers that do not support Fortran (like ``clang``), put
+``None`` for ``f77`` and ``fc``:
+
+.. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: []
+ operating_system: centos6
+ paths:
+ cc: /usr/bin/clang
+ cxx: /usr/bin/clang++
+ f77: None
+ fc: None
+ spec: clang@3.3svn
+
+Once you save the file, the configured compilers will show up in the
+list displayed by ``spack compilers``.
+
+You can also add compiler flags to manually configured compilers. These
+flags should be specified in the ``flags`` section of the compiler
+specification. The valid flags are ``cflags``, ``cxxflags``, ``fflags``,
+``cppflags``, ``ldflags``, and ``ldlibs``. For example:
+
+.. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: []
+ operating_system: centos6
+ paths:
+ cc: /usr/bin/gcc
+ cxx: /usr/bin/g++
+ f77: /usr/bin/gfortran
+ fc: /usr/bin/gfortran
+ flags:
+ cflags: -O3 -fPIC
+ cxxflags: -O3 -fPIC
+ cppflags: -O3 -fPIC
+ spec: gcc@4.7.2
+
+These flags will be treated by spack as if they were entered from
+the command line each time this compiler is used. The compiler wrappers
+then inject those flags into the compiler command. Compiler flags
+entered from the command line will be discussed in more detail in the
+following section.
+
+^^^^^^^^^^^^^^^^^^^^^^^
+Build Your Own Compiler
+^^^^^^^^^^^^^^^^^^^^^^^
+
+If you are particular about which compiler/version you use, you might
+wish to have Spack build it for you. For example:
+
+.. code-block:: console
+
+ $ spack install gcc@4.9.3
+
+Once that has finished, you will need to add it to your
+``compilers.yaml`` file. You can then set Spack to use it by default
+by adding the following to your ``packages.yaml`` file:
+
+.. code-block:: yaml
+
+ packages:
+ all:
+ compiler: [gcc@4.9.3]
+
+
+.. tip::
+
+ If you are building your own compiler, some users prefer to have a
+ Spack instance just for that. For example, create a new Spack in
+ ``~/spack-tools`` and then run ``~/spack-tools/bin/spack install
+ gcc@4.9.3``. Once the compiler is built, don't build anything
+ more in that Spack instance; instead, create a new "real" Spack
+ instance, configure Spack to use the compiler you've just built,
+ and then build your application software in the new Spack
+ instance. Following this tip makes it easy to delete all your
+ Spack packages *except* the compiler.
+
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Compilers Requiring Modules
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Many installed compilers will work regardless of the environment they
+are called with. However, some installed compilers require
+``$LD_LIBRARY_PATH`` or other environment variables to be set in order
+to run; this is typical for Intel and other proprietary compilers.
+
+In such a case, you should tell Spack which module(s) to load in order
+to run the chosen compiler (If the compiler does not come with a
+module file, you might consider making one by hand). Spack will load
+this module into the environment ONLY when the compiler is run, and
+NOT in general for a package's ``install()`` method. See, for
+example, this ``compilers.yaml`` file:
+
+.. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: [other/comp/gcc-5.3-sp3]
+ operating_system: SuSE11
+ paths:
+ cc: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gcc
+ cxx: /usr/local/other/SLES11.3/gcc/5.3.0/bin/g++
+ f77: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gfortran
+ fc: /usr/local/other/SLES11.3/gcc/5.3.0/bin/gfortran
+ spec: gcc@5.3.0
+
+Some compilers require special environment settings to be loaded not just
+to run, but also to execute the code they build, breaking packages that
+need to execute code they just compiled. If it's not possible or
+practical to use a better compiler, you'll need to ensure that
+environment settings are preserved for compilers like this (i.e., you'll
+need to load the module or source the compiler's shell script).
+
+By default, Spack tries to ensure that builds are reproducible by
+cleaning the environment before building. If this interferes with your
+compiler settings, you CAN use ``spack install --dirty`` as a workaround.
+Note that this MAY interfere with package builds.
+
+.. _licensed-compilers:
+
+^^^^^^^^^^^^^^^^^^
+Licensed Compilers
+^^^^^^^^^^^^^^^^^^
+
+Some proprietary compilers require licensing to use. If you need to
+use a licensed compiler (eg, PGI), the process is similar to a mix of
+build your own, plus modules:
+
+#. Create a Spack package (if it doesn't exist already) to install
+ your compiler. Follow instructions on installing :ref:`license`.
+
+#. Once the compiler is installed, you should be able to test it by
+ using Spack to load the module it just created, and running simple
+ builds (eg: ``cc helloWorld.c && ./a.out``)
+
+#. Add the newly-installed compiler to ``compilers.yaml`` as shown
+ above.
+
+.. _mixed-toolchains:
+
+^^^^^^^^^^^^^^^^
+Mixed Toolchains
+^^^^^^^^^^^^^^^^
+
+Modern compilers typically come with related compilers for C, C++ and
+Fortran bundled together. When possible, results are best if the same
+compiler is used for all languages.
+
+In some cases, this is not possible. For example, starting with macOS El
+Capitan (10.11), many packages no longer build with GCC, but XCode
+provides no Fortran compilers. The user is therefore forced to use a
+mixed toolchain: XCode-provided Clang for C/C++ and GNU ``gfortran`` for
+Fortran.
+
+#. You need to make sure that command-line tools are installed. To that
+ end run ``$ xcode-select --install``.
+
+#. Run ``$ spack compiler find`` to locate Clang.
+
+#. There are different ways to get ``gfortran`` on macOS. For example, you can
+ install GCC with Spack (``$ spack install gcc``) or with Homebrew
+ (``$ brew install gcc``).
+
+#. The only thing left to do is to edit ``~/.spack/compilers.yaml`` to provide
+ the path to ``gfortran``:
+
+ .. code-block:: yaml
+
+ compilers:
+ darwin-x86_64:
+ clang@7.3.0-apple:
+ cc: /usr/bin/clang
+ cxx: /usr/bin/clang++
+ f77: /path/to/bin/gfortran
+ fc: /path/to/bin/gfortran
+
+ If you used Spack to install GCC, you can get the installation prefix by
+ ``$ spack location -i gcc`` (this will only work if you have a single version
+ of GCC installed). Whereas for Homebrew, GCC is installed in
+ ``/usr/local/Cellar/gcc/x.y.z``.
+
+^^^^^^^^^^^^^^^^^^^^^
+Compiler Verification
+^^^^^^^^^^^^^^^^^^^^^
+
+You can verify that your compilers are configured properly by installing a
+simple package. For example:
+
+.. code-block:: console
+
+ $ spack install zlib%gcc@5.3.0
+
+--------------------------------------
+Vendor-Specific Compiler Configuration
+--------------------------------------
+
+With Spack, things usually "just work" with GCC. Not so for other
+compilers. This section provides details on how to get specific
+compilers working.
+
+^^^^^^^^^^^^^^^
+Intel Compilers
+^^^^^^^^^^^^^^^
+
+Intel compilers are unusual because a single Intel compiler version
+can emulate multiple GCC versions. In order to provide this
+functionality, the Intel compiler needs GCC to be installed.
+Therefore, the following steps are necessary to successfully use Intel
+compilers:
+
+#. Install a version of GCC that implements the desired language
+ features (``spack install gcc``).
+
+#. Tell the Intel compiler how to find that desired GCC. This may be
+ done in one of two ways:
+
+ "By default, the compiler determines which version of ``gcc`` or ``g++``
+ you have installed from the ``PATH`` environment variable.
+
+ If you want use a version of ``gcc`` or ``g++`` other than the default
+ version on your system, you need to use either the ``-gcc-name``
+ or ``-gxx-name`` compiler option to specify the path to the version of
+ ``gcc`` or ``g++`` that you want to use."
+
+ -- `Intel Reference Guide <https://software.intel.com/en-us/node/522750>`_
+
+Intel compilers may therefore be configured in one of two ways with
+Spack: using modules, or using compiler flags.
+
+""""""""""""""""""""""""""
+Configuration with Modules
+""""""""""""""""""""""""""
+
+One can control which GCC is seen by the Intel compiler with modules.
+A module must be loaded both for the Intel Compiler (so it will run)
+and GCC (so the compiler can find the intended GCC). The following
+configuration in ``compilers.yaml`` illustrates this technique:
+
+.. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: [gcc-4.9.3, intel-15.0.24]
+ operating_system: centos7
+ paths:
+ cc: /opt/intel-15.0.24/bin/icc-15.0.24-beta
+ cxx: /opt/intel-15.0.24/bin/icpc-15.0.24-beta
+ f77: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
+ fc: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
+ spec: intel@15.0.24.4.9.3
+
+
+.. note::
+
+ The version number on the Intel compiler is a combination of
+ the "native" Intel version number and the GNU compiler it is
+ targeting.
+
+""""""""""""""""""""""""""
+Command Line Configuration
+""""""""""""""""""""""""""
+
+One can also control which GCC is seen by the Intel compiler by adding
+flags to the ``icc`` command:
+
+#. Identify the location of the compiler you just installed:
+
+ .. code-block:: console
+
+ $ spack location --install-dir gcc
+ /home2/rpfische/spack2/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw...
+
+#. Set up ``compilers.yaml``, for example:
+
+ .. code-block:: yaml
+
+ compilers:
+ - compiler:
+ modules: [intel-15.0.24]
+ operating_system: centos7
+ paths:
+ cc: /opt/intel-15.0.24/bin/icc-15.0.24-beta
+ cflags: -gcc-name /home2/rpfische/spack2/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/gcc
+ cxx: /opt/intel-15.0.24/bin/icpc-15.0.24-beta
+ cxxflags: -gxx-name /home2/rpfische/spack2/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/g++
+ f77: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
+ fc: /opt/intel-15.0.24/bin/ifort-15.0.24-beta
+ fflags: -gcc-name /home2/rpfische/spack2/opt/spack/linux-centos7-x86_64/gcc-4.9.3-iy4rw.../bin/gcc
+ spec: intel@15.0.24.4.9.3
+
+
+^^^
+PGI
+^^^
+
+PGI comes with two sets of compilers for C++ and Fortran,
+distinguishable by their names. "Old" compilers:
+
+.. code-block:: yaml
+
+ cc: /soft/pgi/15.10/linux86-64/15.10/bin/pgcc
+ cxx: /soft/pgi/15.10/linux86-64/15.10/bin/pgCC
+ f77: /soft/pgi/15.10/linux86-64/15.10/bin/pgf77
+ fc: /soft/pgi/15.10/linux86-64/15.10/bin/pgf90
+
+"New" compilers:
+
+.. code-block:: yaml
+
+ cc: /soft/pgi/15.10/linux86-64/15.10/bin/pgcc
+ cxx: /soft/pgi/15.10/linux86-64/15.10/bin/pgc++
+ f77: /soft/pgi/15.10/linux86-64/15.10/bin/pgfortran
+ fc: /soft/pgi/15.10/linux86-64/15.10/bin/pgfortran
+
+Older installations of PGI contains just the old compilers; whereas
+newer installations contain the old and the new. The new compiler is
+considered preferable, as some packages
+(``hdf``) will not build with the old compiler.
+
+When auto-detecting a PGI compiler, there are cases where Spack will
+find the old compilers, when you really want it to find the new
+compilers. It is best to check this ``compilers.yaml``; and if the old
+compilers are being used, change ``pgf77`` and ``pgf90`` to
+``pgfortran``.
+
+Other issues:
+
+* There are reports that some packages will not build with PGI,
+ including ``libpciaccess`` and ``openssl``. A workaround is to
+ build these packages with another compiler and then use them as
+ dependencies for PGI-build packages. For example:
+
+ .. code-block:: console
+
+ $ spack install openmpi%pgi ^libpciaccess%gcc
+
+
+* PGI requires a license to use; see :ref:`licensed-compilers` for more
+ information on installation.
+
+.. note::
+
+ It is believed the problem with HDF 4 is that everything is
+ compiled with the ``F77`` compiler, but at some point some Fortran
+ 90 code slipped in there. So compilers that can handle both FORTRAN
+ 77 and Fortran 90 (``gfortran``, ``pgfortran``, etc) are fine. But
+ compilers specific to one or the other (``pgf77``, ``pgf90``) won't
+ work.
+
+
+^^^
+NAG
+^^^
+
+The Numerical Algorithms Group provides a licensed Fortran compiler. Like Clang,
+this requires you to set up a :ref:`mixed-toolchains`. It is recommended to use
+GCC for your C/C++ compilers.
+
+The NAG Fortran compilers are a bit more strict than other compilers, and many
+packages will fail to install with error messages like:
+
+.. code-block:: none
+
+ Error: mpi_comm_spawn_multiple_f90.f90: Argument 3 to MPI_COMM_SPAWN_MULTIPLE has data type DOUBLE PRECISION in reference from MPI_COMM_SPAWN_MULTIPLEN and CHARACTER in reference from MPI_COMM_SPAWN_MULTIPLEA
+
+In order to convince the NAG compiler not to be too picky about calling conventions,
+you can use ``FFLAGS=-mismatch`` and ``FCFLAGS=-mismatch``. This can be done through
+the command line:
+
+.. code-block:: console
+
+ $ spack install openmpi fflags=\"-mismatch\"
+
+Or it can be set permanently in your ``compilers.yaml``:
+
+.. code-block:: yaml
+
+ - compiler:
+ modules: []
+ operating_system: centos6
+ paths:
+ cc: /soft/spack/opt/spack/linux-x86_64/gcc-5.3.0/gcc-6.1.0-q2zosj3igepi3pjnqt74bwazmptr5gpj/bin/gcc
+ cxx: /soft/spack/opt/spack/linux-x86_64/gcc-5.3.0/gcc-6.1.0-q2zosj3igepi3pjnqt74bwazmptr5gpj/bin/g++
+ f77: /soft/spack/opt/spack/linux-x86_64/gcc-4.4.7/nag-6.1-jt3h5hwt5myezgqguhfsan52zcskqene/bin/nagfor
+ fc: /soft/spack/opt/spack/linux-x86_64/gcc-4.4.7/nag-6.1-jt3h5hwt5myezgqguhfsan52zcskqene/bin/nagfor
+ flags:
+ fflags: -mismatch
+ spec: nag@6.1
+
+---------------
+System Packages
+---------------
+
+Once compilers are configured, one needs to determine which
+pre-installed system packages, if any, to use in builds. This is
+configured in the file ``~/.spack/packages.yaml``. For example, to use
+an OpenMPI installed in /opt/local, one would use:
+
+.. code-block:: yaml
+
+ packages:
+ openmpi:
+ paths:
+ openmpi@1.10.1: /opt/local
+ buildable: False
+
+In general, Spack is easier to use and more reliable if it builds all of
+its own dependencies. However, there are two packages for which one
+commonly needs to use system versions:
+
+^^^
+MPI
+^^^
+
+On supercomputers, sysadmins have already built MPI versions that take
+into account the specifics of that computer's hardware. Unless you
+know how they were built and can choose the correct Spack variants,
+you are unlikely to get a working MPI from Spack. Instead, use an
+appropriate pre-installed MPI.
+
+If you choose a pre-installed MPI, you should consider using the
+pre-installed compiler used to build that MPI; see above on
+``compilers.yaml``.
+
+^^^^^^^
+OpenSSL
+^^^^^^^
+
+The ``openssl`` package underlies much of modern security in a modern
+OS; an attacker can easily "pwn" any computer on which they can modify SSL.
+Therefore, any ``openssl`` used on a system should be created in a
+"trusted environment" --- for example, that of the OS vendor.
+
+OpenSSL is also updated by the OS vendor from time to time, in
+response to security problems discovered in the wider community. It
+is in everyone's best interest to use any newly updated versions as
+soon as they come out. Modern Linux installations have standard
+procedures for security updates without user involvement.
+
+Spack running at user-level is not a trusted environment, nor do Spack
+users generally keep up-to-date on the latest security holes in SSL. For
+these reasons, a Spack-installed OpenSSL should likely not be trusted.
+
+As long as the system-provided SSL works, you can use it instead. One
+can check if it works by trying to download an ``https://``. For
+example:
+
+.. code-block:: console
+
+ $ curl -O https://github.com/ImageMagick/ImageMagick/archive/7.0.2-7.tar.gz
+
+The recommended way to tell Spack to use the system-supplied OpenSSL is
+to add the following to ``packages.yaml``. Note that the ``@system``
+"version" means "I don't care what version it is, just use what is
+there." This is reasonable for OpenSSL, which has a stable API.
+
+
+.. code-block:: yaml
+
+ packages:
+ openssl:
+ paths:
+ openssl@system: /usr
+ version: [system]
+ buildable: False
+
+
+^^^^^^^^^^^^^
+BLAS / LAPACK
+^^^^^^^^^^^^^
+
+The recommended way to use system-supplied BLAS / LAPACK packages is
+to add the following to ``packages.yaml``:
+
+.. code-block:: yaml
+
+ packages:
+ netlib-lapack:
+ paths:
+ netlib-lapack@system: /usr
+ version: [system]
+ buildable: False
+ all:
+ providers:
+ blas: [netlib-lapack]
+ lapack: [netlib-lapack]
+
+.. note::
+
+ The ``@system`` "version" means "I don't care what version it is,
+ just use what is there." Above we pretend that the system-provided
+ Blas/Lapack is ``netlib-lapack`` only because it is the only BLAS / LAPACK
+ provider which use standard names for libraries (as opposed to, for example,
+ `libopenblas.so`).
+
+ Although we specify external package in ``/usr``, Spack is smart enough not
+ to add ``/usr/lib`` to RPATHs, where it could cause unrelated system
+ libraries to be used instead of their Spack equivalents. ``usr/bin`` will be
+ present in PATH, however it will have lower precedence compared to paths
+ from other dependencies. This ensures that binaries in Spack dependencies
+ are preferred over system binaries.
+
+^^^
+Git
+^^^
+
+Some Spack packages use ``git`` to download, which might not work on
+some computers. For example, the following error was
+encountered on a Macintosh during ``spack install julia-master``:
+
+.. code-block:: console
+
+ ==> Trying to clone git repository:
+ https://github.com/JuliaLang/julia.git
+ on branch master
+ Cloning into 'julia'...
+ fatal: unable to access 'https://github.com/JuliaLang/julia.git/':
+ SSL certificate problem: unable to get local issuer certificate
+
+This problem is related to OpenSSL, and in some cases might be solved
+by installing a new version of ``git`` and ``openssl``:
+
+#. Run ``spack install git``
+#. Add the output of ``spack module loads git`` to your ``.bahsrc``.
+
+If this doesn't work, it is also possible to disable checking of SSL
+certificates by using:
+
+.. code-block:: console
+
+ $ spack --insecure install
+
+Using ``--insecure`` makes Spack disable SSL checking when fetching
+from websites and from git.
+
+.. warning::
+
+ This workaround should be used ONLY as a last resort! Wihout SSL
+ certificate verification, spack and git will download from sites you
+ wouldn't normally trust. The code you download and run may then be
+ compromised! While this is not a major issue for archives that will
+ be checksummed, it is especially problematic when downloading from
+ name Git branches or tags, which relies entirely on trusting a
+ certificate for security (no verification).
+
+-----------------------
+Utilities Configuration
+-----------------------
+
+Although Spack does not need installation *per se*, it does rely on
+other packages to be available on its host system. If those packages
+are out of date or missing, then Spack will not work. Sometimes, an
+appeal to the system's package manager can fix such problems. If not,
+the solution is have Spack install the required packages, and then
+have Spack use them.
+
+For example, if ``curl`` doesn't work, one could use the following steps
+to provide Spack a working ``curl``:
+
+.. code-block:: console
+
+ $ spack install curl
+ $ spack load curl
+
+or alternately:
+
+.. code-block:: console
+
+ $ spack module loads curl >>~/.bashrc
+
+or if environment modules don't work:
+
+.. code-block:: console
+
+ $ export PATH=`spack location --install-dir curl`/bin:$PATH
+
+
+External commands are used by Spack in two places: within core Spack,
+and in the package recipes. The bootstrapping procedure for these two
+cases is somewhat different, and is treated separately below.
+
+^^^^^^^^^^^^^^^^^^^^
+Core Spack Utilities
+^^^^^^^^^^^^^^^^^^^^
+
+Core Spack uses the following packages, mainly to download and unpack
+source code, and to load generated environment modules: ``curl``,
+``env``, ``git``, ``go``, ``hg``, ``svn``, ``tar``, ``unzip``,
+``patch``, ``environment-modules``.
+
+As long as the user's environment is set up to successfully run these
+programs from outside of Spack, they should work inside of Spack as
+well. They can generally be activated as in the ``curl`` example above;
+or some systems might already have an appropriate hand-built
+environment module that may be loaded. Either way works.
+
+A few notes on specific programs in this list:
+
+""""""""""""""""""""""""""
+cURL, git, Mercurial, etc.
+""""""""""""""""""""""""""
+
+Spack depends on cURL to download tarballs, the format that most
+Spack-installed packages come in. Your system's cURL should always be
+able to download unencrypted ``http://``. However, the cURL on some
+systems has problems with SSL-enabled ``https://`` URLs, due to
+outdated / insecure versions of OpenSSL on those systems. This will
+prevent Spack from installing any software requiring ``https://``
+until a new cURL has been installed, using the technique above.
+
+.. warning::
+
+ remember that if you install ``curl`` via Spack that it may rely on a
+ user-space OpenSSL that is not upgraded regularly. It may fall out of
+ date faster than your system OpenSSL.
+
+Some packages use source code control systems as their download method:
+``git``, ``hg``, ``svn`` and occasionally ``go``. If you had to install
+a new ``curl``, then chances are the system-supplied version of these
+other programs will also not work, because they also rely on OpenSSL.
+Once ``curl`` has been installed, you can similarly install the others.
+
+
+.. _InstallEnvironmentModules:
+
+"""""""""""""""""""
+Environment Modules
+"""""""""""""""""""
+
+In order to use Spack's generated environment modules, you must have
+installed one of *Environment Modules* or *Lmod*. On many Linux
+distributions, this can be installed from the vendor's repository. For
+example: ``yum install environment-modules`` (Fedora/RHEL/CentOS). If
+your Linux distribution does not have Environment Modules, you can get it
+with Spack:
+
+#. Consider using system tcl (as long as your system has Tcl version 8.0 or later):
+
+ #) Identify its location using ``which tclsh``
+ #) Identify its version using ``echo 'puts $tcl_version;exit 0' | tclsh``
+ #) Add to ``~/.spack/packages.yaml`` and modify as appropriate:
+
+ .. code-block:: yaml
+
+ packages:
+ tcl:
+ paths:
+ tcl@8.5: /usr
+ version: [8.5]
+ buildable: False
+
+#. Install with:
+
+ .. code-block:: console
+
+ $ spack install environment-modules
+
+#. Activate with the following script (or apply the updates to your
+ ``.bashrc`` file manually):
+
+ .. code-block:: sh
+
+ TMP=`tempfile`
+ echo >$TMP
+ MODULE_HOME=`spack location --install-dir environment-modules`
+ MODULE_VERSION=`ls -1 $MODULE_HOME/Modules | head -1`
+ ${MODULE_HOME}/Modules/${MODULE_VERSION}/bin/add.modules <$TMP
+ cp .bashrc $TMP
+ echo "MODULE_VERSION=${MODULE_VERSION}" > .bashrc
+ cat $TMP >>.bashrc
+
+This adds to your ``.bashrc`` (or similar) files, enabling Environment
+Modules when you log in. Re-load your .bashrc (or log out and in
+again), and then test that the ``module`` command is found with:
+
+.. code-block:: console
+
+ $ module avail
+
+
+^^^^^^^^^^^^^^^^^
+Package Utilities
+^^^^^^^^^^^^^^^^^
+
+Spack may also encounter bootstrapping problems inside a package's
+``install()`` method. In this case, Spack will normally be running
+inside a *sanitized build environment*. This includes all of the
+package's dependencies, but none of the environment Spack inherited
+from the user: if you load a module or modify ``$PATH`` before
+launching Spack, it will have no effect.
+
+In this case, you will likely need to use the ``--dirty`` flag when
+running ``spack install``, causing Spack to **not** sanitize the build
+environment. You are now responsible for making sure that environment
+does not do strange things to Spack or its installs.
+
+Another way to get Spack to use its own version of something is to add
+that something to a package that needs it. For example:
+
+.. code-block:: python
+
+ depends_on('binutils', type='build')
+
+This is considered best practice for some common build dependencies,
+such as ``autotools`` (if the ``autoreconf`` command is needed) and
+``cmake`` --- ``cmake`` especially, because different packages require
+a different version of CMake.
+
+""""""""
+binutils
+""""""""
+
+.. https://groups.google.com/forum/#!topic/spack/i_7l_kEEveI
+
+Sometimes, strange error messages can happen while building a package.
+For example, ``ld`` might crash. Or one receives a message like:
+
+.. code-block:: console
+
+ ld: final link failed: Nonrepresentable section on output
+
+
+or:
+
+.. code-block:: console
+
+ ld: .../_fftpackmodule.o: unrecognized relocation (0x2a) in section `.text'
+
+These problems are often caused by an outdated ``binutils`` on your
+system. Unlike CMake or Autotools, adding ``depends_on('binutils')`` to
+every package is not considered a best practice because every package
+written in C/C++/Fortran would need it. A potential workaround is to
+load a recent ``binutils`` into your environment and use the ``--dirty``
+flag.
+
+
+.. _cray-support:
+
+-------------
+Spack on Cray
+-------------
+
+Spack differs slightly when used on a Cray system. The architecture spec
+can differentiate between the front-end and back-end processor and operating system.
+For example, on Edison at NERSC, the back-end target processor
+is "Ivy Bridge", so you can specify to use the back-end this way:
+
+.. code-block:: console
+
+ $ spack install zlib target=ivybridge
+
+You can also use the operating system to build against the back-end:
+
+.. code-block:: console
+
+ $ spack install zlib os=CNL10
+
+Notice that the name includes both the operating system name and the major
+version number concatenated together.
+
+Alternatively, if you want to build something for the front-end,
+you can specify the front-end target processor. The processor for a login node
+on Edison is "Sandy bridge" so we specify on the command line like so:
+
+.. code-block:: console
+
+ $ spack install zlib target=sandybridge
+
+And the front-end operating system is:
+
+.. code-block:: console
+
+ $ spack install zlib os=SuSE11
+
+^^^^^^^^^^^^^^^^^^^^^^^
+Cray compiler detection
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Spack can detect compilers using two methods. For the front-end, we treat
+everything the same. The difference lies in back-end compiler detection.
+Back-end compiler detection is made via the Tcl module avail command.
+Once it detects the compiler it writes the appropriate PrgEnv and compiler
+module name to compilers.yaml and sets the paths to each compiler with Cray\'s
+compiler wrapper names (i.e. cc, CC, ftn). During build time, Spack will load
+the correct PrgEnv and compiler module and will call appropriate wrapper.
+
+The compilers.yaml config file will also differ. There is a
+modules section that is filled with the compiler's Programming Environment
+and module name. On other systems, this field is empty []:
+
+.. code-block:: yaml
+
+ - compiler:
+ modules:
+ - PrgEnv-intel
+ - intel/15.0.109
+
+As mentioned earlier, the compiler paths will look different on a Cray system.
+Since most compilers are invoked using cc, CC and ftn, the paths for each
+compiler are replaced with their respective Cray compiler wrapper names:
+
+.. code-block:: yaml
+
+ paths:
+ cc: cc
+ cxx: CC
+ f77: ftn
+ fc: ftn
+
+As opposed to an explicit path to the compiler executable. This allows Spack
+to call the Cray compiler wrappers during build time.
+
+For more on compiler configuration, check out :ref:`compiler-config`.
+
+Spack sets the default Cray link type to dynamic, to better match other
+other platforms. Individual packages can enable static linking (which is the
+default outside of Spack on cray systems) using the ``-static`` flag.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Setting defaults and using Cray modules
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you want to use default compilers for each PrgEnv and also be able
+to load cray external modules, you will need to set up a ``packages.yaml``.
+
+Here's an example of an external configuration for cray modules:
+
+.. code-block:: yaml
+
+ packages:
+ mpi:
+ modules:
+ mpich@7.3.1%gcc@5.2.0 arch=cray_xc-haswell-CNL10: cray-mpich
+ mpich@7.3.1%intel@16.0.0.109 arch=cray_xc-haswell-CNL10: cray-mpich
+
+This tells Spack that for whatever package that depends on mpi, load the
+cray-mpich module into the environment. You can then be able to use whatever
+environment variables, libraries, etc, that are brought into the environment
+via module load.
+
+You can set the default compiler that Spack can use for each compiler type.
+If you want to use the Cray defaults, then set them under ``all:`` in packages.yaml.
+In the compiler field, set the compiler specs in your order of preference.
+Whenever you build with that compiler type, Spack will concretize to that version.
+
+Here is an example of a full packages.yaml used at NERSC
+
+.. code-block:: yaml
+
+ packages:
+ mpi:
+ modules:
+ mpich@7.3.1%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-mpich
+ mpich@7.3.1%intel@16.0.0.109 arch=cray_xc-SuSE11-ivybridge: cray-mpich
+ buildable: False
+ netcdf:
+ modules:
+ netcdf@4.3.3.1%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-netcdf
+ netcdf@4.3.3.1%intel@16.0.0.109 arch=cray_xc-CNL10-ivybridge: cray-netcdf
+ buildable: False
+ hdf5:
+ modules:
+ hdf5@1.8.14%gcc@5.2.0 arch=cray_xc-CNL10-ivybridge: cray-hdf5
+ hdf5@1.8.14%intel@16.0.0.109 arch=cray_xc-CNL10-ivybridge: cray-hdf5
+ buildable: False
+ all:
+ compiler: [gcc@5.2.0, intel@16.0.0.109]
+
+Here we tell spack that whenever we want to build with gcc use version 5.2.0 or
+if we want to build with intel compilers, use version 16.0.0.109. We add a spec
+for each compiler type for each cray modules. This ensures that for each
+compiler on our system we can use that external module.
+
+For more on external packages check out the section :ref:`sec-external-packages`.
diff --git a/lib/spack/docs/index.rst b/lib/spack/docs/index.rst
index 98ed9ff0fe..65fb995d1e 100644
--- a/lib/spack/docs/index.rst
+++ b/lib/spack/docs/index.rst
@@ -3,8 +3,9 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
+===================
Spack Documentation
-=================================
+===================
Spack is a package management tool designed to support multiple
versions and configurations of software on a wide variety of platforms
@@ -27,7 +28,7 @@ Get spack from the `github repository
<https://github.com/llnl/spack>`_ and install your first
package:
-.. code-block:: sh
+.. code-block:: console
$ git clone https://github.com/llnl/spack.git
$ cd spack/bin
@@ -36,23 +37,47 @@ package:
If you're new to spack and want to start using it, see :doc:`getting_started`,
or refer to the full manual below.
-Table of Contents
----------------------
.. toctree::
:maxdepth: 2
+ :caption: Basics
features
getting_started
basic_usage
- packaging_guide
- mirrors
+ workflows
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Reference Manual
+
configuration
- developer_guide
- command_index
+ config_yaml
+ build_settings
+ mirrors
+ module_file_support
package_list
+ command_index
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Step-by-step
+
+ basic_usage_tutorial
+ module_file_tutorial
+ package_tutorial
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Contributing to Spack
+
+ contribution_guide
+ packaging_guide
+ developer_guide
API Docs <spack>
+
+==================
Indices and tables
==================
diff --git a/lib/spack/docs/mirrors.rst b/lib/spack/docs/mirrors.rst
index 583575a565..87ce3d96a2 100644
--- a/lib/spack/docs/mirrors.rst
+++ b/lib/spack/docs/mirrors.rst
@@ -1,7 +1,8 @@
.. _mirrors:
+=======
Mirrors
-============================
+=======
Some sites may not have access to the internet for fetching packages.
These sites will need a local repository of tarballs from which they
@@ -10,27 +11,29 @@ mirror is a URL that points to a directory, either on the local
filesystem or on some server, containing tarballs for all of Spack's
packages.
-Here's an example of a mirror's directory structure::
-
- mirror/
- cmake/
- cmake-2.8.10.2.tar.gz
- dyninst/
- dyninst-8.1.1.tgz
- dyninst-8.1.2.tgz
- libdwarf/
- libdwarf-20130126.tar.gz
- libdwarf-20130207.tar.gz
- libdwarf-20130729.tar.gz
- libelf/
- libelf-0.8.12.tar.gz
- libelf-0.8.13.tar.gz
- libunwind/
- libunwind-1.1.tar.gz
- mpich/
- mpich-3.0.4.tar.gz
- mvapich2/
- mvapich2-1.9.tgz
+Here's an example of a mirror's directory structure:
+
+.. code-block:: none
+
+ mirror/
+ cmake/
+ cmake-2.8.10.2.tar.gz
+ dyninst/
+ dyninst-8.1.1.tgz
+ dyninst-8.1.2.tgz
+ libdwarf/
+ libdwarf-20130126.tar.gz
+ libdwarf-20130207.tar.gz
+ libdwarf-20130729.tar.gz
+ libelf/
+ libelf-0.8.12.tar.gz
+ libelf-0.8.13.tar.gz
+ libunwind/
+ libunwind-1.1.tar.gz
+ mpich/
+ mpich-3.0.4.tar.gz
+ mvapich2/
+ mvapich2-1.9.tgz
The structure is very simple. There is a top-level directory. The
second level directories are named after packages, and the third level
@@ -49,27 +52,16 @@ contains tarballs for each package, named after each package.
not standardize on a particular compression algorithm, because this
would potentially require expanding and re-compressing each archive.
-.. _spack-mirror:
+.. _cmd-spack-mirror:
+----------------
``spack mirror``
-----------------------------
+----------------
Mirrors are managed with the ``spack mirror`` command. The help for
-``spack mirror`` looks like this::
+``spack mirror`` looks like this:
- $ spack mirror -h
- usage: spack mirror [-h] SUBCOMMAND ...
-
- positional arguments:
- SUBCOMMAND
- create Create a directory to be used as a spack mirror, and fill
- it with package archives.
- add Add a mirror to Spack.
- remove Remove a mirror by name.
- list Print out available mirrors to the console.
-
- optional arguments:
- -h, --help show this help message and exit
+.. command-output:: spack help mirror
The ``create`` command actually builds a mirror by fetching all of its
packages from the internet and checksumming them.
@@ -79,8 +71,9 @@ control the URL(s) from which Spack downloads its packages.
.. _spack-mirror-create:
+-----------------------
``spack mirror create``
-----------------------------
+-----------------------
You can create a mirror using the ``spack mirror create`` command, assuming
you're on a machine where you can access the internet.
@@ -89,8 +82,7 @@ The command will iterate through all of Spack's packages and download
the safe ones into a directory structure like the one above. Here is
what it looks like:
-
-.. code-block:: bash
+.. code-block:: console
$ spack mirror create libelf libdwarf
==> Created new mirror in spack-mirror-2014-06-24
@@ -124,25 +116,31 @@ what it looks like:
Once this is done, you can tar up the ``spack-mirror-2014-06-24`` directory and
copy it over to the machine you want it hosted on.
+^^^^^^^^^^^^^^^^^^^
Custom package sets
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
Normally, ``spack mirror create`` downloads all the archives it has
checksums for. If you want to only create a mirror for a subset of
packages, you can do that by supplying a list of package specs on the
command line after ``spack mirror create``. For example, this
-command::
+command:
- $ spack mirror create libelf@0.8.12: boost@1.44:
+.. code-block:: console
+
+ $ spack mirror create libelf@0.8.12: boost@1.44:
Will create a mirror for libelf versions greater than or equal to
0.8.12 and boost versions greater than or equal to 1.44.
+^^^^^^^^^^^^
Mirror files
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^
If you have a *very* large number of packages you want to mirror, you
-can supply a file with specs in it, one per line::
+can supply a file with specs in it, one per line:
+
+.. code-block:: console
$ cat specs.txt
libdwarf
@@ -150,7 +148,7 @@ can supply a file with specs in it, one per line::
boost@1.44:
boost@1.39.0
...
- $ spack mirror create -f specs.txt
+ $ spack mirror create --file specs.txt
...
This is useful if there is a specific suite of software managed by
@@ -158,24 +156,27 @@ your site.
.. _spack-mirror-add:
+--------------------
``spack mirror add``
-----------------------------
+--------------------
Once you have a mirror, you need to let spack know about it. This is
relatively simple. First, figure out the URL for the mirror. If it's
-a file, you can use a file URL like this one::
+a file, you can use a file URL like this one:
- file:///Users/gamblin2/spack-mirror-2014-06-24
+.. code-block:: none
+
+ file:///Users/gamblin2/spack-mirror-2014-06-24
That points to the directory on the local filesystem. If it were on a
web server, you could use a URL like this one:
- https://example.com/some/web-hosted/directory/spack-mirror-2014-06-24
+https://example.com/some/web-hosted/directory/spack-mirror-2014-06-24
Spack will use the URL as the root for all of the packages it fetches.
You can tell your Spack installation to use that mirror like this:
-.. code-block:: bash
+.. code-block:: console
$ spack mirror add local_filesystem file:///Users/gamblin2/spack-mirror-2014-06-24
@@ -183,29 +184,38 @@ Each mirror has a name so that you can refer to it again later.
.. _spack-mirror-list:
+---------------------
``spack mirror list``
-----------------------------
+---------------------
+
+To see all the mirrors Spack knows about, run ``spack mirror list``:
-To see all the mirrors Spack knows about, run ``spack mirror list``::
+.. code-block:: console
$ spack mirror list
local_filesystem file:///Users/gamblin2/spack-mirror-2014-06-24
.. _spack-mirror-remove:
+-----------------------
``spack mirror remove``
-----------------------------
+-----------------------
-To remove a mirror by name::
+To remove a mirror by name, run:
+
+.. code-block:: console
$ spack mirror remove local_filesystem
$ spack mirror list
==> No mirrors configured.
+-----------------
Mirror precedence
-----------------------------
+-----------------
+
+Adding a mirror really adds a line in ``~/.spack/mirrors.yaml``:
-Adding a mirror really adds a line in ``~/.spack/mirrors.yaml``::
+.. code-block:: yaml
mirrors:
local_filesystem: file:///Users/gamblin2/spack-mirror-2014-06-24
@@ -217,18 +227,19 @@ search the topmost mirror first and the bottom-most mirror last.
.. _caching:
+-------------------
Local Default Cache
-----------------------------
+-------------------
Spack caches resources that are downloaded as part of installs. The cache is
a valid spack mirror: it uses the same directory structure and naming scheme
as other Spack mirrors (so it can be copied anywhere and referenced with a URL
-like other mirrors). The mirror is maintained locally (within the Spack
-installation directory) at :file:`var/spack/cache/`. It is always enabled (and
-is always searched first when attempting to retrieve files for an installation)
-but can be cleared with :ref:`purge <spack-purge>`; the cache directory can also
-be deleted manually without issue.
+like other mirrors). The mirror is maintained locally (within the Spack
+installation directory) at :file:`var/spack/cache/`. It is always enabled (and
+is always searched first when attempting to retrieve files for an installation)
+but can be cleared with :ref:`purge <cmd-spack-purge>`; the cache directory can also
+be deleted manually without issue.
Caching includes retrieved tarball archives and source control repositories, but
-only resources with an associated digest or commit ID (e.g. a revision number
+only resources with an associated digest or commit ID (e.g. a revision number
for SVN) will be cached.
diff --git a/lib/spack/docs/module_file_support.rst b/lib/spack/docs/module_file_support.rst
new file mode 100644
index 0000000000..f07bd31c2e
--- /dev/null
+++ b/lib/spack/docs/module_file_support.rst
@@ -0,0 +1,682 @@
+.. _modules:
+
+=======
+Modules
+=======
+
+The use of module systems to manage user environment in a controlled way
+is a common practice at HPC centers that is often embraced also by individual
+programmers on their development machines. To support this common practice
+Spack provides integration with `Environment Modules
+<http://modules.sourceforge.net/>`_ , `LMod
+<http://lmod.readthedocs.io/en/latest/>`_ and `Dotkit <https://computing.llnl.gov/?set=jobs&page=dotkit>`_ by:
+
+* generating module files after a successful installation
+* providing commands that can leverage the spec syntax to manipulate modules
+
+In the following you will see how to activate shell support for commands in Spack
+that requires it, and discover what benefits this may bring with respect to deal
+directly with automatically generated module files.
+
+.. note::
+
+ If your machine does not already have a module system installed,
+ we advise you to use either Environment Modules or LMod. See :ref:`InstallEnvironmentModules`
+ for more details.
+
+.. _shell_support:
+
+-------------
+Shell support
+-------------
+
+You can enable shell support by sourcing the appropriate setup file
+in the ``$SPACK_ROOT/share/spack`` directory.
+For ``bash`` or ``ksh`` users:
+
+.. code-block:: console
+
+ $ . ${SPACK_ROOT}/share/spack/setup-env.sh
+
+For ``csh`` and ``tcsh`` instead:
+
+.. code-block:: console
+
+ $ source $SPACK_ROOT/share/spack/setup-env.csh
+
+
+.. note::
+ You can put the source line in your ``.bashrc`` or ``.cshrc`` to
+ have Spack's shell support available on the command line at any login.
+
+
+----------------------------
+Using module files via Spack
+----------------------------
+
+If you have shell support enabled you should be able to run either
+``module avail`` or ``use -l spack`` to see what module/dotkit files have
+been installed. Here is sample output of those programs, showing lots
+of installed packages.
+
+.. code-block:: console
+
+ $ module avail
+
+ ------- /home/gamblin2/spack/share/spack/modules/linux-debian7-x86_64 --------
+ adept-utils@1.0%gcc@4.4.7-5adef8da libelf@0.8.13%gcc@4.4.7
+ automaded@1.0%gcc@4.4.7-d9691bb0 libelf@0.8.13%intel@15.0.0
+ boost@1.55.0%gcc@4.4.7 mpc@1.0.2%gcc@4.4.7-559607f5
+ callpath@1.0.1%gcc@4.4.7-5dce4318 mpfr@3.1.2%gcc@4.4.7
+ dyninst@8.1.2%gcc@4.4.7-b040c20e mpich@3.0.4%gcc@4.4.7
+ gcc@4.9.1%gcc@4.4.7-93ab98c5 mpich@3.0.4%gcc@4.9.0
+ gmp@6.0.0a%gcc@4.4.7 mrnet@4.1.0%gcc@4.4.7-72b7881d
+ graphlib@2.0.0%gcc@4.4.7 netgauge@2.4.6%gcc@4.9.0-27912b7b
+ launchmon@1.0.1%gcc@4.4.7 stat@2.1.0%gcc@4.4.7-51101207
+ libNBC@1.1.1%gcc@4.9.0-27912b7b sundials@2.5.0%gcc@4.9.0-27912b7b
+ libdwarf@20130729%gcc@4.4.7-b52fac98
+
+.. code-block:: console
+
+ $ use -l spack
+
+ spack ----------
+ adept-utils@1.0%gcc@4.4.7-5adef8da - adept-utils @1.0
+ automaded@1.0%gcc@4.4.7-d9691bb0 - automaded @1.0
+ boost@1.55.0%gcc@4.4.7 - boost @1.55.0
+ callpath@1.0.1%gcc@4.4.7-5dce4318 - callpath @1.0.1
+ dyninst@8.1.2%gcc@4.4.7-b040c20e - dyninst @8.1.2
+ gmp@6.0.0a%gcc@4.4.7 - gmp @6.0.0a
+ libNBC@1.1.1%gcc@4.9.0-27912b7b - libNBC @1.1.1
+ libdwarf@20130729%gcc@4.4.7-b52fac98 - libdwarf @20130729
+ libelf@0.8.13%gcc@4.4.7 - libelf @0.8.13
+ libelf@0.8.13%intel@15.0.0 - libelf @0.8.13
+ mpc@1.0.2%gcc@4.4.7-559607f5 - mpc @1.0.2
+ mpfr@3.1.2%gcc@4.4.7 - mpfr @3.1.2
+ mpich@3.0.4%gcc@4.4.7 - mpich @3.0.4
+ mpich@3.0.4%gcc@4.9.0 - mpich @3.0.4
+ netgauge@2.4.6%gcc@4.9.0-27912b7b - netgauge @2.4.6
+ sundials@2.5.0%gcc@4.9.0-27912b7b - sundials @2.5.0
+
+The names here should look familiar, they're the same ones from
+``spack find``. You *can* use the names here directly. For example,
+you could type either of these commands to load the callpath module:
+
+.. code-block:: console
+
+ $ use callpath@1.0.1%gcc@4.4.7-5dce4318
+
+.. code-block:: console
+
+ $ module load callpath@1.0.1%gcc@4.4.7-5dce4318
+
+.. _cmd-spack-load:
+
+^^^^^^^^^^^^^^^^^^^^^^^
+``spack load / unload``
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Neither of these is particularly pretty, easy to remember, or
+easy to type. Luckily, Spack has its own interface for using modules
+and dotkits. You can use the same spec syntax you're used to:
+
+========================= ==========================
+Environment Modules Dotkit
+========================= ==========================
+``spack load <spec>`` ``spack use <spec>``
+``spack unload <spec>`` ``spack unuse <spec>``
+========================= ==========================
+
+And you can use the same shortened names you use everywhere else in
+Spack. For example, this will add the ``mpich`` package built with
+``gcc`` to your path:
+
+.. code-block:: console
+
+ $ spack install mpich %gcc@4.4.7
+
+ # ... wait for install ...
+
+ $ spack use mpich %gcc@4.4.7
+ Prepending: mpich@3.0.4%gcc@4.4.7 (ok)
+ $ which mpicc
+ ~/src/spack/opt/linux-debian7-x86_64/gcc@4.4.7/mpich@3.0.4/bin/mpicc
+
+Or, similarly with modules, you could type:
+
+.. code-block:: console
+
+ $ spack load mpich %gcc@4.4.7
+
+These commands will add appropriate directories to your ``PATH``,
+``MANPATH``, ``CPATH``, and ``LD_LIBRARY_PATH``. When you no longer
+want to use a package, you can type unload or unuse similarly:
+
+.. code-block:: console
+
+ $ spack unload mpich %gcc@4.4.7 # modules
+ $ spack unuse mpich %gcc@4.4.7 # dotkit
+
+.. note::
+
+ These ``use``, ``unuse``, ``load``, and ``unload`` subcommands are
+ only available if you have enabled Spack's shell support *and* you
+ have dotkit or modules installed on your machine.
+
+^^^^^^^^^^^^^^^^^^^^^^
+Ambiguous module names
+^^^^^^^^^^^^^^^^^^^^^^
+
+If a spec used with load/unload or use/unuse is ambiguous (i.e. more
+than one installed package matches it), then Spack will warn you:
+
+.. code-block:: console
+
+ $ spack load libelf
+ ==> Error: Multiple matches for spec libelf. Choose one:
+ libelf@0.8.13%gcc@4.4.7 arch=linux-debian7-x86_64
+ libelf@0.8.13%intel@15.0.0 arch=linux-debian7-x86_64
+
+You can either type the ``spack load`` command again with a fully
+qualified argument, or you can add just enough extra constraints to
+identify one package. For example, above, the key differentiator is
+that one ``libelf`` is built with the Intel compiler, while the other
+used ``gcc``. You could therefore just type:
+
+.. code-block:: console
+
+ $ spack load libelf %intel
+
+To identify just the one built with the Intel compiler.
+
+.. _extensions:
+
+.. _cmd-spack-module-loads:
+
+^^^^^^^^^^^^^^^^^^^^^^
+``spack module loads``
+^^^^^^^^^^^^^^^^^^^^^^
+
+In some cases, it is desirable to load not just a module, but also all
+the modules it depends on. This is not required for most modules
+because Spack builds binaries with RPATH support. However, not all
+packages use RPATH to find their dependencies: this can be true in
+particular for Python extensions, which are currently *not* built with
+RPATH.
+
+Scripts to load modules recursively may be made with the command:
+
+.. code-block:: console
+
+ $ spack module loads --dependencies <spec>
+
+An equivalent alternative is:
+
+.. code-block :: console
+
+ $ source <( spack module loads --dependencies <spec> )
+
+.. warning::
+
+ The ``spack load`` command does not currently accept the
+ ``--dependencies`` flag. Use ``spack module loads`` instead, for
+ now.
+
+.. See #1662
+
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Module Commands for Shell Scripts
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Although Spack is flexible, the ``module`` command is much faster.
+This could become an issue when emitting a series of ``spack load``
+commands inside a shell script. By adding the ``--shell`` flag,
+``spack module find`` may also be used to generate code that can be
+cut-and-pasted into a shell script. For example:
+
+.. code-block:: console
+
+ $ spack module loads --dependencies py-numpy git
+ # bzip2@1.0.6%gcc@4.9.3=linux-x86_64
+ module load bzip2-1.0.6-gcc-4.9.3-ktnrhkrmbbtlvnagfatrarzjojmkvzsx
+ # ncurses@6.0%gcc@4.9.3=linux-x86_64
+ module load ncurses-6.0-gcc-4.9.3-kaazyneh3bjkfnalunchyqtygoe2mncv
+ # zlib@1.2.8%gcc@4.9.3=linux-x86_64
+ module load zlib-1.2.8-gcc-4.9.3-v3ufwaahjnviyvgjcelo36nywx2ufj7z
+ # sqlite@3.8.5%gcc@4.9.3=linux-x86_64
+ module load sqlite-3.8.5-gcc-4.9.3-a3eediswgd5f3rmto7g3szoew5nhehbr
+ # readline@6.3%gcc@4.9.3=linux-x86_64
+ module load readline-6.3-gcc-4.9.3-se6r3lsycrwxyhreg4lqirp6xixxejh3
+ # python@3.5.1%gcc@4.9.3=linux-x86_64
+ module load python-3.5.1-gcc-4.9.3-5q5rsrtjld4u6jiicuvtnx52m7tfhegi
+ # py-setuptools@20.5%gcc@4.9.3=linux-x86_64
+ module load py-setuptools-20.5-gcc-4.9.3-4qr2suj6p6glepnedmwhl4f62x64wxw2
+ # py-nose@1.3.7%gcc@4.9.3=linux-x86_64
+ module load py-nose-1.3.7-gcc-4.9.3-pwhtjw2dvdvfzjwuuztkzr7b4l6zepli
+ # openblas@0.2.17%gcc@4.9.3+shared=linux-x86_64
+ module load openblas-0.2.17-gcc-4.9.3-pw6rmlom7apfsnjtzfttyayzc7nx5e7y
+ # py-numpy@1.11.0%gcc@4.9.3+blas+lapack=linux-x86_64
+ module load py-numpy-1.11.0-gcc-4.9.3-mulodttw5pcyjufva4htsktwty4qd52r
+ # curl@7.47.1%gcc@4.9.3=linux-x86_64
+ module load curl-7.47.1-gcc-4.9.3-ohz3fwsepm3b462p5lnaquv7op7naqbi
+ # autoconf@2.69%gcc@4.9.3=linux-x86_64
+ module load autoconf-2.69-gcc-4.9.3-bkibjqhgqm5e3o423ogfv2y3o6h2uoq4
+ # cmake@3.5.0%gcc@4.9.3~doc+ncurses+openssl~qt=linux-x86_64
+ module load cmake-3.5.0-gcc-4.9.3-x7xnsklmgwla3ubfgzppamtbqk5rwn7t
+ # expat@2.1.0%gcc@4.9.3=linux-x86_64
+ module load expat-2.1.0-gcc-4.9.3-6pkz2ucnk2e62imwakejjvbv6egncppd
+ # git@2.8.0-rc2%gcc@4.9.3+curl+expat=linux-x86_64
+ module load git-2.8.0-rc2-gcc-4.9.3-3bib4hqtnv5xjjoq5ugt3inblt4xrgkd
+
+The script may be further edited by removing unnecessary modules.
+
+
+^^^^^^^^^^^^^^^
+Module Prefixes
+^^^^^^^^^^^^^^^
+
+On some systems, modules are automatically prefixed with a certain
+string; ``spack module loads`` needs to know about that prefix when it
+issues ``module load`` commands. Add the ``--prefix`` option to your
+``spack module loads`` commands if this is necessary.
+
+For example, consider the following on one system:
+
+.. code-block:: console
+
+ $ module avail
+ linux-SuSE11-x86_64/antlr-2.7.7-gcc-5.3.0-bdpl46y
+
+ $ spack module loads antlr # WRONG!
+ # antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
+ module load antlr-2.7.7-gcc-5.3.0-bdpl46y
+
+ $ spack module loads --prefix linux-SuSE11-x86_64/ antlr
+ # antlr@2.7.7%gcc@5.3.0~csharp+cxx~java~python arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/antlr-2.7.7-gcc-5.3.0-bdpl46y
+
+----------------------------
+Auto-generating Module Files
+----------------------------
+
+Module files are generated by post-install hooks after the successful
+installation of a package. The following table summarizes the essential
+information associated with the different file formats
+that can be generated by Spack:
+
+ +-----------------------------+--------------------+-------------------------------+----------------------+
+ | | **Hook name** | **Default root directory** | **Compatible tools** |
+ +=============================+====================+===============================+======================+
+ | **Dotkit** | ``dotkit`` | share/spack/dotkit | DotKit |
+ +-----------------------------+--------------------+-------------------------------+----------------------+
+ | **TCL - Non-Hierarchical** | ``tcl`` | share/spack/modules | Env. Modules/LMod |
+ +-----------------------------+--------------------+-------------------------------+----------------------+
+ | **Lua - Hierarchical** | ``lmod`` | share/spack/lmod | LMod |
+ +-----------------------------+--------------------+-------------------------------+----------------------+
+
+
+Though Spack ships with sensible defaults for the generation of module files,
+one can customize many aspects of it to accommodate package or site specific needs.
+These customizations are enabled by either:
+
+ 1. overriding certain callback APIs in the Python packages
+ 2. writing specific rules in the ``modules.yaml`` configuration file
+
+The former method fits best cases that are site independent, e.g. injecting variables
+from language interpreters into their extensions. The latter instead permits to
+fine tune the content, naming and creation of module files to meet site specific conventions.
+
+^^^^^^^^^^^^^^^^^^^^
+``Package`` file API
+^^^^^^^^^^^^^^^^^^^^
+
+There are two methods that can be overridden in any ``package.py`` to affect the
+content of generated module files. The first one is:
+
+.. code-block:: python
+
+ def setup_environment(self, spack_env, run_env):
+ """Set up the compile and runtime environments for a package."""
+ pass
+
+and can alter the content of *the same package where it is overridden*
+by adding actions to ``run_env``. The second method is:
+
+.. code-block:: python
+
+ def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
+ """Set up the environment of packages that depend on this one"""
+ pass
+
+and has similar effects on module file of dependees. Even in this case
+``run_env`` must be filled with the desired list of environment modifications.
+
+.. note::
+ The ``R`` package and callback APIs
+ A typical example in which overriding both methods prove to be useful
+ is given by the ``R`` package. This package installs libraries and headers
+ in non-standard locations and it is possible to prepend the appropriate directory
+ to the corresponding environment variables:
+
+ ================== =================================
+ LIBRARY_PATH ``self.prefix/rlib/R/lib``
+ LD_LIBRARY_PATH ``self.prefix/rlib/R/lib``
+ CPATH ``self.prefix/rlib/R/include``
+ ================== =================================
+
+ with the following snippet:
+
+ .. literalinclude:: ../../../var/spack/repos/builtin/packages/R/package.py
+ :pyobject: R.setup_environment
+
+ The ``R`` package also knows which environment variable should be modified
+ to make language extensions provided by other packages available, and modifies
+ it appropriately in the override of the second method:
+
+ .. literalinclude:: ../../../var/spack/repos/builtin/packages/R/package.py
+ :lines: 128-129,146-151
+
+.. _modules-yaml:
+
+---------------------------------
+Configuration in ``modules.yaml``
+---------------------------------
+
+The name of the configuration file that controls module generation behavior
+is ``modules.yaml``. The default configuration:
+
+.. literalinclude:: ../../../etc/spack/defaults/modules.yaml
+ :language: yaml
+
+activates generation for ``tcl`` and ``dotkit`` module files and inspects
+the installation folder of each package for the presence of a set of subdirectories
+(``bin``, ``man``, ``share/man``, etc.). If any is found its full path is prepended
+to the environment variables listed below the folder name.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Activation of other systems
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Any other module file generator shipped with Spack can be activated adding it to the
+list under the ``enable`` key in the module file. Currently the only generator that
+is not activated by default is ``lmod``, which produces hierarchical lua module files.
+For each module system that can be enabled a finer configuration is possible.
+
+Directives that are aimed at driving the generation of a particular type of module files
+should be listed under a top level key that corresponds to the generator being
+customized:
+
+.. code-block:: yaml
+
+ modules:
+ enable:
+ - tcl
+ - dotkit
+ - lmod
+ tcl:
+ # contains environment modules specific customizations
+ dotkit:
+ # contains dotkit specific customizations
+ lmod:
+ # contains lmod specific customizations
+
+All these module sections allow for both:
+
+1. global directives that usually affect the whole layout of modules or the naming scheme
+2. directives that affect only a set of packages and modify their content
+
+For the latter point in particular it is possible to use anonymous specs
+to select an appropriate set of packages on which the modifications should be applied.
+
+.. _anonymous_specs:
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Selection by anonymous specs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The procedure to select packages using anonymous specs is a natural
+extension of using them to install packages, the only difference being
+that specs in this case **are not required to have a root package**.
+Consider for instance this snippet:
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ # The keyword `all` selects every package
+ all:
+ environment:
+ set:
+ BAR: 'bar'
+ # This anonymous spec selects any package that
+ # depends on openmpi. The double colon at the
+ # end clears the set of rules that matched so far.
+ ^openmpi::
+ environment:
+ set:
+ BAR: 'baz'
+ # Selects any zlib package
+ zlib:
+ environment:
+ prepend_path:
+ LD_LIBRARY_PATH: 'foo'
+ # Selects zlib compiled with gcc@4.8
+ zlib%gcc@4.8:
+ environment:
+ unset:
+ - FOOBAR
+
+During module file generation, the configuration above will instruct
+Spack to set the environment variable ``BAR=bar`` for every module,
+unless the associated spec satisfies ``^openmpi`` in which case ``BAR=baz``.
+In addition in any spec that satisfies ``zlib`` the value ``foo`` will be
+prepended to ``LD_LIBRARY_PATH`` and in any spec that satisfies ``zlib%gcc@4.8``
+the variable ``FOOBAR`` will be unset.
+
+.. note::
+ Order does matter
+ The modifications associated with the ``all`` keyword are always evaluated
+ first, no matter where they appear in the configuration file. All the other
+ spec constraints are instead evaluated top to bottom.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Blacklist or whitelist the generation of specific module files
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Anonymous specs are also used to prevent module files from being written or
+to force them to be written. A common case for that at HPC centers is to hide
+from users all of the software that needs to be built with system compilers.
+Suppose for instance to have ``gcc@4.4.7`` provided by your system. Then
+with a configuration file like this one:
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ whitelist: ['gcc', 'llvm'] # Whitelist will have precedence over blacklist
+ blacklist: ['%gcc@4.4.7'] # Assuming gcc@4.4.7 is the system compiler
+
+you will skip the generation of module files for any package that
+is compiled with ``gcc@4.4.7``, with the exception of any ``gcc``
+or any ``llvm`` installation.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Customize the naming scheme
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The names of environment modules generated by spack are not always easy to
+fully comprehend due to the long hash in the name. There are two module
+configuration options to help with that. The first is a global setting to
+adjust the hash length. It can be set anywhere from 0 to 32 and has a default
+length of 7. This is the representation of the hash in the module file name and
+does not affect the size of the package hash. Be aware that the smaller the
+hash length the more likely naming conflicts will occur. The following snippet
+shows how to set hash length in the module file names:
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ hash_length: 7
+
+To help make module names more readable, and to help alleviate name conflicts
+with a short hash, one can use the ``suffixes`` option in the modules
+configuration file. This option will add strings to modules that match a spec.
+For instance, the following config options,
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ all:
+ suffixes:
+ ^python@2.7.12: 'python-2.7.12'
+ ^openblas: 'openblas'
+
+will add a ``python-2.7.12`` version string to any packages compiled with
+python matching the spec, ``python@2.7.12``. This is useful to know which
+version of python a set of python extensions is associated with. Likewise, the
+``openblas`` string is attached to any program that has openblas in the spec,
+most likely via the ``+blas`` variant specification.
+
+.. note::
+ TCL module files
+ A modification that is specific to ``tcl`` module files is the possibility
+ to change the naming scheme of modules.
+
+ .. code-block:: yaml
+
+ modules:
+ tcl:
+ naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
+ all:
+ conflict: ['${PACKAGE}', 'intel/14.0.1']
+
+ will create module files that will conflict with ``intel/14.0.1`` and with the
+ base directory of the same module, effectively preventing the possibility to
+ load two or more versions of the same software at the same time. The tokens
+ that are available for use in this directive are the same understood by
+ the ``Spec.format`` method.
+
+
+.. note::
+ LMod hierarchical module files
+ When ``lmod`` is activated Spack will generate a set of hierarchical lua module
+ files that are understood by LMod. The generated hierarchy always contains the
+ three layers ``Core`` / ``Compiler`` / ``MPI`` but can be further extended to
+ any other virtual dependency present in Spack. A case that could be useful in
+ practice is for instance:
+
+ .. code-block:: yaml
+
+ modules:
+ enable:
+ - lmod
+ lmod:
+ core_compilers: ['gcc@4.8']
+ hierarchical_scheme: ['lapack']
+
+ that will generate a hierarchy in which the ``lapack`` layer is treated as the ``mpi``
+ one. This allows a site to build the same libraries or applications against different
+ implementations of ``mpi`` and ``lapack``, and let LMod switch safely from one to the
+ other.
+
+.. warning::
+ Deep hierarchies and ``lmod spider``
+ For hierarchies that are deeper than three layers ``lmod spider`` may have some issues.
+ See `this discussion on the LMod project <https://github.com/TACC/Lmod/issues/114>`_.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Filter out environment modifications
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Modifications to certain environment variables in module files are generated by
+default, for instance by prefix inspections in the default configuration file.
+There are cases though where some of these modifications are unwanted.
+Suppose you need to avoid having ``CPATH`` and ``LIBRARY_PATH``
+modified by your ``dotkit`` modules:
+
+.. code-block:: yaml
+
+ modules:
+ dotkit:
+ all:
+ filter:
+ # Exclude changes to any of these variables
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+
+The configuration above will generate dotkit module files that will not contain
+modifications to either ``CPATH`` or ``LIBRARY_PATH`` and environment module
+files that instead will contain these modifications.
+
+^^^^^^^^^^^^^^^^^^^^^
+Autoload dependencies
+^^^^^^^^^^^^^^^^^^^^^
+
+In some cases it can be useful to have module files directly autoload
+their dependencies. This may be the case for Python extensions, if not
+activated using ``spack activate``:
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ ^python:
+ autoload: 'direct'
+
+The configuration file above will produce module files that will
+automatically load their direct dependencies. The allowed values for the
+``autoload`` statement are either ``none``, ``direct`` or ``all``.
+
+.. note::
+ TCL prerequisites
+ In the ``tcl`` section of the configuration file it is possible to use
+ the ``prerequisites`` directive that accepts the same values as
+ ``autoload``. It will produce module files that have a ``prereq``
+ statement instead of automatically loading other modules.
+
+------------------------
+Maintaining Module Files
+------------------------
+
+Spack not only provides great flexibility in the generation of module files
+and in the customization of both their layout and content, but also ships with
+a tool to ease the burden of their maintenance in production environments.
+This tool is the ``spack module`` command:
+
+.. command-output:: spack module --help
+
+.. _cmd-spack-module-refresh:
+
+^^^^^^^^^^^^^^^^^^^^^^^^
+``spack module refresh``
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+The command that regenerates module files to update their content or
+their layout is ``module refresh``:
+
+.. command-output:: spack module refresh --help
+
+A set of packages can be selected using anonymous specs for the optional
+``constraint`` positional argument. The argument ``--module-type`` identifies
+the type of module files to refresh. Optionally the entire tree can be deleted
+before regeneration if the change in layout is radical.
+
+.. _cmd-spack-module-rm:
+
+^^^^^^^^^^^^^^^^^^^
+``spack module rm``
+^^^^^^^^^^^^^^^^^^^
+
+If instead what you need is just to delete a few module files, then the right
+command is ``module rm``:
+
+.. command-output:: spack module rm --help
+
+.. note::
+ We care about your module files!
+ Every modification done on modules
+ that are already existing will ask for a confirmation by default. If
+ the command is used in a script it is possible though to pass the
+ ``-y`` argument, that will skip this safety measure.
diff --git a/lib/spack/docs/module_file_tutorial.rst b/lib/spack/docs/module_file_tutorial.rst
new file mode 100644
index 0000000000..83df756566
--- /dev/null
+++ b/lib/spack/docs/module_file_tutorial.rst
@@ -0,0 +1,982 @@
+.. _module_tutorial:
+
+=============================
+Module Configuration Tutorial
+=============================
+
+This tutorial will guide you through the customization of both
+content and naming of module files generated by Spack.
+
+Starting from the default Spack settings you will add an increasing
+number of directives to the ``modules.yaml`` configuration file to
+satisfy a number of constraints that mimic those that you may encounter
+in a typical production environment at HPC sites.
+
+Even though the focus will be for the most part on customizing
+TCL non-hierarchical module files, everything
+you'll see applies also to other kinds of module files generated by Spack.
+
+The generation of Lua hierarchical
+module files will be addressed at the end of the tutorial,
+and you'll see that with minor modifications
+to an existing ``modules.yaml`` written for TCL
+non-hierarchical modules you'll get almost
+for free the possibility to try a hierarchical layout.
+
+Let's start!
+
+.. _module_file_tutorial_prerequisites:
+
+-------------
+Prerequisites
+-------------
+
+Before proceeding further ensure:
+
+- you have LMod or Environment Modules available
+- have :ref:`shell support <shell_support>` activated in Spack
+
+If you need to install Lmod or Environment module you can refer
+to the documentation :ref:`here <InstallEnvironmentModules>`.
+
+
+^^^^^^^^^^^^^^^^^^
+Add a new compiler
+^^^^^^^^^^^^^^^^^^
+
+Spack automatically scans the environment to search for available
+compilers on first use. On a Ubuntu 14.04 a fresh clone will show
+something like this:
+
+.. code-block:: console
+
+ $ uname -a
+ Linux nuvolari 4.4.0-45-generic #66~14.04.1-Ubuntu SMP Wed Oct 19 15:05:38 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
+
+ $ spack compilers
+ ==> Available compilers
+ -- gcc ----------------------------------------------------------
+ gcc@4.8
+
+For the purpose of building a limited set of packages with some features
+that will help showcasing the capabilities of
+module customization the first thing we need is to build a new compiler:
+
+.. code-block:: console
+
+ $ spack install gcc@6.2.0
+ # ...
+ # Wait a long time
+ # ...
+
+Then we can use shell support for modules to add it to the list of known compilers:
+
+.. code-block:: console
+
+ # The name of the generated module may vary
+ $ module load gcc-6.2.0-gcc-4.8-twd5nqg
+
+ $ spack compiler add
+ ==> Added 1 new compiler to /home/mculpo/.spack/linux/compilers.yaml
+ gcc@6.2.0
+
+ $ spack compilers
+ ==> Available compilers
+ -- gcc ----------------------------------------------------------
+ gcc@6.2.0 gcc@4.8
+
+Note that the final 7 digits hash at the end of the generated module may vary depending
+on architecture or package version.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Build software that will be used in the tutorial
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Next you should install a few modules that will be used in the tutorial:
+
+.. code-block:: console
+
+ $ spack install netlib-scalapack ^openmpi ^openblas
+ # ...
+
+The packages you need to install are:
+
+- ``netlib-scalapack ^openmpi ^openblas``
+- ``netlib-scalapack ^mpich ^openblas``
+- ``netlib-scalapack ^openmpi ^netlib-lapack``
+- ``netlib-scalapack ^mpich ^netlib-lapack``
+- ``py-scipy ^openblas``
+
+In the end your environment should look something like:
+
+.. code-block:: console
+
+ $ module avail
+
+ ------------------------------------------------------------------------ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64 ------------------------------------------------------------------------
+ binutils-2.27-gcc-4.8-dz3xevw libpciaccess-0.13.4-gcc-6.2.0-eo2siet lzo-2.09-gcc-6.2.0-jcngz72 netlib-scalapack-2.0.2-gcc-6.2.0-wnimqhw python-2.7.12-gcc-6.2.0-qu7rc5p
+ bzip2-1.0.6-gcc-6.2.0-csoc2mq libsigsegv-2.10-gcc-4.8-avb6azw m4-1.4.17-gcc-4.8-iggewke netlib-scalapack-2.0.2-gcc-6.2.0-wojunhq sqlite-3.8.5-gcc-6.2.0-td3zfe7
+ cmake-3.5.2-gcc-6.2.0-6poypqg libsigsegv-2.10-gcc-6.2.0-g3qpmbi m4-1.4.17-gcc-6.2.0-lhgqa6s nettle-3.2-gcc-6.2.0-djdthlh tcl-8.6.5-gcc-4.8-atddxu7
+ curl-7.50.3-gcc-6.2.0-2ffacqm libtool-2.4.6-gcc-6.2.0-kiepac6 mpc-1.0.3-gcc-4.8-lylv7lk openblas-0.2.19-gcc-6.2.0-js33umc util-macros-1.19.0-gcc-6.2.0-uoukuqk
+ expat-2.2.0-gcc-6.2.0-bxqnjar libxml2-2.9.4-gcc-6.2.0-3k4ykbe mpfr-3.1.4-gcc-4.8-bldfx3w openmpi-2.0.1-gcc-6.2.0-s3qbtby xz-5.2.2-gcc-6.2.0-t5lk6in
+ gcc-6.2.0-gcc-4.8-twd5nqg lmod-6.4.5-gcc-4.8-7v7bh7b mpich-3.2-gcc-6.2.0-5n5xoep openssl-1.0.2j-gcc-6.2.0-hibnfda zlib-1.2.8-gcc-4.8-bds4ies
+ gmp-6.1.1-gcc-4.8-uq52e2n lua-5.3.2-gcc-4.8-xozf2hx ncurses-6.0-gcc-4.8-u62fit4 pkg-config-0.29.1-gcc-6.2.0-rslsgcs zlib-1.2.8-gcc-6.2.0-asydrba
+ gmp-6.1.1-gcc-6.2.0-3cfh3hi lua-luafilesystem-1_6_3-gcc-4.8-sbzejlz ncurses-6.0-gcc-6.2.0-7tb426s py-nose-1.3.7-gcc-6.2.0-4gl5c42
+ hwloc-1.11.4-gcc-6.2.0-3ostwel lua-luaposix-33.4.0-gcc-4.8-xf7y2p5 netlib-lapack-3.6.1-gcc-6.2.0-mirer2l py-numpy-1.11.1-gcc-6.2.0-i3rpk4e
+ isl-0.14-gcc-4.8-cq73t5m lz4-131-gcc-6.2.0-cagoem4 netlib-scalapack-2.0.2-gcc-6.2.0-6bqlxqy py-scipy-0.18.1-gcc-6.2.0-e6uljfi
+ libarchive-3.2.1-gcc-6.2.0-2b54aos lzma-4.32.7-gcc-6.2.0-sfmeynw netlib-scalapack-2.0.2-gcc-6.2.0-hpqb3dp py-setuptools-25.2.0-gcc-6.2.0-hkqauaa
+
+------------------------------------------------
+Filter unwanted modifications to the environment
+------------------------------------------------
+
+The non-hierarchical TCL module files that have been generated so far
+follow the default rules for module generation, which are given
+:ref:`here <modules-yaml>` in the reference part of the manual. Taking a
+look at the ``gcc`` module you'll see something like:
+
+.. code-block:: console
+
+ $ module show gcc-6.2.0-gcc-4.8-twd5nqg
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/gcc-6.2.0-gcc-4.8-twd5nqg:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ whatis("gcc @6.2.0 ")
+ prepend_path("PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/bin")
+ prepend_path("CMAKE_PREFIX_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/")
+ prepend_path("MANPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/share/man")
+ prepend_path("PKG_CONFIG_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64/pkgconfig")
+ prepend_path("LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64")
+ prepend_path("LD_LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64")
+ prepend_path("CPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/include")
+ help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
+ Fortran, and Java.
+ ]])
+
+As expected, a few environment variables representing paths will be modified
+by the modules according to the default prefix inspection rules.
+
+Consider now the case that your site has decided that e.g. ``CPATH`` and
+``LIBRARY_PATH`` modifications should not be present in module files. What you can
+do to abide by the rules is to create a configuration file ``~/.spack/modules.yaml``
+with the following content:
+
+.. code-block:: yaml
+
+ modules:
+ tcl:
+ all:
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+
+Next you should regenerate all the module files:
+
+.. code-block:: console
+
+ $ spack module refresh --module-type tcl
+ ==> You are about to regenerate tcl module files for:
+
+ -- linux-Ubuntu14-x86_64 / gcc@4.8 ------------------------------
+ dz3xevw binutils@2.27 uq52e2n gmp@6.1.1 avb6azw libsigsegv@2.10 xozf2hx lua@5.3.2 xf7y2p5 lua-luaposix@33.4.0 lylv7lk mpc@1.0.3 u62fit4 ncurses@6.0 bds4ies zlib@1.2.8
+ twd5nqg gcc@6.2.0 cq73t5m isl@0.14 7v7bh7b lmod@6.4.5 sbzejlz lua-luafilesystem@1_6_3 iggewke m4@1.4.17 bldfx3w mpfr@3.1.4 atddxu7 tcl@8.6.5
+
+ ...
+
+ ==> Do you want to proceed ? [y/n]
+ y
+ ==> Regenerating tcl module files
+
+If you take a look now at the module for ``gcc`` you'll see that the unwanted
+paths have disappeared:
+
+.. code-block:: console
+
+ $ module show gcc-6.2.0-gcc-4.8-twd5nqg
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/gcc-6.2.0-gcc-4.8-twd5nqg:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ whatis("gcc @6.2.0 ")
+ prepend_path("PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/bin")
+ prepend_path("CMAKE_PREFIX_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/")
+ prepend_path("MANPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/share/man")
+ prepend_path("PKG_CONFIG_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64/pkgconfig")
+ prepend_path("LD_LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64")
+ help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
+ Fortran, and Java.
+ ]])
+
+----------------------------------------------
+Prevent some module files from being generated
+----------------------------------------------
+
+Another common request at many sites is to avoid exposing software that
+is only needed as an intermediate step when building a newer stack.
+Let's try to prevent the generation of
+module files for anything that is compiled with ``gcc@4.8`` (the OS provided compiler).
+
+To do this you should add a ``blacklist`` keyword to the configuration file:
+
+.. code-block:: yaml
+ :emphasize-lines: 3,4
+
+ modules:
+ tcl:
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+
+and regenerate the module files:
+
+.. code-block:: console
+
+ $ spack module refresh --module-type tcl --delete-tree
+ ==> You are about to regenerate tcl module files for:
+
+ -- linux-Ubuntu14-x86_64 / gcc@4.8 ------------------------------
+ dz3xevw binutils@2.27 uq52e2n gmp@6.1.1 avb6azw libsigsegv@2.10 xozf2hx lua@5.3.2 xf7y2p5 lua-luaposix@33.4.0 lylv7lk mpc@1.0.3 u62fit4 ncurses@6.0 bds4ies zlib@1.2.8
+ twd5nqg gcc@6.2.0 cq73t5m isl@0.14 7v7bh7b lmod@6.4.5 sbzejlz lua-luafilesystem@1_6_3 iggewke m4@1.4.17 bldfx3w mpfr@3.1.4 atddxu7 tcl@8.6.5
+
+ -- linux-Ubuntu14-x86_64 / gcc@6.2.0 ----------------------------
+ csoc2mq bzip2@1.0.6 2b54aos libarchive@3.2.1 sfmeynw lzma@4.32.7 wnimqhw netlib-scalapack@2.0.2 s3qbtby openmpi@2.0.1 hkqauaa py-setuptools@25.2.0
+ 6poypqg cmake@3.5.2 eo2siet libpciaccess@0.13.4 jcngz72 lzo@2.09 6bqlxqy netlib-scalapack@2.0.2 hibnfda openssl@1.0.2j qu7rc5p python@2.7.12
+ 2ffacqm curl@7.50.3 g3qpmbi libsigsegv@2.10 lhgqa6s m4@1.4.17 wojunhq netlib-scalapack@2.0.2 rslsgcs pkg-config@0.29.1 td3zfe7 sqlite@3.8.5
+ bxqnjar expat@2.2.0 kiepac6 libtool@2.4.6 5n5xoep mpich@3.2 hpqb3dp netlib-scalapack@2.0.2 4gl5c42 py-nose@1.3.7 uoukuqk util-macros@1.19.0
+ 3cfh3hi gmp@6.1.1 3k4ykbe libxml2@2.9.4 7tb426s ncurses@6.0 djdthlh nettle@3.2 i3rpk4e py-numpy@1.11.1 t5lk6in xz@5.2.2
+ 3ostwel hwloc@1.11.4 cagoem4 lz4@131 mirer2l netlib-lapack@3.6.1 js33umc openblas@0.2.19 e6uljfi py-scipy@0.18.1 asydrba zlib@1.2.8
+
+ ==> Do you want to proceed ? [y/n]
+ y
+
+ $ module avail
+
+ ------------------------------------------------------------------------ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64 ------------------------------------------------------------------------
+ bzip2-1.0.6-gcc-6.2.0-csoc2mq libsigsegv-2.10-gcc-6.2.0-g3qpmbi ncurses-6.0-gcc-6.2.0-7tb426s openmpi-2.0.1-gcc-6.2.0-s3qbtby sqlite-3.8.5-gcc-6.2.0-td3zfe7
+ cmake-3.5.2-gcc-6.2.0-6poypqg libtool-2.4.6-gcc-6.2.0-kiepac6 netlib-lapack-3.6.1-gcc-6.2.0-mirer2l openssl-1.0.2j-gcc-6.2.0-hibnfda util-macros-1.19.0-gcc-6.2.0-uoukuqk
+ curl-7.50.3-gcc-6.2.0-2ffacqm libxml2-2.9.4-gcc-6.2.0-3k4ykbe netlib-scalapack-2.0.2-gcc-6.2.0-6bqlxqy pkg-config-0.29.1-gcc-6.2.0-rslsgcs xz-5.2.2-gcc-6.2.0-t5lk6in
+ expat-2.2.0-gcc-6.2.0-bxqnjar lz4-131-gcc-6.2.0-cagoem4 netlib-scalapack-2.0.2-gcc-6.2.0-hpqb3dp py-nose-1.3.7-gcc-6.2.0-4gl5c42 zlib-1.2.8-gcc-6.2.0-asydrba
+ gmp-6.1.1-gcc-6.2.0-3cfh3hi lzma-4.32.7-gcc-6.2.0-sfmeynw netlib-scalapack-2.0.2-gcc-6.2.0-wnimqhw py-numpy-1.11.1-gcc-6.2.0-i3rpk4e
+ hwloc-1.11.4-gcc-6.2.0-3ostwel lzo-2.09-gcc-6.2.0-jcngz72 netlib-scalapack-2.0.2-gcc-6.2.0-wojunhq py-scipy-0.18.1-gcc-6.2.0-e6uljfi
+ libarchive-3.2.1-gcc-6.2.0-2b54aos m4-1.4.17-gcc-6.2.0-lhgqa6s nettle-3.2-gcc-6.2.0-djdthlh py-setuptools-25.2.0-gcc-6.2.0-hkqauaa
+ libpciaccess-0.13.4-gcc-6.2.0-eo2siet mpich-3.2-gcc-6.2.0-5n5xoep openblas-0.2.19-gcc-6.2.0-js33umc python-2.7.12-gcc-6.2.0-qu7rc5p
+
+This time it is convenient to pass the option ``--delete-tree`` to the command that
+regenerates the module files to instruct it to delete the existing tree and regenerate
+a new one instead of overwriting the files in the existing directory.
+
+If you pay careful attention you'll see though that we went too far in blacklisting modules:
+the module for ``gcc@6.2.0`` disappeared as it was bootstrapped with ``gcc@4.8``. To specify
+exceptions to the blacklist rules you can use ``whitelist``:
+
+.. code-block:: yaml
+ :emphasize-lines: 3,4
+
+ modules:
+ tcl:
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+
+``whitelist`` rules always have precedence over ``blacklist`` rules. If you regenerate the modules again:
+
+.. code-block:: console
+
+ $ spack module refresh --module-type tcl -y
+
+you'll see that now the module for ``gcc@6.2.0`` has reappeared:
+
+.. code-block:: console
+
+ $ module avail gcc-6.2.0-gcc-4.8-twd5nqg
+
+ ------------------------------------------------------------------------ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64 ------------------------------------------------------------------------
+ gcc-6.2.0-gcc-4.8-twd5nqg
+
+-------------------------
+Change module file naming
+-------------------------
+
+The next step in making module files more user-friendly is to
+improve their naming scheme.
+To reduce the length of the hash or remove it altogether you can
+use the ``hash_length`` keyword in the configuration file:
+
+.. TODO: give reasons to remove hashes if they are not evident enough?
+
+.. code-block:: yaml
+ :emphasize-lines: 3
+
+ modules:
+ tcl:
+ hash_length: 0
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+
+If you try to regenerate the module files now you will get an error:
+
+.. code-block:: console
+
+ $ spack module refresh --module-type tcl --delete-tree -y
+ ==> Error: Name clashes detected in module files:
+
+ file : /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/netlib-scalapack-2.0.2-gcc-6.2.0
+ spec : netlib-scalapack@2.0.2%gcc@6.2.0~fpic+shared arch=linux-Ubuntu14-x86_64
+ spec : netlib-scalapack@2.0.2%gcc@6.2.0~fpic+shared arch=linux-Ubuntu14-x86_64
+ spec : netlib-scalapack@2.0.2%gcc@6.2.0~fpic+shared arch=linux-Ubuntu14-x86_64
+ spec : netlib-scalapack@2.0.2%gcc@6.2.0~fpic+shared arch=linux-Ubuntu14-x86_64
+
+ ==> Error: Operation aborted
+
+.. note::
+ We try to check for errors upfront!
+ In Spack we check for errors upfront whenever possible, so don't worry about your module files:
+ as a name clash was detected nothing has been changed on disk.
+
+The problem here is that without
+the hashes the four different flavors of ``netlib-scalapack`` map to the same module file
+name. We have the possibility to add suffixes to differentiate them:
+
+.. code-block:: yaml
+ :emphasize-lines: 9-11,14-17
+
+ modules:
+ tcl:
+ hash_length: 0
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ netlib-scalapack:
+ suffixes:
+ '^openmpi': openmpi
+ '^mpich': mpich
+
+As you can see it is possible to specify rules that applies only to a
+restricted set of packages using :ref:`anonymous specs <anonymous_specs>`.
+Regenerating module files now we obtain:
+
+.. code-block:: console
+
+ $ spack module refresh --module-type tcl --delete-tree -y
+ ==> Regenerating tcl module files
+ $ module avail
+
+ ------------------------------------------------------------------------ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64 ------------------------------------------------------------------------
+ bzip2-1.0.6-gcc-6.2.0 libpciaccess-0.13.4-gcc-6.2.0 mpich-3.2-gcc-6.2.0 openblas-0.2.19-gcc-6.2.0 python-2.7.12-gcc-6.2.0
+ cmake-3.5.2-gcc-6.2.0 libsigsegv-2.10-gcc-6.2.0 ncurses-6.0-gcc-6.2.0 openmpi-2.0.1-gcc-6.2.0 sqlite-3.8.5-gcc-6.2.0
+ curl-7.50.3-gcc-6.2.0 libtool-2.4.6-gcc-6.2.0 netlib-lapack-3.6.1-gcc-6.2.0 openssl-1.0.2j-gcc-6.2.0 util-macros-1.19.0-gcc-6.2.0
+ expat-2.2.0-gcc-6.2.0 libxml2-2.9.4-gcc-6.2.0 netlib-scalapack-2.0.2-gcc-6.2.0-netlib-mpich pkg-config-0.29.1-gcc-6.2.0 xz-5.2.2-gcc-6.2.0
+ gcc-6.2.0-gcc-4.8 lz4-131-gcc-6.2.0 netlib-scalapack-2.0.2-gcc-6.2.0-netlib-openmpi py-nose-1.3.7-gcc-6.2.0 zlib-1.2.8-gcc-6.2.0
+ gmp-6.1.1-gcc-6.2.0 lzma-4.32.7-gcc-6.2.0 netlib-scalapack-2.0.2-gcc-6.2.0-openblas-mpich py-numpy-1.11.1-gcc-6.2.0-openblas
+ hwloc-1.11.4-gcc-6.2.0 lzo-2.09-gcc-6.2.0 netlib-scalapack-2.0.2-gcc-6.2.0-openblas-openmpi py-scipy-0.18.1-gcc-6.2.0-openblas
+ libarchive-3.2.1-gcc-6.2.0 m4-1.4.17-gcc-6.2.0 nettle-3.2-gcc-6.2.0 py-setuptools-25.2.0-gcc-6.2.0
+
+Finally we can set a ``naming_scheme`` to prevent users from loading
+modules that refer to different flavors of the same library/application:
+
+.. code-block:: yaml
+ :emphasize-lines: 4,10,11
+
+ modules:
+ tcl:
+ hash_length: 0
+ naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ conflict:
+ - '${PACKAGE}'
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ netlib-scalapack:
+ suffixes:
+ '^openmpi': openmpi
+ '^mpich': mpich
+
+The final result should look like:
+
+.. code-block:: console
+
+ $ module avail
+
+ ------------------------------------------------------------------------ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64 ------------------------------------------------------------------------
+ bzip2/1.0.6-gcc-6.2.0 libpciaccess/0.13.4-gcc-6.2.0 mpich/3.2-gcc-6.2.0 openblas/0.2.19-gcc-6.2.0 python/2.7.12-gcc-6.2.0
+ cmake/3.5.2-gcc-6.2.0 libsigsegv/2.10-gcc-6.2.0 ncurses/6.0-gcc-6.2.0 openmpi/2.0.1-gcc-6.2.0 sqlite/3.8.5-gcc-6.2.0
+ curl/7.50.3-gcc-6.2.0 libtool/2.4.6-gcc-6.2.0 netlib-lapack/3.6.1-gcc-6.2.0 openssl/1.0.2j-gcc-6.2.0 util-macros/1.19.0-gcc-6.2.0
+ expat/2.2.0-gcc-6.2.0 libxml2/2.9.4-gcc-6.2.0 netlib-scalapack/2.0.2-gcc-6.2.0-netlib-mpich pkg-config/0.29.1-gcc-6.2.0 xz/5.2.2-gcc-6.2.0
+ gcc/6.2.0-gcc-4.8 lz4/131-gcc-6.2.0 netlib-scalapack/2.0.2-gcc-6.2.0-netlib-openmpi py-nose/1.3.7-gcc-6.2.0 zlib/1.2.8-gcc-6.2.0
+ gmp/6.1.1-gcc-6.2.0 lzma/4.32.7-gcc-6.2.0 netlib-scalapack/2.0.2-gcc-6.2.0-openblas-mpich py-numpy/1.11.1-gcc-6.2.0-openblas
+ hwloc/1.11.4-gcc-6.2.0 lzo/2.09-gcc-6.2.0 netlib-scalapack/2.0.2-gcc-6.2.0-openblas-openmpi (D) py-scipy/0.18.1-gcc-6.2.0-openblas
+ libarchive/3.2.1-gcc-6.2.0 m4/1.4.17-gcc-6.2.0 nettle/3.2-gcc-6.2.0 py-setuptools/25.2.0-gcc-6.2.0
+
+.. note::
+ TCL specific directive
+ The directives ``naming_scheme`` and ``conflict`` are TCL specific and do not apply
+ to the ``dotkit`` or ``lmod`` sections in the configuration file.
+
+------------------------------------
+Add custom environment modifications
+------------------------------------
+
+At many sites it is customary to set an environment variable in a
+package's module file that points to the folder in which the package
+is installed. You can achieve this with Spack by adding an
+``environment`` directive to the configuration file:
+
+.. code-block:: yaml
+ :emphasize-lines: 17-19
+
+ modules:
+ tcl:
+ hash_length: 0
+ naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ conflict:
+ - '${PACKAGE}'
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ environment:
+ set:
+ '${PACKAGE}_ROOT': '${PREFIX}'
+ netlib-scalapack:
+ suffixes:
+ '^openmpi': openmpi
+ '^mpich': mpich
+
+There are many variable tokens available to use in the ``environment``
+and ``naming_scheme`` directives, such as ``${PACKAGE}``,
+``${VERSION}``, etc. (see the :meth:`~spack.spec.Spec.format` API
+documentation for the complete list).
+
+Regenerating the module files should result in something like:
+
+.. code-block:: console
+ :emphasize-lines: 14
+
+ $ spack module refresh -y --module-type tcl
+ ==> Regenerating tcl module files
+
+ $ module show gcc
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/gcc/6.2.0-gcc-4.8:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ whatis("gcc @6.2.0 ")
+ prepend_path("PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/bin")
+ prepend_path("CMAKE_PREFIX_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/")
+ prepend_path("MANPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/share/man")
+ prepend_path("PKG_CONFIG_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64/pkgconfig")
+ prepend_path("LD_LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64")
+ setenv("GCC_ROOT","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u")
+ conflict("gcc")
+ help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
+ Fortran, and Java.
+ ]])
+
+As you see the ``gcc`` module has the environment variable ``GCC_ROOT`` set.
+
+Sometimes it's also useful to apply environment modifications selectively and target
+only certain packages. You can, for instance set the common variables ``CC``, ``CXX``,
+etc. in the ``gcc`` module file and apply other custom modifications to the
+``openmpi`` modules as follows:
+
+.. code-block:: yaml
+ :emphasize-lines: 20-32
+
+ modules:
+ tcl:
+ hash_length: 0
+ naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ conflict:
+ - '${PACKAGE}'
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ environment:
+ set:
+ '${PACKAGE}_ROOT': '${PREFIX}'
+ gcc:
+ environment:
+ set:
+ CC: gcc
+ CXX: g++
+ FC: gfortran
+ F90: gfortran
+ F77: gfortran
+ openmpi:
+ environment:
+ set:
+ SLURM_MPI_TYPE: pmi2
+ OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
+ netlib-scalapack:
+ suffixes:
+ '^openmpi': openmpi
+ '^mpich': mpich
+
+This time we will be more selective and regenerate only the ``gcc`` and
+``openmpi`` module files:
+
+.. code-block:: console
+
+ $ spack module refresh -y --module-type tcl gcc
+ ==> Regenerating tcl module files
+
+ $ spack module refresh -y --module-type tcl openmpi
+ ==> Regenerating tcl module files
+
+ $ module show gcc
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/gcc/6.2.0-gcc-4.8:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ whatis("gcc @6.2.0 ")
+ prepend_path("PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/bin")
+ prepend_path("CMAKE_PREFIX_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/")
+ prepend_path("MANPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/share/man")
+ prepend_path("PKG_CONFIG_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64/pkgconfig")
+ prepend_path("LD_LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u/lib64")
+ setenv("GCC_ROOT","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-4.8/gcc-6.2.0-twd5nqg33hrrssqclcfi5k42eccwxz5u")
+ setenv("CC","gcc")
+ setenv("CXX","g++")
+ setenv("F90","gfortran")
+ setenv("FC","gfortran")
+ setenv("F77","gfortran")
+ conflict("gcc")
+ help([[The GNU Compiler Collection includes front ends for C, C++, Objective-C,
+ Fortran, and Java.
+ ]])
+
+ $ module show openmpi
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64/openmpi/2.0.1-gcc-6.2.0:
+ ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ whatis("openmpi @2.0.1 ")
+ prepend_path("PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w/bin")
+ prepend_path("CMAKE_PREFIX_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w/")
+ prepend_path("LD_LIBRARY_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w/lib")
+ prepend_path("PKG_CONFIG_PATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w/lib/pkgconfig")
+ prepend_path("MANPATH","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w/share/man")
+ setenv("SLURM_MPI_TYPE","pmi2")
+ setenv("OMPI_MCA_BTL_OPENIB_WARN_DEFAULT_GID_PREFIX","0")
+ setenv("OPENMPI_ROOT","/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/openmpi-2.0.1-s3qbtbyh3y5y4gkchmhcuak7th44l53w")
+ conflict("openmpi")
+ help([[The Open MPI Project is an open source Message Passing Interface
+ implementation that is developed and maintained by a consortium of
+ academic, research, and industry partners. Open MPI is therefore able to
+ combine the expertise, technologies, and resources from all across the
+ High Performance Computing community in order to build the best MPI
+ library available. Open MPI offers advantages for system and software
+ vendors, application developers and computer science researchers.
+ ]])
+
+
+---------------------
+Autoload dependencies
+---------------------
+
+Spack can also generate module files that contain code to load the
+dependencies automatically. You can, for instance generate python
+modules that load their dependencies by adding the ``autoload``
+directive and assigning it the value ``direct``:
+
+.. code-block:: yaml
+ :emphasize-lines: 37,38
+
+ modules:
+ tcl:
+ hash_length: 0
+ naming_scheme: '${PACKAGE}/${VERSION}-${COMPILERNAME}-${COMPILERVER}'
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ conflict:
+ - '${PACKAGE}'
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ environment:
+ set:
+ '${PACKAGE}_ROOT': '${PREFIX}'
+ gcc:
+ environment:
+ set:
+ CC: gcc
+ CXX: g++
+ FC: gfortran
+ F90: gfortran
+ F77: gfortran
+ openmpi:
+ environment:
+ set:
+ SLURM_MPI_TYPE: pmi2
+ OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
+ netlib-scalapack:
+ suffixes:
+ '^openmpi': openmpi
+ '^mpich': mpich
+ ^python:
+ autoload: 'direct'
+
+and regenerating the module files for every package that depends on ``python``:
+
+.. code-block:: console
+
+ $ spack module refresh -y --module-type tcl ^python
+ ==> Regenerating tcl module files
+
+Now the ``py-scipy`` module will be:
+
+.. code-block:: tcl
+
+ #%Module1.0
+ ## Module file created by spack (https://github.com/LLNL/spack) on 2016-11-02 20:53:21.283547
+ ##
+ ## py-scipy@0.18.1%gcc@6.2.0 arch=linux-Ubuntu14-x86_64-e6uljfi
+ ##
+ module-whatis "py-scipy @0.18.1"
+
+ proc ModulesHelp { } {
+ puts stderr "SciPy (pronounced "Sigh Pie") is a Scientific Library for Python. It"
+ puts stderr "provides many user-friendly and efficient numerical routines such as"
+ puts stderr "routines for numerical integration and optimization."
+ }
+
+ if ![ is-loaded python/2.7.12-gcc-6.2.0 ] {
+ puts stderr "Autoloading python/2.7.12-gcc-6.2.0"
+ module load python/2.7.12-gcc-6.2.0
+ }
+
+ if ![ is-loaded openblas/0.2.19-gcc-6.2.0 ] {
+ puts stderr "Autoloading openblas/0.2.19-gcc-6.2.0"
+ module load openblas/0.2.19-gcc-6.2.0
+ }
+
+ if ![ is-loaded py-numpy/1.11.1-gcc-6.2.0-openblas ] {
+ puts stderr "Autoloading py-numpy/1.11.1-gcc-6.2.0-openblas"
+ module load py-numpy/1.11.1-gcc-6.2.0-openblas
+ }
+
+ prepend-path CMAKE_PREFIX_PATH "/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/py-scipy-0.18.1-e6uljfiffgym4xvj6wveevqxfqnfb3gh/"
+ prepend-path LD_LIBRARY_PATH "/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/py-scipy-0.18.1-e6uljfiffgym4xvj6wveevqxfqnfb3gh/lib"
+ prepend-path PYTHONPATH "/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/py-scipy-0.18.1-e6uljfiffgym4xvj6wveevqxfqnfb3gh/lib/python2.7/site-packages"
+ setenv PY_SCIPY_ROOT "/home/mculpo/wdir/spack/opt/spack/linux-Ubuntu14-x86_64/gcc-6.2.0/py-scipy-0.18.1-e6uljfiffgym4xvj6wveevqxfqnfb3gh"
+ conflict py-scipy
+
+and will contain code to autoload all the dependencies:
+
+.. code-block:: console
+
+ $ module load py-scipy
+ Autoloading python/2.7.12-gcc-6.2.0
+ Autoloading openblas/0.2.19-gcc-6.2.0
+ Autoloading py-numpy/1.11.1-gcc-6.2.0-openblas
+
+-----------------------------
+Lua hierarchical module files
+-----------------------------
+
+In the final part of this tutorial you will modify ``modules.yaml`` to generate
+Lua hierarchical module files. You will see that most of the directives used before
+are also valid in the ``lmod`` context.
+
+^^^^^^^^^^^^^^^^^
+Core/Compiler/MPI
+^^^^^^^^^^^^^^^^^
+
+.. warning::
+ Only LMod supports Lua hierarchical module files
+ For this part of the tutorial you need to be using LMod to
+ manage your environment.
+
+The most common hierarchy is the so called ``Core/Compiler/MPI``. To have an idea
+how a hierarchy is organized you may refer to the
+`Lmod guide <https://www.tacc.utexas.edu/research-development/tacc-projects/lmod/user-guide/module-hierarchy>`_.
+Since ``lmod`` is not enabled by default, you need to add it to the list of
+enabled module file generators. The other things you need to do are:
+
+- change the ``tcl`` tag to ``lmod``
+- remove ``tcl`` specific directives (``naming_scheme`` and ``conflict``)
+- set which compilers are considered ``core``
+- remove the ``mpi`` related suffixes (as they will be substituted by hierarchies)
+
+After modifications the configuration file will be:
+
+.. code-block:: yaml
+ :emphasize-lines: 2-6
+
+ modules:
+ enable::
+ - lmod
+ lmod:
+ core_compilers:
+ - 'gcc@4.8'
+ hash_length: 0
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ all:
+ suffixes:
+ '^openblas': openblas
+ '^netlib-lapack': netlib
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ environment:
+ set:
+ '${PACKAGE}_ROOT': '${PREFIX}'
+ gcc:
+ environment:
+ set:
+ CC: gcc
+ CXX: g++
+ FC: gfortran
+ F90: gfortran
+ F77: gfortran
+ openmpi:
+ environment:
+ set:
+ SLURM_MPI_TYPE: pmi2
+ OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
+
+
+.. note::
+ The double colon
+ The double colon after ``enable`` is intentional and it serves the
+ purpose of overriding the default list of enabled generators so
+ that only ``lmod`` will be active (see :ref:`the reference
+ manual <config-overrides>` for a more detailed explanation of
+ config scopes).
+
+The directive ``core_compilers`` accepts a list of compilers : everything built
+using these compilers will create a module in the ``Core`` part of the hierarchy. It is
+common practice to put the OS provided compilers in the list and only build common utilities
+and other compilers in ``Core``.
+
+If you regenerate the module files
+
+.. code-block:: console
+
+ $ spack module refresh --module-type lmod --delete-tree -y
+
+and update ``MODULEPATH`` to point to the ``Core`` folder, and
+list the available modules, you'll see:
+
+.. code-block:: console
+
+ $ module unuse /home/mculpo/wdir/spack/share/spack/modules/linux-Ubuntu14-x86_64
+ $ module use /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/Core
+ $ module avail
+
+ ----------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/Core -----------------------------------------------------------------------
+ gcc/6.2.0
+
+The only module visible now is ``gcc``. Loading that you will make
+visible the ``Compiler`` part of the software stack that was built with ``gcc/6.2.0``:
+
+.. code-block:: console
+
+ $ module load gcc
+ $ module avail
+
+ -------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/gcc/6.2.0 ---------------------------------------------------------------------
+ binutils/2.27 curl/7.50.3 hwloc/1.11.4 libtool/2.4.6 lzo/2.09 netlib-lapack/3.6.1 openssl/1.0.2j py-scipy/0.18.1-openblas util-macros/1.19.0
+ bison/3.0.4 expat/2.2.0 libarchive/3.2.1 libxml2/2.9.4 m4/1.4.17 nettle/3.2 pkg-config/0.29.1 py-setuptools/25.2.0 xz/5.2.2
+ bzip2/1.0.6 flex/2.6.0 libpciaccess/0.13.4 lz4/131 mpich/3.2 openblas/0.2.19 py-nose/1.3.7 python/2.7.12 zlib/1.2.8
+ cmake/3.6.1 gmp/6.1.1 libsigsegv/2.10 lzma/4.32.7 ncurses/6.0 openmpi/2.0.1 py-numpy/1.11.1-openblas sqlite/3.8.5
+
+ ----------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/Core -----------------------------------------------------------------------
+ gcc/6.2.0 (L)
+
+The same holds true for the ``MPI`` part of the stack, that you can enable by loading
+either ``mpich`` or ``openmpi``. The nice features of LMod will become evident
+once you'll try switching among different stacks:
+
+.. code-block:: console
+
+ $ module load mpich
+ $ module avail
+
+ ----------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/mpich/3.2-5n5xoep/gcc/6.2.0 ------------------------------------------------------------
+ netlib-scalapack/2.0.2-netlib netlib-scalapack/2.0.2-openblas (D)
+
+ -------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/gcc/6.2.0 ---------------------------------------------------------------------
+ binutils/2.27 curl/7.50.3 hwloc/1.11.4 libtool/2.4.6 lzo/2.09 netlib-lapack/3.6.1 openssl/1.0.2j py-scipy/0.18.1-openblas util-macros/1.19.0
+ bison/3.0.4 expat/2.2.0 libarchive/3.2.1 libxml2/2.9.4 m4/1.4.17 nettle/3.2 pkg-config/0.29.1 py-setuptools/25.2.0 xz/5.2.2
+ bzip2/1.0.6 flex/2.6.0 libpciaccess/0.13.4 lz4/131 mpich/3.2 (L) openblas/0.2.19 py-nose/1.3.7 python/2.7.12 zlib/1.2.8
+ cmake/3.6.1 gmp/6.1.1 libsigsegv/2.10 lzma/4.32.7 ncurses/6.0 openmpi/2.0.1 py-numpy/1.11.1-openblas sqlite/3.8.5
+
+ ----------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/Core -----------------------------------------------------------------------
+ gcc/6.2.0 (L)
+
+ $ module load openblas netlib-scalapack/2.0.2-openblas
+ $ module list
+
+ Currently Loaded Modules:
+ 1) gcc/6.2.0 2) mpich/3.2 3) openblas/0.2.19 4) netlib-scalapack/2.0.2-openblas
+
+ $ module load openmpi
+
+ Lmod is automatically replacing "mpich/3.2" with "openmpi/2.0.1"
+
+
+ Due to MODULEPATH changes the following have been reloaded:
+ 1) netlib-scalapack/2.0.2-openblas
+
+This layout is already a great improvement over the usual non-hierarchical layout,
+but it still has an asymmetry: ``LAPACK`` providers are semantically the same as ``MPI``
+providers, but they are still not part of the hierarchy. We'll see a possible solution
+next.
+
+.. Activate lmod and turn the previous modifications into lmod:
+ Add core compilers
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Extend the hierarchy to other virtual providers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. warning::
+ This is an experimental feature
+ Having a hierarchy deeper than ``Core``/``Compiler``/``MPI`` is an experimental
+ feature, still not fully supported by ``module spider``,
+ see `here <https://github.com/TACC/Lmod/issues/114>`_. Furthermore its use
+ with hierarchies more complex than ``Core``/``Compiler``/``MPI``/``LAPACK``
+ has not been thoroughly tested in production environments.
+
+Spack permits you to generate Lua hierarchical module files where users
+can add an arbitrary list of virtual providers to the triplet
+``Core``/``Compiler``/``MPI``. A configuration file like:
+
+.. code-block:: yaml
+ :emphasize-lines: 7,8
+
+ modules:
+ enable::
+ - lmod
+ lmod:
+ core_compilers:
+ - 'gcc@4.8'
+ hierarchical_scheme:
+ - lapack
+ hash_length: 0
+ whitelist:
+ - gcc
+ blacklist:
+ - '%gcc@4.8'
+ - readline
+ all:
+ filter:
+ environment_blacklist: ['CPATH', 'LIBRARY_PATH']
+ environment:
+ set:
+ '${PACKAGE}_ROOT': '${PREFIX}'
+ gcc:
+ environment:
+ set:
+ CC: gcc
+ CXX: g++
+ FC: gfortran
+ F90: gfortran
+ F77: gfortran
+ openmpi:
+ environment:
+ set:
+ SLURM_MPI_TYPE: pmi2
+ OMPI_MCA_btl_openib_warn_default_gid_prefix: '0'
+
+will add ``lapack`` providers to the mix. After the usual regeneration of module files:
+
+.. code-block:: console
+
+ $ module purge
+ $ spack module refresh --module-type lmod --delete-tree -y
+ ==> Regenerating lmod module files
+
+you will have something like:
+
+.. code-block:: console
+
+ $ module load gcc
+ $ module load openblas
+ $ module load openmpi
+ $ module avail
+
+ --------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/openblas/0.2.19-js33umc/openmpi/2.0.1-s3qbtby/gcc/6.2.0 ----------------------------------------------
+ netlib-scalapack/2.0.2
+
+ -------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/openblas/0.2.19-js33umc/gcc/6.2.0 ---------------------------------------------------------
+ py-numpy/1.11.1 py-scipy/0.18.1
+
+ -------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/gcc/6.2.0 ---------------------------------------------------------------------
+ binutils/2.27 curl/7.50.3 hwloc/1.11.4 libtool/2.4.6 lzo/2.09 netlib-lapack/3.6.1 openssl/1.0.2j python/2.7.12 zlib/1.2.8
+ bison/3.0.4 expat/2.2.0 libarchive/3.2.1 libxml2/2.9.4 m4/1.4.17 nettle/3.2 pkg-config/0.29.1 sqlite/3.8.5
+ bzip2/1.0.6 flex/2.6.0 libpciaccess/0.13.4 lz4/131 mpich/3.2 openblas/0.2.19 (L) py-nose/1.3.7 util-macros/1.19.0
+ cmake/3.6.1 gmp/6.1.1 libsigsegv/2.10 lzma/4.32.7 ncurses/6.0 openmpi/2.0.1 (L) py-setuptools/25.2.0 xz/5.2.2
+
+ ----------------------------------------------------------------------- /home/mculpo/wdir/spack/share/spack/lmod/linux-Ubuntu14-x86_64/Core -----------------------------------------------------------------------
+ gcc/6.2.0 (L)
+
+Now both the ``MPI`` and the ``LAPACK`` providers are handled by LMod as hierarchies:
+
+.. code-block:: console
+
+ $ module load py-numpy netlib-scalapack
+ $ module load mpich
+
+ Lmod is automatically replacing "openmpi/2.0.1" with "mpich/3.2"
+
+
+ Due to MODULEPATH changes the following have been reloaded:
+ 1) netlib-scalapack/2.0.2
+
+ $ module load netlib-lapack
+
+ Lmod is automatically replacing "openblas/0.2.19" with "netlib-lapack/3.6.1"
+
+
+ Inactive Modules:
+ 1) py-numpy
+
+ Due to MODULEPATH changes the following have been reloaded:
+ 1) netlib-scalapack/2.0.2
+
+making the use of tags to differentiate them unnecessary.
+Note that because we only compiled ``py-numpy`` with ``openblas`` the module
+is made inactive when we switch the ``LAPACK`` provider. The user
+environment will now be consistent by design!
diff --git a/lib/spack/docs/package_tutorial.rst b/lib/spack/docs/package_tutorial.rst
new file mode 100644
index 0000000000..e955780345
--- /dev/null
+++ b/lib/spack/docs/package_tutorial.rst
@@ -0,0 +1,463 @@
+.. _package_tutorial:
+
+=========================
+Package Creation Tutorial
+=========================
+
+This tutorial will walk you through the steps behind building a simple
+package installation script. We'll focus building an mpileaks package,
+which is a MPI debugging tool. By creating a package file we're
+essentially giving Spack a recipe for how to build a particular piece of
+software. We're describing some of the software's dependencies, where to
+find the package, what commands and options are used to build the package
+from source, and more. Once we've specified a package's recipe, we can
+ask Spack to build that package in many different ways.
+
+This tutorial assumes you have a basic familiarity with some of the Spack
+commands, and that you have a working version of Spack installed. If
+not, we suggest looking at Spack's *Getting Started* guide. This
+tutorial also assumes you have at least a beginner's-level familiarity
+with Python.
+
+Also note that this document is a tutorial. It can help you get started
+with packaging, but is not intended to be complete. See Spack's
+:ref:`packaging-guide` for more complete documentation on this topic.
+
+---------------
+Getting Started
+---------------
+
+A few things before we get started:
+
+- We'll refer to the Spack installation location via the environment
+ variable ``SPACK_ROOT``. You should point ``SPACK_ROOT`` at wherever
+ you have Spack installed.
+- Add ``$SPACK_ROOT/bin`` to your ``PATH`` before you start.
+- Make sure your ``EDITOR`` environment variable is set to some text
+ editor you like.
+- We'll be writting Python code as part of this tutorial. You can find
+ successive versions of the Python code in
+ ``$SPACK_ROOT/lib/spack/docs/packaging_tutorial``.
+
+-------------------------
+Creating the Package File
+-------------------------
+
+Spack comes with a handy command to create a new package: ``spack create``
+
+This command is given the location of a package's source code, downloads
+the code, and sets up some basic packaging infrastructure for you. The
+mpileaks source code can be found on github, and here's what happens when
+we run ``spack create`` on it:
+
+.. code-block:: console
+
+ % spack create -f https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz
+ ==> This looks like a URL for mpileaks version 1.0
+ ==> Creating template for package mpileaks
+ ==> Downloading...
+ ==> Fetching https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz
+ ###################################################################################### 100.0%
+
+And Spack should spawn a text editor with this file:
+
+.. literalinclude:: packaging_tutorial/0.package.py
+ :start-after: # flake8: noqa
+ :language: python
+
+Spack has created this file in
+``$SPACK_ROOT/var/spack/repos/builtin/packages/mpileaks/package.py``. Take a
+moment to look over the file. There's a few placeholders that Spack has
+created, which we'll fill in as part of this tutorial:
+
+- We'll document some information about this package in the comments.
+- We'll fill in the dependency list for this package.
+- We'll fill in some of the configuration arguments needed to build this
+ package.
+
+For the moment, exit your editor and let's see what happens when we try
+to build this package:
+
+.. code-block:: console
+
+ % spack install mpileaks
+ ==> Installing mpileaks
+ ==> Using cached archive: /usr/workspace/wsa/legendre/spack/var/spack/cache/mpileaks/mpileaks-1.0.tar.gz
+ ==> Staging archive: /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-hufwhwpq5benv3sslie6ryflk5s6nm35/mpileaks-1.0.tar.gz
+ ==> Created stage in /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-hufwhwpq5benv3sslie6ryflk5s6nm35
+ ==> Ran patch() for mpileaks
+ ==> Building mpileaks [AutotoolsPackage]
+ ==> Executing phase : 'autoreconf'
+ ==> Executing phase : 'configure'
+ ==> Error: ProcessError: Command exited with status 1:
+ './configure' '--prefix=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/mpileaks-1.0-hufwhwpq5benv3sslie6ryflk5s6nm35'
+ /usr/workspace/wsa/legendre/spack/lib/spack/spack/build_systems/autotools.py:150, in configure:
+ 145 def configure(self, spec, prefix):
+ 146 """Runs configure with the arguments specified in `configure_args`
+ 147 and an appropriately set prefix
+ 148 """
+ 149 options = ['--prefix={0}'.format(prefix)] + self.configure_args()
+ >> 150 inspect.getmodule(self).configure(*options)
+
+ See build log for details:
+ /tmp/legendre/spack-stage/spack-stage-8HVzqu/mpileaks-1.0/spack-build.out
+
+This obviously didn't work; we need to fill in the package-specific
+information. Specifically, Spack didn't try to build any of mpileaks'
+dependencies, nor did it use the proper configure arguments. Let's start
+fixing things
+
+---------------------
+Package Documentation
+---------------------
+
+We can bring the ``package.py`` file back into our ``EDITOR`` with the
+``spack edit`` command:
+
+.. code-block:: console
+
+ % spack edit mpileaks
+
+Let's remove some of the ``TODO`` comments, and add links to the mpileaks
+homepage and document what mpileaks does. I'm also going to cut out the
+Copyright clause at this point to keep this tutorial document shorter,
+but you shouldn't do that normally. The results of these changes can be
+found in ``$SPACK_ROOT/lib/spack/docs/packaging_tutorial/1.package.py``
+and are below. Make these changes to your ``package.py``:
+
+.. literalinclude:: packaging_tutorial/1.package.py
+ :start-after: # flake8: noqa
+ :language: python
+
+We've filled in the comment that describes what this package does and
+added a link to the web site. That won't help us build yet, but it will
+allow Spack to provide some documentation on this package to other users:
+
+.. code-block:: console
+
+ % spack info mpileaks
+ AutotoolsPackage: mpileaks
+ Homepage: https://github.com/hpc/mpileaks
+
+ Safe versions:
+ 1.0 https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz
+
+ Variants:
+ None
+
+ Installation Phases:
+ autoreconf configure build install
+
+ Build Dependencies:
+ None
+
+ Link Dependencies:
+ None
+
+ Run Dependencies:
+ None
+
+ Virtual Packages:
+ None
+
+ Description:
+ Tool to detect and report MPI objects like MPI_Requests and
+ MPI_Datatypes
+
+As we fill in more information about this package the spack info command
+will become more informative. Now let's start making this package build.
+
+------------
+Dependencies
+------------
+
+The mpileaks packages depends on three other package: ``MPI``,
+``adept-utils``, and ``callpath``. Let's add those via the
+``depends_on`` command in our ``package.py`` (this version is in
+``$SPACK_ROOT/lib/spack/docs/packaging_tutorial/2.package.py``):
+
+.. literalinclude:: packaging_tutorial/2.package.py
+ :start-after: # flake8: noqa
+ :language: python
+
+Now when we go to build mpileaks, Spack will fetch and build these
+dependencies before building mpileaks. Note that the mpi dependency is a
+different kind of beast than the adept-utils and callpath dependencies;
+there is no mpi package available in Spack. Instead mpi is a virtual
+dependency. Spack may satisfy that dependency by installing packages
+such as ``openmpi`` or ``mvapich``. See the :ref:`packaging-guide` for more
+information on virtual dependencies.
+
+Now when we try to install this package a lot more happens:
+
+.. code-block:: console
+
+ % spack install mpileaks
+ ==> Installing mpileaks
+ ==> openmpi is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz
+ ==> callpath is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/callpath-1.0.2-zm4pf3gasgxeibyu2y262suktvaazube
+ ==> adept-utils is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/adept-utils-1.0.1-7p7ezxwtajdglj6cmojy2vybjct4j4jz
+ ==> Using cached archive: /usr/workspace/wsa/legendre/spack/var/spack/cache/mpileaks/mpileaks-1.0.tar.gz
+ ==> Already staged mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk in /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk
+ ==> Already patched mpileaks
+ ==> Building mpileaks [AutotoolsPackage]
+ ==> Executing phase : 'autoreconf'
+ ==> Executing phase : 'configure'
+ ==> Error: ProcessError: Command exited with status 1:
+ './configure' '--prefix=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk'
+ /usr/workspace/wsa/legendre/spack/lib/spack/spack/build_systems/autotools.py:150, in configure:
+ 145 def configure(self, spec, prefix):
+ 146 """Runs configure with the arguments specified in `configure_args`
+ 147 and an appropriately set prefix
+ 148 """
+ 149 options = ['--prefix={0}'.format(prefix)] + self.configure_args()
+ >> 150 inspect.getmodule(self).configure(*options)
+
+ See build log for details:
+ /tmp/legendre/spack-stage/spack-stage-7V5yyk/mpileaks-1.0/spack-build.out
+
+Note that this command may take a while to run and produce more output if
+you don't have an MPI already installed or configured in Spack.
+
+Now Spack has identified and made sure all of our dependencies have been
+built. It found the ``openmpi`` package that will satisfy our ``mpi``
+dependency, and the callpath and ``adept-utils`` package to satisfy our
+concrete dependencies.
+
+------------------------
+Debugging Package Builds
+------------------------
+
+Our ``mpileaks`` package is still not building. It may be obvious to
+many of you that we're still missing the configure options. But let's
+pretend we're not all intelligent developers and use this opportunity
+spend some time debugging. We a few options that can tell us about
+what's going wrong:
+
+As per the error message, Spack has given us a ``spack-build.out`` debug log:
+
+.. code-block:: console
+
+ ==> './configure' '--prefix=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk'
+ checking metadata... no
+ checking installation directory variables... yes
+ checking for a BSD-compatible install... /usr/bin/install -c
+ checking whether build environment is sane... yes
+ checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
+ checking for gawk... gawk
+ checking whether make sets $(MAKE)... yes
+ checking for gcc... /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc
+ checking for C compiler default output file name... a.out
+ checking whether the C compiler works... yes
+ checking whether we are cross compiling... no
+ checking for suffix of executables...
+ checking for suffix of object files... o
+ checking whether we are using the GNU C compiler... yes
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc accepts -g... yes
+ checking for /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc option to accept ISO C89... none needed
+ checking for style of include used by make... GNU
+ checking dependency style of /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc... gcc3
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc and cc understand -c and -o together... yes
+ checking whether we are using the GNU C++ compiler... yes
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/g++ accepts -g... yes
+ checking dependency style of /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/g++... gcc3
+ checking for /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc... /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc
+ Checking whether /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc responds to '-showme:compile'... yes
+ configure: error: unable to locate ``adept-utils`` installation
+
+This gives us the output from the build, and it's fairly obvious that
+mpileaks isn't finding it's ``adept-utils`` package. Spack has
+automatically added the include and library directories of
+``adept-utils`` to the compiler's search path, but some packages like
+mpileaks can sometimes be picky and still want things spelled out on
+their command line. But let's continue to pretend we're not brilliant
+developers, and explore some other debugging paths:
+
+We can also enter the build area and try to manually run the build:
+
+.. code-block:: console
+
+ % spack env mpileaks tcsh
+ % spack cd mpileaks
+
+The ``spack env`` command spawned a new shell that contains the same
+environment that Spack used to build the mpileaks package (you can
+substitute tcsh for your favorite shell). The ``spack cd`` command
+changed our working dirctory to the last attempted build for mpileaks.
+From here we can manually re-run the build:
+
+.. code-block:: console
+
+ % ./configure
+ checking metadata... no
+ checking installation directory variables... yes
+ checking for a BSD-compatible install... /usr/bin/install -c
+ checking whether build environment is sane... yes
+ checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
+ checking for gawk... gawk
+ checking whether make sets $(MAKE)... yes
+ checking for gcc... /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc
+ checking for C compiler default output file name... a.out
+ checking whether the C compiler works... yes
+ checking whether we are cross compiling... no
+ checking for suffix of executables...
+ checking for suffix of object files... o
+ checking whether we are using the GNU C compiler... yes
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc accepts -g... yes
+ checking for /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc option to accept ISO C89... none needed
+ checking for style of include used by make... GNU
+ checking dependency style of /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc... gcc3
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/gcc and cc understand -c and -o together... yes
+ checking whether we are using the GNU C++ compiler... yes
+ checking whether /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/g++ accepts -g... yes
+ checking dependency style of /usr/workspace/wsa/legendre/spack/lib/spack/env/gcc/g++... gcc3
+ checking for /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc... /usr/workspace/wsa /legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc
+ Checking whether /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz/bin/mpicc responds to '-showme:compile'... yes
+ configure: error: unable to locate adept-utils installation
+
+We're seeing the same error, but now we're in a shell where we can run
+the command ourselves and debug as needed. We could, for example, run
+``./configure --help`` to see what options we can use to specify
+dependencies.
+
+We can use the ``exit`` command to leave the shell spawned by ``spack
+env``.
+
+------------------------------
+Specifying Configure Arguments
+------------------------------
+
+Let's add the configure arguments to the mpileaks' ``package.py``. This
+version can be found in
+``$SPACK_ROOT/lib/spack/docs/packaging_tutorial/3.package.py``:
+
+.. literalinclude:: packaging_tutorial/3.package.py
+ :start-after: # flake8: noqa
+ :language: python
+
+This is all we need for working mpileaks! If we install now we'll see:
+
+.. code-block:: console
+
+ % spack install mpileaks
+ spack install mpileaks
+ ==> Installing mpileaks
+ ==> openmpi is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz
+ ==> callpath is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/callpath-1.0.2-zm4pf3gasgxeibyu2y262suktvaazube
+ ==> adept-utils is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/adept-utils-1.0.1-7p7ezxwtajdglj6cmojy2vybjct4j4jz
+ ==> Using cached archive: /usr/workspace/wsa/legendre/spack/var/spack/cache/mpileaks/mpileaks-1.0.tar.gz
+ ==> Already staged mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk in /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk
+ ==> Already patched mpileaks
+ ==> Building mpileaks [AutotoolsPackage]
+ ==> Executing phase : 'autoreconf'
+ ==> Executing phase : 'configure'
+ ==> Executing phase : 'build'
+ ==> Executing phase : 'install'
+ ==> Successfully installed mpileaks
+ Fetch: 0.00s. Build: 14.08s. Total: 14.08s.
+ [+] /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/mpileaks-1.0-eum4hmnlt6ovalwjnciaygfb3beja4gk
+
+We took a few shortcuts for this package that are worth highlighting.
+Spack automatically detected that mpileaks was an autotools-based package
+when we ran spack create. If this had been a CMake-based package we
+would have been filling in a ``cmake_args`` function instead of
+``configure_args``. If spack hadn't been able to detect the build
+system, we'd be filling in a generic install method that would manually
+be calling build commands, such as is found in the ``zlib`` package:
+
+.. code-block:: python
+
+ def install(self, spec, prefix):
+ configure('--prefix={0}'.format(prefix))
+ make()
+ make('install')
+
+--------
+Variants
+--------
+
+We have a successful mpileaks build, but let's take some time to improve
+it. ``mpileaks`` has a build-time option to truncate parts of the stack
+that it walks. Let's add a variant to allow users to set this when they
+build in Spack.
+
+To do this, we'll add a variant to our package, as per the following (see
+``$SPACK_ROOT/lib/spack/docs/packaging_tutorial/4.package.py``):
+
+.. literalinclude:: packaging_tutorial/4.package.py
+ :start-after: # flake8: noqa
+ :language: python
+
+We've added the variant ``stackstart``, and given it a default value of
+``0``. If we install now we can see the stackstart variant added to the
+configure line (output truncated for length):
+
+.. code-block:: console
+
+ % spack install --verbose mpileaks stackstart=4
+ ==> Installing mpileaks
+ ==> openmpi is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/openmpi-2.0.1-5ee5j34c2y4kb5c3joygrgahidqnwhnz
+ ==> callpath is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/callpath-1.0.2-zm4pf3gasgxeibyu2y262suktvaazube
+ ==> adept-utils is already installed in /usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/adept-utils-1.0.1-7p7ezxwtajdglj6cmojy2vybjct4j4jz
+ ==> Using cached archive: /usr/workspace/wsa/legendre/spack/var/spack/cache/mpileaks/mpileaks-1.0.tar.gz
+ ==> Staging archive: /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-otqo2opkhan5ksujt6tpmdftydrieig7/mpileaks-1.0.tar.gz
+ ==> Created stage in /usr/workspace/wsa/legendre/spack/var/spack/stage/mpileaks-1.0-otqo2opkhan5ksujt6tpmdftydrieig7
+ ==> Ran patch() for mpileaks
+ ==> Building mpileaks [AutotoolsPackage]
+ ==> Executing phase : 'autoreconf'
+ ==> Executing phase : 'configure'
+ ==> './configure' '--prefix=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/mpileaks-1.0-otqo2opkhan5ksujt6tpmdftydrieig7' '--with-adept-utils=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/adept-utils-1.0.1-7p7ezxwtajdglj6cmojy2vybjct4j4jz' '--with-callpath=/usr/workspace/wsa/legendre/spack/opt/spack/linux-rhel7-x86_64/gcc-4.9.3/callpath-1.0.2-zm4pf3gasgxeibyu2y262suktvaazube' '--with-stack-start-c=4' '--with-stack-start-fortran=4'
+
+---------------
+The Spec Object
+---------------
+
+This tutorial has glossed over a few important features, which weren't
+too relevant for mpleaks but may be useful for other packages. There
+were several places we references the ``self.spec`` object. This is a
+powerful class for querying information about what we're building. For
+example, you could use the spec to query information about how a
+package's dependencies were built, or what compiler was being used, or
+what version of a package is being installed. Full documentation can be
+found in the :ref:`packaging-guide`, but here's some quick snippets with
+common queries:
+
+- Am I building ``mpileaks`` version ``1.1`` or greater?
+
+.. code-block:: python
+
+ if self.spec.satisfies('@1.1:'):
+ # Do things needed for 1.1+
+
+- Is ``openmpi`` the MPI I'm building with?
+
+.. code-block:: python
+
+ if self.spec['mpi'].name == 'openmpi':
+ # Do openmpi things
+
+- Am I building with ``gcc`` version less than ``5.0.0``:
+
+.. code-block:: python
+
+ if self.spec.satisfies('%gcc@:5.0.0'):
+ # Add arguments specific to gcc's earlier than 5.0.0
+
+- Am I built with the ``debug`` variant:
+
+.. code-block:: python
+
+ if self.spec.satisfies('+debug'):
+ # Add -g option to configure flags
+
+- Is my ``dyninst`` dependency greater than version ``8.0``?
+
+.. code-block:: python
+
+ if self.spec['dyninst'].satisfies('@8.0:'):
+ # Use newest dyninst options
+
+More examples can be found in the thousands of packages already added to
+Spack in ``$SPACK_ROOT/var/spack/repos/builtin/packages``.
+
+Good Luck!
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index a082b85efa..a22fcd71ba 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -1,7 +1,8 @@
.. _packaging-guide:
+===============
Packaging Guide
-=====================
+===============
This guide is intended for developers or administrators who want to
package software so that Spack can install it. It assumes that you
@@ -11,9 +12,9 @@ have at least some familiarity with Python, and that you've read the
There are two key parts of Spack:
- #. **Specs**: expressions for describing builds of software, and
- #. **Packages**: Python modules that describe how to build
- software according to a spec.
+#. **Specs**: expressions for describing builds of software, and
+#. **Packages**: Python modules that describe how to build
+ software according to a spec.
Specs allow a user to describe a *particular* build in a way that a
package author can understand. Packages allow a the packager to
@@ -28,13 +29,15 @@ ubiquitous in the scientific software community. Second, it's a modern
language and has many powerful features to help make package writing
easy.
+---------------------------
Creating & editing packages
-----------------------------------
+---------------------------
-.. _spack-create:
+.. _cmd-spack-create:
+^^^^^^^^^^^^^^^^
``spack create``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
The ``spack create`` command creates a directory with the package name and
generates a ``package.py`` file with a boilerplate package template from a URL.
@@ -44,7 +47,7 @@ working.
Here's an example:
-.. code-block:: sh
+.. code-block:: console
$ spack create http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz
@@ -59,7 +62,7 @@ strings look like for this package. Using this information, it will try to find
versions, Spack prompts you to tell it how many versions you want to download
and checksum:
-.. code-block:: sh
+.. code-block:: console
$ spack create http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz
==> This looks like a URL for cmake version 2.8.12.1.
@@ -78,30 +81,32 @@ Spack will automatically download the number of tarballs you specify
You do not *have* to download all of the versions up front. You can
always choose to download just one tarball initially, and run
-:ref:`spack checksum <spack-checksum>` later if you need more.
+:ref:`cmd-spack-checksum` later if you need more.
.. note::
If ``spack create`` fails to detect the package name correctly,
- you can try supplying it yourself, e.g.::
+ you can try supplying it yourself, e.g.:
+
+ .. code-block:: console
$ spack create --name cmake http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz
If it fails entirely, you can get minimal boilerplate by using
- :ref:`spack-edit-f`, or you can manually create a directory and
+ :ref:`spack edit --force <spack-edit-f>`, or you can manually create a directory and
``package.py`` file for the package in ``var/spack/repos/builtin/packages``.
.. note::
Spack can fetch packages from source code repositories, but,
``spack create`` will *not* currently create a boilerplate package
- from a repository URL. You will need to use :ref:`spack-edit-f`
+ from a repository URL. You will need to use :ref:`spack edit --force <spack-edit-f>`
and manually edit the ``version()`` directives to fetch from a
repo. See :ref:`vcs-fetch` for details.
Let's say you download 3 tarballs:
-.. code-block:: sh
+.. code-block:: none
Include how many checksums in the package file? (default is 5, q to abort) 3
==> Downloading...
@@ -118,27 +123,28 @@ file in your favorite ``$EDITOR``:
.. code-block:: python
:linenos:
- # FIXME:
- # This is a template package file for Spack. We've conveniently
- # put "FIXME" labels next to all the things you'll want to change.
#
- # Once you've edited all the FIXME's, delete this whole message,
- # save this file, and test out your package like this:
+ # This is a template package file for Spack. We've put "FIXME"
+ # next to all the things you'll want to change. Once you've handled
+ # them, you can save this file and test your package like this:
#
# spack install cmake
#
- # You can always get back here to change things with:
+ # You can edit this file again by typing:
#
# spack edit cmake
#
- # See the spack documentation for more information on building
- # packages.
+ # See the Spack documentation for more information on packaging.
+ # If you submit this package back to Spack as a pull request,
+ # please first remove this boilerplate and all FIXME comments.
#
from spack import *
+
class Cmake(Package):
- """FIXME: put a proper description of your package here."""
- # FIXME: add a proper url for your package's homepage here.
+ """FIXME: Put a proper description of your package here."""
+
+ # FIXME: Add a proper url for your package's homepage here.
homepage = "http://www.example.com"
url = "http://www.cmake.org/files/v2.8/cmake-2.8.12.1.tar.gz"
@@ -162,79 +168,81 @@ done for you.
In the generated package, the download ``url`` attribute is already
set. All the things you still need to change are marked with
-``FIXME`` labels. The first ``FIXME`` refers to the commented
-instructions at the top of the file. You can delete these
-instructions after reading them. The rest of them are as follows:
+``FIXME`` labels. You can delete the commented instructions between
+the license and the first import statement after reading them.
+The rest of the tasks you need to do are as follows:
- #. Add a description.
+#. Add a description.
- Immediately inside the package class is a *docstring* in
- triple-quotes (``"""``). It's used to generate the description
- shown when users run ``spack info``.
+ Immediately inside the package class is a *docstring* in
+ triple-quotes (``"""``). It's used to generate the description
+ shown when users run ``spack info``.
- #. Change the ``homepage`` to a useful URL.
+#. Change the ``homepage`` to a useful URL.
- The ``homepage`` is displayed when users run ``spack info`` so
- that they can learn about packages.
+ The ``homepage`` is displayed when users run ``spack info`` so
+ that they can learn about packages.
- #. Add ``depends_on()`` calls for the package's dependencies.
+#. Add ``depends_on()`` calls for the package's dependencies.
- ``depends_on`` tells Spack that other packages need to be built
- and installed before this one. See `dependencies_`.
+ ``depends_on`` tells Spack that other packages need to be built
+ and installed before this one. See :ref:`dependencies`.
- #. Get the ``install()`` method working.
+#. Get the ``install()`` method working.
- The ``install()`` method implements the logic to build a
- package. The code should look familiar; it is designed to look
- like a shell script. Specifics will differ depending on the package,
- and :ref:`implementing the install method <install-method>` is
- covered in detail later.
+ The ``install()`` method implements the logic to build a
+ package. The code should look familiar; it is designed to look
+ like a shell script. Specifics will differ depending on the package,
+ and :ref:`implementing the install method <install-method>` is
+ covered in detail later.
Before going into details, we'll cover a few more basics.
-.. _spack-edit:
+.. _cmd-spack-edit:
+^^^^^^^^^^^^^^
``spack edit``
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
One of the easiest ways to learn to write packages is to look at
existing ones. You can edit a package file by name with the ``spack
edit`` command:
-.. code-block:: sh
+.. code-block:: console
- spack edit cmake
+ $ spack edit cmake
So, if you used ``spack create`` to create a package, then saved and
closed the resulting file, you can get back to it with ``spack edit``.
The ``cmake`` package actually lives in
-``$SPACK_ROOT/var/spack/repos/builtin/packages/cmake/package.py``, but this provides
-a much simpler shortcut and saves you the trouble of typing the full
-path.
+``$SPACK_ROOT/var/spack/repos/builtin/packages/cmake/package.py``,
+but this provides a much simpler shortcut and saves you the trouble
+of typing the full path.
If you try to edit a package that doesn't exist, Spack will recommend
-using ``spack create`` or ``spack edit -f``:
+using ``spack create`` or ``spack edit --force``:
-.. code-block:: sh
+.. code-block:: console
$ spack edit foo
==> Error: No package 'foo'. Use spack create, or supply -f/--force to edit a new file.
.. _spack-edit-f:
-``spack edit -f``
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^
+``spack edit --force``
+^^^^^^^^^^^^^^^^^^^^^^
-``spack edit -f`` can be used to create a new, minimal boilerplate
+``spack edit --force`` can be used to create a new, minimal boilerplate
package:
-.. code-block:: sh
+.. code-block:: console
- $ spack edit -f foo
+ $ spack edit --force foo
Unlike ``spack create``, which infers names and versions, and which
actually downloads the tarball and checksums it for you, ``spack edit
--f`` has no such fanciness. It will substitute dummy values for you
+--force`` has no such fanciness. It will substitute dummy values for you
to fill in yourself:
.. code-block:: python
@@ -251,31 +259,25 @@ to fill in yourself:
version('1.0', '0123456789abcdef0123456789abcdef')
def install(self, spec, prefix):
- configure("--prefix=" + prefix)
+ configure("--prefix=%s" % prefix)
make()
make("install")
This is useful when ``spack create`` cannot figure out the name and
version of your package from the archive URL.
-
+----------------------------
Naming & directory structure
---------------------------------------
-
-.. note::
-
- Spack's default naming and directory structure will change in
- version 0.9. Specifically, 0.9 will stop using directory names
- with special characters like ``@``, to avoid interfering with
- certain packages that do not handle this well.
+----------------------------
This section describes how packages need to be named, and where they
-live in Spack's directory structure. In general, `spack-create`_ and
-`spack-edit`_ handle creating package files for you, so you can skip
+live in Spack's directory structure. In general, :ref:`cmd-spack-create` and
+:ref:`cmd-spack-edit` handle creating package files for you, so you can skip
most of the details here.
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``var/spack/repos/builtin/packages``
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A Spack installation directory is structured like a standard UNIX
install prefix (``bin``, ``lib``, ``include``, ``var``, ``opt``,
@@ -285,28 +287,30 @@ Packages themselves live in ``$SPACK_ROOT/var/spack/repos/builtin/packages``.
If you ``cd`` to that directory, you will see directories for each
package:
-.. command-output:: cd $SPACK_ROOT/var/spack/repos/builtin/packages; ls -CF
+.. command-output:: cd $SPACK_ROOT/var/spack/repos/builtin/packages && ls
:shell:
:ellipsis: 10
Each directory contains a file called ``package.py``, which is where
all the python code for the package goes. For example, the ``libelf``
-package lives in::
+package lives in:
+
+.. code-block:: none
$SPACK_ROOT/var/spack/repos/builtin/packages/libelf/package.py
Alongside the ``package.py`` file, a package may contain extra
directories or files (like patches) that it needs to build.
-
+^^^^^^^^^^^^^
Package Names
-~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^
Packages are named after the directory containing ``package.py``. It is
preferred, but not required, that the directory, and thus the package name, are
lower case. So, ``libelf``'s ``package.py`` lives in a directory called
``libelf``. The ``package.py`` file defines a class called ``Libelf``, which
-extends Spack's ``Package`` class. for example, here is
+extends Spack's ``Package`` class. For example, here is
``$SPACK_ROOT/var/spack/repos/builtin/packages/libelf/package.py``:
.. code-block:: python
@@ -328,7 +332,7 @@ The **directory name** (``libelf``) determines the package name that
users should provide on the command line. e.g., if you type any of
these:
-.. code-block:: sh
+.. code-block:: console
$ spack install libelf
$ spack install libelf@0.8.13
@@ -346,8 +350,9 @@ difficult to support these options. So, you can name a package
``3proxy`` or ``_foo`` and Spack won't care. It just needs to see
that name in the package spec.
+^^^^^^^^^^^^^^^^^^^
Package class names
-~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
Spack loads ``package.py`` files dynamically, and it needs to find a
special class name in the file for the load to succeed. The **class
@@ -366,11 +371,138 @@ some examples:
================= =================
In general, you won't have to remember this naming convention because
-`spack-create`_ and `spack-edit`_ handle the details for you.
+:ref:`cmd-spack-create` and :ref:`cmd-spack-edit` handle the details for you.
+
+-----------------
+Trusted Downloads
+-----------------
+
+Spack verifies that the source code it downloads is not corrupted or
+compromised; or at least, that it is the same version the author of
+the Spack package saw when the package was created. If Spack uses a
+download method it can verify, we say the download method is
+*trusted*. Trust is important for *all downloads*: Spack
+has no control over the security of the various sites from which it
+downloads source code, and can never assume that any particular site
+hasn't been compromised.
+
+Trust is established in different ways for different download methods.
+For the most common download method --- a single-file tarball --- the
+tarball is checksummed. Git downloads using ``commit=`` are trusted
+implicitly, as long as a hash is specified.
+
+Spack also provides untrusted download methods: tarball URLs may be
+supplied without a checksum, or Git downloads may specify a branch or
+tag instead of a hash. If the user does not control or trust the
+source of an untrusted download, it is a security risk. Unless otherwise
+specified by the user for special cases, Spack should by default use
+*only* trusted download methods.
+
+Unfortunately, Spack does not currently provide that guarantee. It
+does provide the following mechanisms for safety:
+
+#. By default, Spack will only install a tarball package if it has a
+ checksum and that checksum matches. You can override this with
+ ``spack install --no-checksum``.
+
+#. Numeric versions are almost always tarball downloads, whereas
+ non-numeric versions not named ``develop`` frequently download
+ untrusted branches or tags from a version control system. As long
+ as a package has at least one numeric version, and no non-numeric
+ version named ``develop``, Spack will prefer it over any
+ non-numeric versions.
+
+^^^^^^^^^
+Checksums
+^^^^^^^^^
+
+For tarball downloads, Spack can currently support checksums using the
+MD5, SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512 algorithms. It
+determines the algorithm to use based on the hash length.
+
+-----------------------
+Package Version Numbers
+-----------------------
+
+Most Spack versions are numeric, a tuple of integers; for example,
+``apex@0.1``, ``ferret@6.96`` or ``py-netcdf@1.2.3.1``. Spack knows
+how to compare and sort numeric versions.
+
+Some Spack versions involve slight extensions of numeric syntax; for
+example, ``py-sphinx-rtd-theme@0.1.10a0``. In this case, numbers are
+always considered to be "newer" than letters. This is for consistency
+with `RPM <https://bugzilla.redhat.com/show_bug.cgi?id=50977>`_.
+
+Spack versions may also be arbitrary non-numeric strings; any string
+here will suffice; for example, ``@develop``, ``@master``, ``@local``.
+The following rules determine the sort order of numeric
+vs. non-numeric versions:
+
+#. The non-numeric versions ``@develop`` is considered greatest (newest).
+
+#. Numeric versions are all less than ``@develop`` version, and are
+ sorted numerically.
+
+#. All other non-numeric versions are less than numeric versions, and
+ are sorted alphabetically.
+The logic behind this sort order is two-fold:
+#. Non-numeric versions are usually used for special cases while
+ developing or debugging a piece of software. Keeping most of them
+ less than numeric versions ensures that Spack choose numeric
+ versions by default whenever possible.
+
+#. The most-recent development version of a package will usually be
+ newer than any released numeric versions. This allows the
+ ``develop`` version to satisfy dependencies like ``depends_on(abc,
+ when="@x.y.z:")``
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Concretization Version Selection
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When concretizing, many versions might match a user-supplied spec.
+For example, the spec ``python`` matches all available versions of the
+package ``python``. Similarly, ``python@3:`` matches all versions of
+Python3. Given a set of versions that match a spec, Spack
+concretization uses the following priorities to decide which one to
+use:
+
+#. If the user provided a list of versions in ``packages.yaml``, the
+ first matching version in that list will be used.
+
+#. If one or more versions is specified as ``preferred=True``, in
+ either ``packages.yaml`` or ``package.py``, the largest matching
+ version will be used. ("Latest" is defined by the sort order
+ above).
+
+#. If no preferences in particular are specified in the package or in
+ ``packages.yaml``, then the largest matching non-develop version
+ will be used. By avoiding ``@develop``, this prevents users from
+ accidentally installing a ``@develop`` version.
+
+#. If all else fails and ``@develop`` is the only matching version, it
+ will be used.
+
+^^^^^^^^^^^^^
+Date Versions
+^^^^^^^^^^^^^
+
+If you wish to use dates as versions, it is best to use the format
+``@date-yyyy-mm-dd``. This will ensure they sort in the correct
+order. If you want your date versions to be numeric (assuming they
+don't conflict with other numeric versions), you can use just
+``yyyy.mm.dd``.
+
+Alternately, you might use a hybrid release-version / date scheme.
+For example, ``@1.3.2016.08.31`` would mean the version from the
+``1.3`` branch, as of August 31, 2016.
+
+
+-------------------
Adding new versions
-------------------------
+-------------------
The most straightforward way to add new versions to your package is to
add a line like this in the package class:
@@ -385,45 +517,73 @@ add a line like this in the package class:
Versions should be listed with the newest version first.
+^^^^^^^^^^^^
Version URLs
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^
By default, each version's URL is extrapolated from the ``url`` field
in the package. For example, Spack is smart enough to download
version ``8.2.1.`` of the ``Foo`` package above from
``http://example.com/foo-8.2.1.tar.gz``.
-If spack *cannot* extrapolate the URL from the ``url`` field by
-default, you can write your own URL generation algorithm in place of
-the ``url`` declaration. For example:
+If the URL is particularly complicated or changes based on the release,
+you can override the default URL generation algorithm by defining your
+own ``url_for_version()`` function. For example, the developers of HDF5
+keep changing the archive layout, so the ``url_for_version()`` function
+looks like:
+
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/hdf5/package.py
+ :pyobject: Hdf5.url_for_version
+
+With the use of this ``url_for_version()``, Spack knows to download HDF5 ``1.8.16``
+from ``http://www.hdfgroup.org/ftp/HDF5/releases/hdf5-1.8.16/src/hdf5-1.8.16.tar.gz``
+but download HDF5 ``1.10.0`` from ``http://www.hdfgroup.org/ftp/HDF5/releases/hdf5-1.10/hdf5-1.10.0/src/hdf5-1.10.0.tar.gz``.
+
+You'll notice that HDF5's ``url_for_version()`` function makes use of a special
+``Version`` function called ``up_to()``. When you call ``version.up_to(2)`` on a
+version like ``1.10.0``, it returns ``1.10``. ``version.up_to(1)`` would return
+``1``. This can be very useful for packages that place all ``X.Y.*`` versions in
+a single directory and then places all ``X.Y.Z`` versions in a subdirectory.
+
+There are a few ``Version`` properties you should be aware of. We generally
+prefer numeric versions to be separated by dots for uniformity, but not all
+tarballs are named that way. For example, ``icu4c`` separates its major and minor
+versions with underscores, like ``icu4c-57_1-src.tgz``. The value ``57_1`` can be
+obtained with the use of the ``version.underscored`` property. Note that Python
+properties don't need parentheses. There are other separator properties as well:
+
+=================== ======
+Property Result
+=================== ======
+version.dotted 1.2.3
+version.dashed 1-2-3
+version.underscored 1_2_3
+version.joined 123
+=================== ======
-.. code-block:: python
- :linenos:
+.. note::
- class Foo(Package):
- def url_for_version(self, version):
- return 'http://example.com/version_%s/foo-%s.tar.gz' \
- % (version, version)
- version('8.2.1', '4136d7b4c04df68b686570afa26988ac')
- ...
+ Python properties don't need parentheses. ``version.dashed`` is correct.
+ ``version.dashed()`` is incorrect.
-If a URL cannot be derived systematically, you can add an explicit URL
-for a particular version:
+If a URL cannot be derived systematically, or there is a special URL for one
+of its versions, you can add an explicit URL for a particular version:
.. code-block:: python
version('8.2.1', '4136d7b4c04df68b686570afa26988ac',
url='http://example.com/foo-8.2.1-special-version.tar.gz')
-For the URL above, you might have to add an explicit URL because the
-version can't simply be substituted in the original ``url`` to
-construct the new one for ``8.2.1``.
+This is common for Python packages that download from PyPi. Since newer
+download URLs often contain a unique hash for each version, there is no
+way to guess the URL systematically.
When you supply a custom URL for a version, Spack uses that URL
*verbatim* and does not perform extrapolation.
+^^^^^^^^^^^^^^^^^^^^^^^^
Skipping the expand step
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^
Spack normally expands archives automatically after downloading
them. If you want to skip this step (e.g., for self-extracting
@@ -433,7 +593,7 @@ executables and other custom archive types), you can add
.. code-block:: python
version('8.2.1', '4136d7b4c04df68b686570afa26988ac',
- url='http://example.com/foo-8.2.1-special-version.tar.gz', 'expand=False')
+ url='http://example.com/foo-8.2.1-special-version.tar.gz', expand=False)
When ``expand`` is set to ``False``, Spack sets the current working
directory to the directory containing the downloaded archive before it
@@ -451,26 +611,15 @@ it executable, then runs it with some arguments.
installer = Executable(self.stage.archive_file)
installer('--prefix=%s' % prefix, 'arg1', 'arg2', 'etc.')
-Checksums
-~~~~~~~~~~~~~~~~~
-
-Spack uses a checksum to ensure that the downloaded package version is
-not corrupted or compromised. This is especially important when
-fetching from insecure sources, like unencrypted http. By default, a
-package will *not* be installed if it doesn't pass a checksum test
-(though you can override this with ``spack install --no-checksum``).
-
-Spack can currently support checksums using the MD5, SHA-1, SHA-224,
-SHA-256, SHA-384, and SHA-512 algorithms. It determines the algorithm
-to use based on the hash length.
+^^^^^^^^^^^^^
``spack md5``
-^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^
If you have one or more files to checksum, you can use the ``spack md5``
command to do it:
-.. code-block:: sh
+.. code-block:: console
$ spack md5 foo-8.2.1.tar.gz foo-8.2.2.tar.gz
==> 2 MD5 checksums:
@@ -480,7 +629,7 @@ command to do it:
``spack md5`` also accepts one or more URLs and automatically downloads
the files for you:
-.. code-block:: sh
+.. code-block:: console
$ spack md5 http://example.com/foo-8.2.1.tar.gz
==> Trying to fetch from http://example.com/foo-8.2.1.tar.gz
@@ -492,16 +641,17 @@ Doing this for lots of files, or whenever a new package version is
released, is tedious. See ``spack checksum`` below for an automated
version of this process.
-.. _spack-checksum:
+.. _cmd-spack-checksum:
+^^^^^^^^^^^^^^^^^^
``spack checksum``
-^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^
If you want to add new versions to a package you've already created,
this is automated with the ``spack checksum`` command. Here's an
example for ``libelf``:
-.. code-block:: sh
+.. code-block:: console
$ spack checksum libelf
==> Found 16 versions of libelf.
@@ -525,7 +675,7 @@ they're released). It fetches the tarballs you ask for and prints out
a list of ``version`` commands ready to copy/paste into your package
file:
-.. code-block:: sh
+.. code-block:: console
==> Checksummed new versions of libelf:
version('0.8.13', '4136d7b4c04df68b686570afa26988ac')
@@ -558,8 +708,9 @@ versions. See the documentation on `attribute_list_url`_ and
.. _vcs-fetch:
+------------------------------
Fetching from VCS repositories
---------------------------------------
+------------------------------
For some packages, source code is provided in a Version Control System
(VCS) repository rather than in a tarball. Spack can fetch packages
@@ -572,50 +723,19 @@ call to your package with parameters indicating the repository URL and
any branch, tag, or revision to fetch. See below for the parameters
you'll need for each VCS system.
-Repositories and versions
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-The package author is responsible for coming up with a sensible name
-for each version to be fetched from a repository. For example, if
-you're fetching from a tag like ``v1.0``, you might call that ``1.0``.
-If you're fetching a nameless git commit or an older subversion
-revision, you might give the commit an intuitive name, like ``develop``
-for a development version, or ``some-fancy-new-feature`` if you want
-to be more specific.
-
-In general, it's recommended to fetch tags or particular
-commits/revisions, NOT branches or the repository mainline, as
-branches move forward over time and you aren't guaranteed to get the
-same thing every time you fetch a particular version. Life isn't
-always simple, though, so this is not strictly enforced.
-
-When fetching from from the branch corresponding to the development version
-(often called ``master``,``trunk`` or ``dev``), it is recommended to
-call this version ``develop``. Spack has special treatment for this version so
- that ``@develop`` will satisfy dependencies like
-``depends_on(abc, when="@x.y.z:")``. In other words, ``@develop`` is
-greater than any other version. The rationale is that certain features or
-options first appear in the development branch. Therefore if a package author
-wants to keep the package on the bleeding edge and provide support for new
-features, it is advised to use ``develop`` for such a version which will
-greatly simplify writing dependencies and version-related conditionals.
-
-In some future release, Spack may support extrapolating repository
-versions as it does for tarball URLs, but currently this is not
-supported.
-
.. _git-fetch:
+^^^
Git
-~~~~~~~~~~~~~~~~~~~~
+^^^
Git fetching is enabled with the following parameters to ``version``:
- * ``git``: URL of the git repository.
- * ``tag``: name of a tag to fetch.
- * ``branch``: name of a branch to fetch.
- * ``commit``: SHA hash (or prefix) of a commit to fetch.
- * ``submodules``: Also fetch submodules when checking out this repository.
+* ``git``: URL of the git repository.
+* ``tag``: name of a tag to fetch.
+* ``branch``: name of a branch to fetch.
+* ``commit``: SHA hash (or prefix) of a commit to fetch.
+* ``submodules``: Also fetch submodules when checking out this repository.
Only one of ``tag``, ``branch``, or ``commit`` can be used at a time.
@@ -628,8 +748,7 @@ Default branch
...
version('develop', git='https://github.com/example-project/example.git')
- This is not recommended, as the contents of the default branch
- change over time.
+ This download method is untrusted, and is not recommended.
Tags
To fetch from a particular tag, use the ``tag`` parameter along with
@@ -640,6 +759,8 @@ Tags
version('1.0.1', git='https://github.com/example-project/example.git',
tag='v1.0.1')
+ This download method is untrusted, and is not recommended.
+
Branches
To fetch a particular branch, use ``branch`` instead:
@@ -648,8 +769,7 @@ Branches
version('experimental', git='https://github.com/example-project/example.git',
branch='experimental')
- This is not recommended, as the contents of branches change over
- time.
+ This download method is untrusted, and is not recommended.
Commits
Finally, to fetch a particular commit, use ``commit``:
@@ -659,7 +779,7 @@ Commits
version('2014-10-08', git='https://github.com/example-project/example.git',
commit='9d38cd4e2c94c3cea97d0e2924814acc')
- This doesn't have to be a full hash; You can abbreviate it as you'd
+ This doesn't have to be a full hash; you can abbreviate it as you'd
expect with git:
.. code-block:: python
@@ -667,6 +787,9 @@ Commits
version('2014-10-08', git='https://github.com/example-project/example.git',
commit='9d38cd')
+ This download method *is trusted*. It is the recommended way to
+ securely download from a Git repository.
+
It may be useful to provide a saner version for commits like this,
e.g. you might use the date as the version, as done above. Or you
could just use the abbreviated commit hash. It's up to the package
@@ -683,24 +806,29 @@ Submodules
tag='v1.0.1', submdoules=True)
-Installing
-^^^^^^^^^^^^^^
-
-You can fetch and install any of the versions above as you'd expect,
-by using ``@<version>`` in a spec:
+.. _github-fetch:
-.. code-block:: sh
+""""""
+GitHub
+""""""
- spack install example@2014-10-08
+If a project is hosted on GitHub, *any* valid Git branch, tag or hash
+may be downloaded as a tarball. This is accomplished simply by
+constructing an appropriate URL. Spack can checksum any package
+downloaded this way, thereby producing a trusted download. For
+example, the following downloads a particular hash, and then applies a
+checksum.
-Git and other VCS versions will show up in the list of versions when
-a user runs ``spack info <package name>``.
+.. code-block:: python
+ version('1.9.5.1.1', 'd035e4bc704d136db79b43ab371b27d2',
+ url='https://www.github.com/jswhit/pyproj/tarball/0be612cc9f972e38b50a90c946a9b353e2ab140f')
.. _hg-fetch:
+^^^^^^^^^
Mercurial
-~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^
Fetching with mercurial works much like `git <git-fetch>`_, but you
use the ``hg`` parameter.
@@ -712,8 +840,7 @@ Default
version('develop', hg='https://jay.grs.rwth-aachen.de/hg/example')
- Note that this is not recommended; try to fetch a particular
- revision instead.
+ This download method is untrusted, and is not recommended.
Revisions
Add ``hg`` and ``revision`` parameters:
@@ -723,6 +850,8 @@ Revisions
version('1.0', hg='https://jay.grs.rwth-aachen.de/hg/example',
revision='v1.0')
+ This download method is untrusted, and is not recommended.
+
Unlike ``git``, which has special parameters for different types of
revisions, you can use ``revision`` for branches, tags, and commits
when you fetch with Mercurial.
@@ -732,8 +861,9 @@ example@<version>`` command-line syntax.
.. _svn-fetch:
+^^^^^^^^^^
Subversion
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^
To fetch with subversion, use the ``svn`` and ``revision`` parameters:
@@ -744,7 +874,7 @@ Fetching the head
version('develop', svn='https://outreach.scidac.gov/svn/libmonitor/trunk')
- This is not recommended, as the head will move forward over time.
+ This download method is untrusted, and is not recommended.
Fetching a revision
To fetch a particular revision, add a ``revision`` to the
@@ -755,9 +885,63 @@ Fetching a revision
version('develop', svn='https://outreach.scidac.gov/svn/libmonitor/trunk',
revision=128)
+ This download method is untrusted, and is not recommended.
+
Subversion branches are handled as part of the directory structure, so
you can check out a branch or tag by changing the ``url``.
+-----------------------------------------
+Standard repositories for python packages
+-----------------------------------------
+
+In addition to their developer websites, many python packages are hosted at the
+`Python Package Index (PyPi) <https://pypi.python.org/pypi>`_. Although links to
+these individual files are typically `generated using a hash
+<https://bitbucket.org/pypa/pypi/issues/438>`_ it is often possible to find a
+reliable link of the format
+
+.. code-block:: sh
+
+ https://pypi.python.org/packages/source/<first letter of package>/<package>/<package>-<version>.<extension>
+
+Packages hosted on GitHub and the like are often developer versions that do not
+contain all of the files (e.g. configuration scripts) necessary to support
+compilation. For this reason it is ideal to link to a repository such as PyPi
+if possible.
+
+More recently, sources are being indexed at `pypi.io <https://pypi.io>`_ as
+well. Links obtained from this site follow a similar pattern, namely
+
+.. code-block:: sh
+
+ https://pypi.io/packages/source/<first letter of package>/<package>/<package>-<version>.<extension>
+
+These links currently redirect back to `pypi.python.org
+<https://pypi.python.org>`_, but this `may change in the future
+<https://bitbucket.org/pypa/pypi/issues/438#comment-27243225>`_.
+
+-------------------------------------------------
+Expanding additional resources in the source tree
+-------------------------------------------------
+
+Some packages (most notably compilers) provide optional features if additional
+resources are expanded within their source tree before building. In Spack it is
+possible to describe such a need with the ``resource`` directive :
+
+ .. code-block:: python
+
+ resource(
+ name='cargo',
+ git='https://github.com/rust-lang/cargo.git',
+ tag='0.10.0',
+ destination='cargo'
+ )
+
+Based on the keywords present among the arguments the appropriate ``FetchStrategy``
+will be used for the resource. The keyword ``destination`` is relative to the source
+root of the package and should point to where the resource is to be expanded.
+
+------------------------------------------------------
Automatic caching of files fetched during installation
------------------------------------------------------
@@ -768,42 +952,48 @@ reinstalled on account of a change in the hashing scheme.
.. _license:
+-----------------
Licensed software
-------------------------------------------
+-----------------
In order to install licensed software, Spack needs to know a few more
details about a package. The following class attributes should be defined.
+^^^^^^^^^^^^^^^^^^^^
``license_required``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
Boolean. If set to ``True``, this software requires a license. If set to
``False``, all of the following attributes will be ignored. Defaults to
``False``.
+^^^^^^^^^^^^^^^^^^^
``license_comment``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
String. Contains the symbol used by the license manager to denote a comment.
Defaults to ``#``.
+^^^^^^^^^^^^^^^^^
``license_files``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
List of strings. These are files that the software searches for when
looking for a license. All file paths must be relative to the installation
directory. More complex packages like Intel may require multiple
licenses for individual components. Defaults to the empty list.
+^^^^^^^^^^^^^^^^
``license_vars``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
List of strings. Environment variables that can be set to tell the software
where to look for a license if it is not in the usual location. Defaults
to the empty list.
+^^^^^^^^^^^^^^^
``license_url``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
String. A URL pointing to license setup instructions for the software.
Defaults to the empty string.
@@ -812,12 +1002,12 @@ For example, let's take a look at the package for the PGI compilers.
.. code-block:: python
- # Licensing
- license_required = True
- license_comment = '#'
- license_files = ['license.dat']
- license_vars = ['PGROUPD_LICENSE_FILE', 'LM_LICENSE_FILE']
- license_url = 'http://www.pgroup.com/doc/pgiinstall.pdf'
+ # Licensing
+ license_required = True
+ license_comment = '#'
+ license_files = ['license.dat']
+ license_vars = ['PGROUPD_LICENSE_FILE', 'LM_LICENSE_FILE']
+ license_url = 'http://www.pgroup.com/doc/pgiinstall.pdf'
As you can see, PGI requires a license. Its license manager, FlexNet, uses
the ``#`` symbol to denote a comment. It expects the license file to be
@@ -840,39 +1030,39 @@ this:
.. code-block:: sh
- # A license is required to use pgi.
- #
- # The recommended solution is to store your license key in this global
- # license file. After installation, the following symlink(s) will be
- # added to point to this file (relative to the installation prefix):
- #
- # license.dat
- #
- # Alternatively, use one of the following environment variable(s):
- #
- # PGROUPD_LICENSE_FILE
- # LM_LICENSE_FILE
- #
- # If you choose to store your license in a non-standard location, you may
- # set one of these variable(s) to the full pathname to the license file, or
- # port@host if you store your license keys on a dedicated license server.
- # You will likely want to set this variable in a module file so that it
- # gets loaded every time someone tries to use pgi.
- #
- # For further information on how to acquire a license, please refer to:
- #
- # http://www.pgroup.com/doc/pgiinstall.pdf
- #
- # You may enter your license below.
+ # A license is required to use pgi.
+ #
+ # The recommended solution is to store your license key in this global
+ # license file. After installation, the following symlink(s) will be
+ # added to point to this file (relative to the installation prefix):
+ #
+ # license.dat
+ #
+ # Alternatively, use one of the following environment variable(s):
+ #
+ # PGROUPD_LICENSE_FILE
+ # LM_LICENSE_FILE
+ #
+ # If you choose to store your license in a non-standard location, you may
+ # set one of these variable(s) to the full pathname to the license file, or
+ # port@host if you store your license keys on a dedicated license server.
+ # You will likely want to set this variable in a module file so that it
+ # gets loaded every time someone tries to use pgi.
+ #
+ # For further information on how to acquire a license, please refer to:
+ #
+ # http://www.pgroup.com/doc/pgiinstall.pdf
+ #
+ # You may enter your license below.
You can add your license directly to this file, or tell FlexNet to use a
license stored on a separate license server. Here is an example that
points to a license server called licman1:
-.. code-block:: sh
+.. code-block:: none
- SERVER licman1.mcs.anl.gov 00163eb7fba5 27200
- USE_SERVER
+ SERVER licman1.mcs.anl.gov 00163eb7fba5 27200
+ USE_SERVER
If your package requires the license to install, you can reference the
location of this global license using ``self.global_license_file``.
@@ -888,8 +1078,9 @@ documentation.
.. _patching:
+-------
Patches
-------------------------------------------
+-------
Depending on the host architecture, package version, known bugs, or
other issues, you may need to patch your software to get it to build
@@ -897,11 +1088,12 @@ correctly. Like many other package systems, spack allows you to store
patches alongside your package files and apply them to source code
after it's downloaded.
+^^^^^^^^^
``patch``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^
You can specify patches in your package file with the ``patch()``
-function. ``patch`` looks like this:
+directive. ``patch`` looks like this:
.. code-block:: python
@@ -913,7 +1105,9 @@ The first argument can be either a URL or a filename. It specifies a
patch file that should be applied to your source. If the patch you
supply is a filename, then the patch needs to live within the spack
source tree. For example, the patch above lives in a directory
-structure like this::
+structure like this:
+
+.. code-block:: none
$SPACK_ROOT/var/spack/repos/builtin/packages/
mvapich2/
@@ -934,52 +1128,59 @@ from the URL and then applied to your source code.
``patch`` can take two options keyword arguments. They are:
+""""""""
``when``
- If supplied, this is a spec that tells spack when to apply
- the patch. If the installed package spec matches this spec, the
- patch will be applied. In our example above, the patch is applied
- when mvapich is at version ``1.9`` or higher.
+""""""""
+
+If supplied, this is a spec that tells spack when to apply
+the patch. If the installed package spec matches this spec, the
+patch will be applied. In our example above, the patch is applied
+when mvapich is at version ``1.9`` or higher.
+"""""""""
``level``
- This tells spack how to run the ``patch`` command. By default,
- the level is 1 and spack runs ``patch -p1``. If level is 2,
- spack will run ``patch -p2``, and so on.
-
- A lot of people are confused by level, so here's a primer. If you
- look in your patch file, you may see something like this:
-
- .. code-block:: diff
- :linenos:
-
- --- a/src/mpi/romio/adio/ad_lustre/ad_lustre_rwcontig.c 2013-12-10 12:05:44.806417000 -0800
- +++ b/src/mpi/romio/adio/ad_lustre/ad_lustre_rwcontig.c 2013-12-10 11:53:03.295622000 -0800
- @@ -8,7 +8,7 @@
- * Copyright (C) 2008 Sun Microsystems, Lustre group
- */
-
- -#define _XOPEN_SOURCE 600
- +//#define _XOPEN_SOURCE 600
- #include <stdlib.h>
- #include <malloc.h>
- #include "ad_lustre.h"
-
- Lines 1-2 show paths with synthetic ``a/`` and ``b/`` prefixes. These
- are placeholders for the two ``mvapich2`` source directories that
- ``diff`` compared when it created the patch file. This is git's
- default behavior when creating patch files, but other programs may
- behave differently.
-
- ``-p1`` strips off the first level of the prefix in both paths,
- allowing the patch to be applied from the root of an expanded mvapich2
- archive. If you set level to ``2``, it would strip off ``src``, and
- so on.
-
- It's generally easier to just structure your patch file so that it
- applies cleanly with ``-p1``, but if you're using a patch you didn't
- create yourself, ``level`` can be handy.
-
-``patch()`` functions
-~~~~~~~~~~~~~~~~~~~~~~~~
+"""""""""
+
+This tells spack how to run the ``patch`` command. By default,
+the level is 1 and spack runs ``patch -p 1``. If level is 2,
+spack will run ``patch -p 2``, and so on.
+
+A lot of people are confused by level, so here's a primer. If you
+look in your patch file, you may see something like this:
+
+.. code-block:: diff
+ :linenos:
+
+ --- a/src/mpi/romio/adio/ad_lustre/ad_lustre_rwcontig.c 2013-12-10 12:05:44.806417000 -0800
+ +++ b/src/mpi/romio/adio/ad_lustre/ad_lustre_rwcontig.c 2013-12-10 11:53:03.295622000 -0800
+ @@ -8,7 +8,7 @@
+ * Copyright (C) 2008 Sun Microsystems, Lustre group
+ \*/
+
+ -#define _XOPEN_SOURCE 600
+ +//#define _XOPEN_SOURCE 600
+ #include <stdlib.h>
+ #include <malloc.h>
+ #include "ad_lustre.h"
+
+Lines 1-2 show paths with synthetic ``a/`` and ``b/`` prefixes. These
+are placeholders for the two ``mvapich2`` source directories that
+``diff`` compared when it created the patch file. This is git's
+default behavior when creating patch files, but other programs may
+behave differently.
+
+``-p1`` strips off the first level of the prefix in both paths,
+allowing the patch to be applied from the root of an expanded mvapich2
+archive. If you set level to ``2``, it would strip off ``src``, and
+so on.
+
+It's generally easier to just structure your patch file so that it
+applies cleanly with ``-p1``, but if you're using a patch you didn't
+create yourself, ``level`` can be handy.
+
+^^^^^^^^^^^^^^^^^^^^^
+Patch functions
+^^^^^^^^^^^^^^^^^^^^^
In addition to supplying patch files, you can write a custom function
to patch a package's source. For example, the ``py-pyside`` package
@@ -988,35 +1189,10 @@ handles ``RPATH``:
.. _pyside-patch:
-.. code-block:: python
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/py-pyside/package.py
+ :pyobject: PyPyside.patch
:linenos:
- class PyPyside(Package):
- ...
-
- def patch(self):
- """Undo PySide RPATH handling and add Spack RPATH."""
- # Figure out the special RPATH
- pypkg = self.spec['python'].package
- rpath = self.rpath
- rpath.append(os.path.join(self.prefix, pypkg.site_packages_dir, 'PySide'))
-
- # Add Spack's standard CMake args to the sub-builds.
- # They're called BY setup.py so we have to patch it.
- filter_file(
- r'OPTION_CMAKE,',
- r'OPTION_CMAKE, ' + (
- '"-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE", '
- '"-DCMAKE_INSTALL_RPATH=%s",' % ':'.join(rpath)),
- 'setup.py')
-
- # PySide tries to patch ELF files to remove RPATHs
- # Disable this and go with the one we set.
- filter_file(
- r'^\s*rpath_cmd\(pyside_path, srcpath\)',
- r'#rpath_cmd(pyside_path, srcpath)',
- 'pyside_postinstall.py')
-
A ``patch`` function, if present, will be run after patch files are
applied and before ``install()`` is run.
@@ -1025,56 +1201,57 @@ function gives you some benefits. First, spack ensures that the
``patch()`` function is run once per code checkout. That means that
if you run install, hit ctrl-C, and run install again, the code in the
patch function is only run once. Also, you can tell Spack to run only
-the patching part of the build using the :ref:`spack-patch` command.
+the patching part of the build using the :ref:`cmd-spack-patch` command.
+---------------
Handling RPATHs
-----------------------------
+---------------
Spack installs each package in a way that ensures that all of its
dependencies are found when it runs. It does this using `RPATHs
<http://en.wikipedia.org/wiki/Rpath>`_. An RPATH is a search
path, stored in a binary (an executable or library), that tells the
dynamic loader where to find its dependencies at runtime. You may be
-familiar with ```LD_LIBRARY_PATH``
+familiar with `LD_LIBRARY_PATH
<http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html>`_
-on Linux or ```DYLD_LIBRARY_PATH``
-<https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html>`
+on Linux or `DYLD_LIBRARY_PATH
+<https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/dyld.1.html>`_
on Mac OS X. RPATH is similar to these paths, in that it tells
the loader where to find libraries. Unlike them, it is embedded in
the binary and not set in each user's environment.
RPATHs in Spack are handled in one of three ways:
- 1. For most packages, RPATHs are handled automatically using Spack's
- :ref:`compiler wrappers <compiler-wrappers>`. These wrappers are
- set in standard variables like ``CC``, ``CXX``, and ``FC``, so
- most build systems (autotools and many gmake systems) pick them
- up and use them.
- 2. CMake also respects Spack's compiler wrappers, but many CMake
- builds have logic to overwrite RPATHs when binaries are
- installed. Spack provides the ``std_cmake_args`` variable, which
- includes parameters necessary for CMake build use the right
- installation RPATH. It can be used like this when ``cmake`` is
- invoked:
-
- .. code-block:: python
-
- class MyPackage(Package):
- ...
- def install(self, spec, prefix):
- cmake('..', *std_cmake_args)
- make()
- make('install')
-
- 3. If you need to modify the build to add your own RPATHs, you can
- use the ``self.rpath`` property of your package, which will
- return a list of all the RPATHs that Spack will use when it
- links. You can see this how this is used in the :ref:`PySide
- example <pyside-patch>` above.
-
-
+#. For most packages, RPATHs are handled automatically using Spack's
+ :ref:`compiler wrappers <compiler-wrappers>`. These wrappers are
+ set in standard variables like ``CC``, ``CXX``, ``F77``, and ``FC``,
+ so most build systems (autotools and many gmake systems) pick them
+ up and use them.
+#. CMake also respects Spack's compiler wrappers, but many CMake
+ builds have logic to overwrite RPATHs when binaries are
+ installed. Spack provides the ``std_cmake_args`` variable, which
+ includes parameters necessary for CMake build use the right
+ installation RPATH. It can be used like this when ``cmake`` is
+ invoked:
+
+ .. code-block:: python
+
+ class MyPackage(Package):
+ ...
+ def install(self, spec, prefix):
+ cmake('..', *std_cmake_args)
+ make()
+ make('install')
+
+#. If you need to modify the build to add your own RPATHs, you can
+ use the ``self.rpath`` property of your package, which will
+ return a list of all the RPATHs that Spack will use when it
+ links. You can see this how this is used in the :ref:`PySide
+ example <pyside-patch>` above.
+
+--------------------
Finding new versions
-----------------------------
+--------------------
You've already seen the ``homepage`` and ``url`` package attributes:
@@ -1083,6 +1260,7 @@ You've already seen the ``homepage`` and ``url`` package attributes:
from spack import *
+
class Mpich(Package):
"""MPICH is a high performance and widely portable implementation of
the Message Passing Interface (MPI) standard."""
@@ -1094,17 +1272,18 @@ information about the package, and to determine where to download its
source code.
Spack uses the tarball URL to extrapolate where to find other tarballs
-of the same package (e.g. in `spack checksum <spack-checksum_>`_, but
+of the same package (e.g. in :ref:`cmd-spack-checksum`, but
this does not always work. This section covers ways you can tell
Spack to find tarballs elsewhere.
.. _attribute_list_url:
+^^^^^^^^^^^^
``list_url``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^
When spack tries to find available versions of packages (e.g. with
-`spack checksum <spack-checksum_>`_), it spiders the parent directory
+:ref:`cmd-spack-checksum`), it spiders the parent directory
of the tarball in the ``url`` attribute. For example, for libelf, the
url is:
@@ -1135,14 +1314,17 @@ the ``list_url``, because that is where links to old versions are:
.. _attribute_list_depth:
+^^^^^^^^^^^^^^
``list_depth``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
``libdwarf`` and many other packages have a listing of available
versions on a single webpage, but not all do. For example, ``mpich``
has a tarball URL that looks like this:
- url = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
+.. code-block:: python
+
+ url = "http://www.mpich.org/static/downloads/3.0.4/mpich-3.0.4.tar.gz"
But its downloads are in many different subdirectories of
``http://www.mpich.org/static/downloads/``. So, we need to add a
@@ -1166,8 +1348,9 @@ when spidering the page.
.. _attribute_parallel:
+---------------
Parallel builds
-------------------
+---------------
By default, Spack will invoke ``make()`` with a ``-j <njobs>``
argument, so that builds run in parallel. It figures out how many
@@ -1219,11 +1402,11 @@ you set ``parallel`` to ``False`` at the package level, then each call
to ``make()`` will be sequential by default, but packagers can call
``make(parallel=True)`` to override it.
-
.. _dependencies:
+------------
Dependencies
-------------------------------
+------------
We've covered how to build a simple package, but what if one package
relies on another package to build? How do you express that in a
@@ -1250,8 +1433,9 @@ Spack makes this relatively easy. Let's take a look at the
def install(self, spec, prefix):
...
+^^^^^^^^^^^^^^^^
``depends_on()``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
The highlighted ``depends_on('libelf')`` call tells Spack that it
needs to build and install the ``libelf`` package before it builds
@@ -1259,8 +1443,9 @@ needs to build and install the ``libelf`` package before it builds
guaranteed that ``libelf`` has been built and installed successfully,
so you can rely on it for your libdwarf build.
+^^^^^^^^^^^^^^^^
Dependency specs
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
``depends_on`` doesn't just take the name of another package. It
takes a full spec. This means that you can restrict the versions or
@@ -1289,7 +1474,7 @@ Or a requirement for a particular variant or compiler flags:
depends_on("libelf@0.8+debug")
depends_on('libelf debug=True')
- depends_on('libelf cppflags="-fPIC")
+ depends_on('libelf cppflags="-fPIC"')
Both users *and* package authors can use the same spec syntax to refer
to different package configurations. Users use the spec syntax on the
@@ -1301,9 +1486,9 @@ Additionally, dependencies may be specified for specific use cases:
.. code-block:: python
- depends_on("cmake", type="build")
- depends_on("libelf", type=("build", "link"))
- depends_on("python", type="run")
+ depends_on("cmake", type="build")
+ depends_on("libelf", type=("build", "link"))
+ depends_on("python", type="run")
The dependency types are:
@@ -1316,16 +1501,31 @@ The dependency types are:
* **"run"**: the project is used by the project at runtime. The package will
be added to ``PATH`` and ``PYTHONPATH``.
-If not specified, ``type`` is assumed to be ``("build", "link")``. This is the
-common case for compiled language usage. Also available are the aliases
-``"alldeps"`` for all dependency types and ``"nolink"`` (``("build", "run")``)
-for use by dependencies which are not expressed via a linker (e.g., Python or
-Lua module loading).
+Additional hybrid dependency types are (note the lack of quotes):
+
+ * **<not specified>**: ``type`` assumed to be ``("build",
+ "link")``. This is the common case for compiled language usage.
+ * **alldeps**: All dependency types. **Note:** No quotes here
+ * **nolink**: Equal to ``("build", "run")``, for use by dependencies
+ that are not expressed via a linker (e.g., Python or Lua module
+ loading). **Note:** No quotes here
+
+"""""""""""""""""""
+Dependency Formulas
+"""""""""""""""""""
+
+This section shows how to write appropriate ``depends_on()``
+declarations for some common cases.
+
+* Python 2 only: ``depends_on('python@:2.8')``
+* Python 2.7 only: ``depends_on('python@2.7:2.8')``
+* Python 3 only: ``depends_on('python@3:')``
.. _setup-dependent-environment:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``setup_dependent_environment()``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Spack provides a mechanism for dependencies to provide variables that
can be used in their dependents' build. Any package can declare a
@@ -1349,34 +1549,16 @@ packages that depend on a particular Qt installation will find it.
The arguments to this function are:
- * **module**: the module of the dependent package, where global
- properties can be assigned.
- * **spec**: the spec of the *dependency package* (the one the function is called on).
- * **dep_spec**: the spec of the dependent package (i.e. dep_spec depends on spec).
+* **module**: the module of the dependent package, where global
+ properties can be assigned.
+* **spec**: the spec of the *dependency package* (the one the function is called on).
+* **dep_spec**: the spec of the dependent package (i.e. dep_spec depends on spec).
A good example of using these is in the Python package:
-.. code-block:: python
-
- def setup_dependent_environment(self, module, spec, dep_spec):
- # Python extension builds can have a global python executable function
- module.python = Executable(join_path(spec.prefix.bin, 'python'))
-
- # Add variables for lib/pythonX.Y and lib/pythonX.Y/site-packages dirs.
- module.python_lib_dir = os.path.join(dep_spec.prefix, self.python_lib_dir)
- module.python_include_dir = os.path.join(dep_spec.prefix, self.python_include_dir)
- module.site_packages_dir = os.path.join(dep_spec.prefix, self.site_packages_dir)
-
- # Make the site packages directory if it does not exist already.
- mkdirp(module.site_packages_dir)
-
- # Set PYTHONPATH to include site-packages dir for the
- # extension and any other python extensions it depends on.
- python_paths = []
- for d in dep_spec.traverse():
- if d.package.extends(self.spec):
- python_paths.append(os.path.join(d.prefix, self.site_packages_dir))
- os.environ['PYTHONPATH'] = ':'.join(python_paths)
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/python/package.py
+ :pyobject: Python.setup_dependent_environment
+ :linenos:
The first thing that happens here is that the ``python`` command is
inserted into module scope of the dependent. This allows most python
@@ -1385,17 +1567,17 @@ packages to have a very simple install method, like this:
.. code-block:: python
def install(self, spec, prefix):
- python('setup.py', 'install', '--prefix=%s' % prefix)
+ python('setup.py', 'install', '--prefix={0}'.format(prefix))
Python's ``setup_dependent_environment`` method also sets up some
other variables, creates a directory, and sets up the ``PYTHONPATH``
so that dependent packages can find their dependencies at build time.
-
.. _packaging_extensions:
+----------
Extensions
--------------------------
+----------
Spack's support for package extensions is documented extensively in
:ref:`extensions`. This section documents how to make your own
@@ -1426,8 +1608,19 @@ Now, the ``py-numpy`` package can be used as an argument to ``spack
activate``. When it is activated, all the files in its prefix will be
symbolically linked into the prefix of the python package.
+Some packages produce a Python extension, but are only compatible with
+Python 3, or with Python 2. In those cases, a ``depends_on()``
+declaration should be made in addition to the ``extends()``
+declaration:
+
+.. code-block:: python
+
+ class Icebin(Package):
+ extends('python', when='+python')
+ depends_on('python@3:', when='+python')
+
Many packages produce Python extensions for *some* variants, but not
-others: they should extend ``python`` only if the apropriate
+others: they should extend ``python`` only if the appropriate
variant(s) are selected. This may be accomplished with conditional
``extends()`` declarations:
@@ -1446,13 +1639,15 @@ when it does the activation:
.. code-block:: python
- class PyNose(Package):
+ class PySncosmo(Package):
...
- extends('python', ignore=r'bin/nosetests.*$')
+ # py-sncosmo binaries are duplicates of those from py-astropy
+ extends('python', ignore=r'bin/.*')
+ depends_on('py-astropy')
...
-The code above will prevent ``$prefix/bin/nosetests`` from being
-linked in at activation time.
+The code above will prevent everything in the ``$prefix/bin/`` directory
+from being linked in at activation time.
.. note::
@@ -1461,10 +1656,9 @@ linked in at activation time.
``depends_on('python')`` and ``extends(python)`` in the same
package. ``extends`` implies ``depends_on``.
-
-
+^^^^^^^^^^^^^^^^^^^^^^^^^
Activation & deactivation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^
Spack's ``Package`` class has default ``activate`` and ``deactivate``
implementations that handle symbolically linking extensions' prefixes
@@ -1482,15 +1676,9 @@ same way that Python does.
Let's look at Python's activate function:
-.. code-block:: python
-
- def activate(self, ext_pkg, **kwargs):
- kwargs.update(ignore=self.python_ignore(ext_pkg, kwargs))
- super(Python, self).activate(ext_pkg, **kwargs)
-
- exts = spack.install_layout.extension_map(self.spec)
- exts[ext_pkg.name] = ext_pkg.spec
- self.write_easy_install_pth(exts)
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/python/package.py
+ :pyobject: Python.activate
+ :linenos:
This function is called on the *extendee* (Python). It first calls
``activate`` in the superclass, which handles symlinking the
@@ -1500,23 +1688,16 @@ Python's setuptools.
Deactivate behaves similarly to activate, but it unlinks files:
-.. code-block:: python
-
- def deactivate(self, ext_pkg, **kwargs):
- kwargs.update(ignore=self.python_ignore(ext_pkg, kwargs))
- super(Python, self).deactivate(ext_pkg, **kwargs)
-
- exts = spack.install_layout.extension_map(self.spec)
- if ext_pkg.name in exts: # Make deactivate idempotent.
- del exts[ext_pkg.name]
- self.write_easy_install_pth(exts)
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/python/package.py
+ :pyobject: Python.deactivate
+ :linenos:
Both of these methods call some custom functions in the Python
package. See the source for Spack's Python package for details.
-
+^^^^^^^^^^^^^^^^^^^^
Activation arguments
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
You may have noticed that the ``activate`` function defined above
takes keyword arguments. These are the keyword arguments from
@@ -1531,11 +1712,11 @@ The only keyword argument supported by default is the ``ignore``
argument, which can take a regex, list of regexes, or a predicate to
determine which files *not* to symlink during activation.
-
.. _virtual-dependencies:
+--------------------
Virtual dependencies
------------------------------
+--------------------
In some cases, more than one package can satisfy another package's
dependency. One way this can happen is if a package depends on a
@@ -1556,8 +1737,9 @@ similar package files, e.g., ``foo``, ``foo-mvapich``, ``foo-mpich``,
but Spack avoids this explosion of package files by providing support
for *virtual dependencies*.
+^^^^^^^^^^^^
``provides``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^
In Spack, ``mpi`` is handled as a *virtual package*. A package like
``mpileaks`` can depend on it just like any other package, by
@@ -1593,8 +1775,9 @@ The ``provides("mpi")`` call tells Spack that the ``mpich`` package
can be used to satisfy the dependency of any package that
``depends_on('mpi')``.
+^^^^^^^^^^^^^^^^^^^^
Versioned Interfaces
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^
Just as you can pass a spec to ``depends_on``, so can you pass a spec
to ``provides`` to add constraints. This allows Spack to support the
@@ -1615,8 +1798,9 @@ This says that ``mpich2`` provides MPI support *up to* version 2, but
if a package ``depends_on("mpi@3")``, then Spack will *not* build that
package with ``mpich2``.
+^^^^^^^^^^^^^^^^^
``provides when``
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
The same package may provide different versions of an interface
depending on *its* version. Above, we simplified the ``provides``
@@ -1650,27 +1834,27 @@ the package ``foo`` declares this:
Suppose a user invokes ``spack install`` like this:
-.. code-block:: sh
+.. code-block:: console
$ spack install foo ^mpich@1.0
Spack will fail with a constraint violation, because the version of
MPICH requested is too low for the ``mpi`` requirement in ``foo``.
-
.. _abstract-and-concrete:
+-------------------------
Abstract & concrete specs
-------------------------------------------
+-------------------------
Now that we've seen how spec constraints can be specified :ref:`on the
command line <sec-specs>` and within package definitions, we can talk
about how Spack puts all of this information together. When you run
this:
-.. code-block:: sh
+.. code-block:: console
- spack install mpileaks ^callpath@1.0+debug ^libelf@0.8.11
+ $ spack install mpileaks ^callpath@1.0+debug ^libelf@0.8.11
Spack parses the command line and builds a spec from the description.
The spec says that ``mpileaks`` should be built with the ``callpath``
@@ -1686,7 +1870,9 @@ abstract spec is partially specified. In other words, it could
describe more than one build of a package. Spack does this to make
things easier on the user: they should only have to specify as much of
the package spec as they care about. Here's an example partial spec
-DAG, based on the constraints above::
+DAG, based on the constraints above:
+
+.. code-block:: none
mpileaks
^callpath@1.0+debug
@@ -1695,7 +1881,6 @@ DAG, based on the constraints above::
^libelf@0.8.11
^mpi
-
.. graphviz::
digraph {
@@ -1706,7 +1891,6 @@ DAG, based on the constraints above::
dyninst -> "libelf@0.8.11"
}
-
This diagram shows a spec DAG output as a tree, where successive
levels of indentation represent a depends-on relationship. In the
above DAG, we can see some packages annotated with their constraints,
@@ -1714,8 +1898,9 @@ and some packages with no annotations at all. When there are no
annotations, it means the user doesn't care what configuration of that
package is built, just so long as it works.
+^^^^^^^^^^^^^^
Concretization
-~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
An abstract spec is useful for the user, but you can't install an
abstract spec. Spack has to take the abstract spec and "fill in" the
@@ -1723,7 +1908,9 @@ remaining unspecified parts in order to install. This process is
called **concretization**. Concretization happens in between the time
the user runs ``spack install`` and the time the ``install()`` method
is called. The concretized version of the spec above might look like
-this::
+this:
+
+.. code-block:: none
mpileaks@2.3%gcc@4.7.3 arch=linux-debian7-x86_64
^callpath@1.0%gcc@4.7.3+debug arch=linux-debian7-x86_64
@@ -1753,15 +1940,16 @@ explicitly. Concretization policies are discussed in more detail in
:ref:`configuration`. Sites using Spack can customize them to match
the preferences of their own users.
-.. _spack-spec:
+.. _cmd-spack-spec:
+^^^^^^^^^^^^^^
``spack spec``
-~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
For an arbitrary spec, you can see the result of concretization by
running ``spack spec``. For example:
-.. code-block:: sh
+.. code-block:: console
$ spack spec dyninst@8.0.1
dyninst@8.0.1
@@ -1775,44 +1963,83 @@ running ``spack spec``. For example:
This is useful when you want to know exactly what Spack will do when
you ask for a particular spec.
+.. _concretization-policies:
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
``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.
-See the `documentation in the config section <concretization-preferences_>`_
-for more details.
+See the :ref:`concretization-preferences` section for more details.
.. _install-method:
+------------------
+Inconsistent Specs
+------------------
+
+Suppose a user needs to install package C, which depends on packages A
+and B. Package A builds a library with a Python2 extension, and
+package B builds a library with a Python3 extension. Packages A and B
+cannot be loaded together in the same Python runtime:
+
+.. code-block:: python
+
+ class A(Package):
+ variant('python', default=True, 'enable python bindings')
+ depends_on('python@2.7', when='+python')
+ def install(self, spec, prefix):
+ # do whatever is necessary to enable/disable python
+ # bindings according to variant
+
+ class B(Package):
+ variant('python', default=True, 'enable python bindings')
+ depends_on('python@3.2:', when='+python')
+ def install(self, spec, prefix):
+ # do whatever is necessary to enable/disable python
+ # bindings according to variant
+
+Package C needs to use the libraries from packages A and B, but does
+not need either of the Python extensions. In this case, package C
+should simply depend on the ``~python`` variant of A and B:
+
+.. code-block:: python
+
+ class C(Package):
+ depends_on('A~python')
+ depends_on('B~python')
+
+This may require that A or B be built twice, if the user wishes to use
+the Python extensions provided by them: once for ``+python`` and once
+for ``~python``. Other than using a little extra disk space, that
+solution has no serious problems.
+
+-----------------------------------
Implementing the ``install`` method
-------------------------------------------
+-----------------------------------
The last element of a package is its ``install()`` method. This is
where the real work of installation happens, and it's the main part of
the package you'll need to customize for each piece of software.
-.. literalinclude:: ../../../var/spack/repos/builtin/packages/libelf/package.py
- :start-after: 0.8.12
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/libpng/package.py
+ :pyobject: Libpng.install
:linenos:
``install`` takes a ``spec``: a description of how the package should
be built, and a ``prefix``: the path to the directory where the
software should be installed.
-
Spack provides wrapper functions for ``configure`` and ``make`` so
that you can call them in a similar way to how you'd call a shell
command. In reality, these are Python functions. Spack provides
these functions to make writing packages more natural. See the section
on :ref:`shell wrappers <shell-wrappers>`.
-
-
Now that the metadata is out of the way, we can move on to the
``install()`` method. When a user runs ``spack install``, Spack
fetches an archive for the correct version of the software, expands
@@ -1862,8 +2089,9 @@ information.
.. _install-environment:
+-----------------------
The install environment
---------------------------
+-----------------------
In general, you should not have to do much differently in your install
method than you would when installing a package on the command line.
@@ -1880,14 +2108,15 @@ custom Makefiles, you may need to add logic to modify the makefiles.
The remainder of the section covers the way Spack's build environment
works.
+^^^^^^^^^^^^^^^^^^^^^
Environment variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^
Spack sets a number of standard environment variables that serve two
purposes:
- #. Make build systems use Spack's compiler wrappers for their builds.
- #. Allow build systems to find dependencies more easily
+#. Make build systems use Spack's compiler wrappers for their builds.
+#. Allow build systems to find dependencies more easily
The Compiler environment variables that Spack sets are:
@@ -1901,7 +2130,7 @@ The Compiler environment variables that Spack sets are:
============ ===============================
All of these are standard variables respected by most build systems.
-If your project uses ``autotools`` or ``CMake``, then it should pick
+If your project uses ``Autotools`` or ``CMake``, then it should pick
them up automatically when you run ``configure`` or ``cmake`` in the
``install()`` function. Many traditional builds using GNU Make and
BSD make also respect these variables, so they may work with these
@@ -1918,12 +2147,12 @@ In addition to the compiler variables, these variables are set before
entering ``install()`` so that packages can locate dependencies
easily:
- ======================= =============================
- ``PATH`` Set to point to ``/bin`` directories of dependencies
- ``CMAKE_PREFIX_PATH`` Path to dependency prefixes for CMake
- ``PKG_CONFIG_PATH`` Path to any pkgconfig directories for dependencies
- ``PYTHONPATH`` Path to site-packages dir of any python dependencies
- ======================= =============================
+===================== ====================================================
+``PATH`` Set to point to ``/bin`` directories of dependencies
+``CMAKE_PREFIX_PATH`` Path to dependency prefixes for CMake
+``PKG_CONFIG_PATH`` Path to any pkgconfig directories for dependencies
+``PYTHONPATH`` Path to site-packages dir of any python dependencies
+===================== ====================================================
``PATH`` is set up to point to dependencies ``/bin`` directories so
that you can use tools installed by dependency packages at build time.
@@ -1934,7 +2163,7 @@ For example, ``$MPICH_ROOT/bin/mpicc`` is frequently used by dependencies of
where ``cmake`` will search for dependency libraries and headers.
This causes all standard CMake find commands to look in the paths of
your dependencies, so you *do not* have to manually specify arguments
-like ``-D DEPENDENCY_DIR=/path/to/dependency`` to ``cmake``. More on
+like ``-DDEPENDENCY_DIR=/path/to/dependency`` to ``cmake``. More on
this is `in the CMake documentation <http://www.cmake.org/cmake/help/v3.0/variable/CMAKE_PREFIX_PATH.html>`_.
``PKG_CONFIG_PATH`` is for packages that attempt to discover
@@ -1944,13 +2173,14 @@ discover its dependencies.
If you want to see the environment that a package will build with, or
if you want to run commands in that environment to test them out, you
-can use the :ref:`spack env <spack-env>` command, documented
+can use the :ref:`cmd-spack-env` command, documented
below.
.. _compiler-wrappers:
+^^^^^^^^^^^^^^^^^^^^^
Compiler interceptors
-~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^
As mentioned, ``CC``, ``CXX``, ``F77``, and ``FC`` are set to point to
Spack's compiler wrappers. These are simply called ``cc``, ``c++``,
@@ -1971,13 +2201,15 @@ flags to the compile line so that dependencies can be easily found.
These flags are added for each dependency, if they exist:
Compile-time library search paths
- * ``-L$dep_prefix/lib``
- * ``-L$dep_prefix/lib64``
+* ``-L$dep_prefix/lib``
+* ``-L$dep_prefix/lib64``
+
Runtime library search paths (RPATHs)
- * ``$rpath_flag$dep_prefix/lib``
- * ``$rpath_flag$dep_prefix/lib64``
+* ``$rpath_flag$dep_prefix/lib``
+* ``$rpath_flag$dep_prefix/lib64``
+
Include search paths
- * ``-I$dep_prefix/include``
+* ``-I$dep_prefix/include``
An example of this would be the ``libdwarf`` build, which has one
dependency: ``libelf``. Every call to ``cc`` in the ``libdwarf``
@@ -2016,12 +2248,14 @@ and/or ``ldlibs``). They do not override the canonical autotools flags with the
same names (but in ALL-CAPS) that may be passed into the build by particularly
challenging package scripts.
+^^^^^^^^^^^^^^
Compiler flags
-~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
+
In rare circumstances such as compiling and running small unit tests, a package
developer may need to know what are the appropriate compiler flags to enable
features like ``OpenMP``, ``c++11``, ``c++14`` and alike. To that end the
-compiler classes in ``spack`` implement the following _properties_ :
+compiler classes in ``spack`` implement the following **properties**:
``openmp_flag``, ``cxx11_flag``, ``cxx14_flag``, which can be accessed in a
package by ``self.compiler.cxx11_flag`` and alike. Note that the implementation
is such that if a given compiler version does not support this feature, an
@@ -2033,12 +2267,14 @@ package supports additional variants like
variant('openmp', default=True, description="Enable OpenMP support.")
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Message Parsing Interface (MPI)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
It is common for high performance computing software/packages to use ``MPI``.
As a result of conretization, a given package can be built using different
implementations of MPI such as ``Openmpi``, ``MPICH`` or ``IntelMPI``.
-In some scenarios to configure a package one have to provide it with appropriate MPI
+In some scenarios, to configure a package, one has to provide it with appropriate MPI
compiler wrappers such as ``mpicc``, ``mpic++``.
However different implementations of ``MPI`` may have different names for those
wrappers. In order to make package's ``install()`` method indifferent to the
@@ -2049,22 +2285,22 @@ Package developers are advised to use these variables, for example ``self.spec['
instead of hard-coding ``join_path(self.spec['mpi'].prefix.bin, 'mpicc')`` for
the reasons outlined above.
-
+^^^^^^^^^^^^^^^^^^^^^^^^^
Blas and Lapack libraries
-~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
Different packages provide implementation of ``Blas`` and ``Lapack`` routines.
The names of the resulting static and/or shared libraries differ from package
-to package. In order to make ``install()`` method indifferent to the
+to package. In order to make the ``install()`` method independent of the
choice of ``Blas`` implementation, each package which provides it
-sets up ``self.spec.blas_shared_lib`` and ``self.spec.blas_static_lib `` to
-point to the shared and static ``Blas`` libraries, respectively. The same
-applies to packages which provide ``Lapack``. Package developers are advised to
-use these variables, for example ``spec['blas'].blas_shared_lib`` instead of
+sets up ``self.spec.blas_libs`` to point to the correct ``Blas`` libraries.
+The same applies to packages which provide ``Lapack``. Package developers are advised to
+use these variables, for example ``spec['blas'].blas_libs.joined()`` instead of
hard-coding ``join_path(spec['blas'].prefix.lib, 'libopenblas.so')``.
-
+^^^^^^^^^^^^^^^^^^^^^
Forking ``install()``
-~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^
To give packagers free reign over their install environment, Spack
forks a new process each time it invokes a package's ``install()``
@@ -2076,9 +2312,9 @@ dedicated process.
.. _prefix-objects:
-
+-----------------
Failing the build
-----------------------
+-----------------
Sometimes you don't want a package to successfully install unless some
condition is true. You can explicitly cause the build to fail from
@@ -2089,9 +2325,9 @@ condition is true. You can explicitly cause the build to fail from
if spec.architecture.startswith('darwin'):
raise InstallError('This package does not build on Mac OS X!')
-
+--------------
Prefix objects
-----------------------
+--------------
Spack passes the ``prefix`` parameter to the install method so that
you can pass it to ``configure``, ``cmake``, or some other installer,
@@ -2101,7 +2337,6 @@ e.g.:
configure('--prefix=' + prefix)
-
For the most part, prefix objects behave exactly like strings. For
packages that do not have their own install target, or for those that
implement it poorly (like ``libdwarf``), you may need to manually copy
@@ -2121,7 +2356,6 @@ yourself, e.g.:
mkdirp(prefix.lib)
install('libfoo.a', prefix.lib)
-
Most of the standard UNIX directory names are attributes on the
``prefix`` object. Here is a full list:
@@ -2148,8 +2382,9 @@ Most of the standard UNIX directory names are attributes on the
.. _spec-objects:
+------------
Spec objects
--------------------------
+------------
When ``install`` is called, most parts of the build process are set up
for you. The correct version's tarball has been downloaded and
@@ -2166,8 +2401,9 @@ special parameters to ``configure``, like
need to supply special compiler flags depending on the compiler. All
of this information is available in the spec.
+^^^^^^^^^^^^^^^^^^^^^^^^
Testing spec constraints
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^
You can test whether your spec is configured a certain way by using
the ``satisfies`` method. For example, if you want to check whether
@@ -2176,9 +2412,14 @@ do that, e.g.:
.. code-block:: python
+ configure_args = [
+ '--prefix={0}'.format(prefix)
+ ]
+
if spec.satisfies('@1.2:1.4'):
configure_args.append("CXXFLAGS='-DWITH_FEATURE'")
- configure('--prefix=' + prefix, *configure_args)
+
+ configure(*configure_args)
This works for compilers, too:
@@ -2232,39 +2473,40 @@ the two functions is that ``satisfies()`` tests whether spec
constraints overlap at all, while ``in`` tests whether a spec or any
of its dependencies satisfy the provided spec.
-
+^^^^^^^^^^^^^^^^^^^^^^
Accessing Dependencies
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^
You may need to get at some file or binary that's in the prefix of one
of your dependencies. You can do that by sub-scripting the spec:
.. code-block:: python
- my_mpi = spec['mpich']
+ my_mpi = spec['mpi']
The value in the brackets needs to be some package name, and spec
needs to depend on that package, or the operation will fail. For
example, the above code will fail if the ``spec`` doesn't depend on
-``mpich``. The value returned and assigned to ``my_mpi``, is itself
+``mpi``. The value returned and assigned to ``my_mpi``, is itself
just another ``Spec`` object, so you can do all the same things you
would do with the package's own spec:
.. code-block:: python
- mpicc = new_path(my_mpi.prefix.bin, 'mpicc')
+ mpicc = join_path(my_mpi.prefix.bin, 'mpicc')
.. _multimethods:
+^^^^^^^^^^^^^^^^^^^^^^^^^^
Multimethods and ``@when``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^
Spack allows you to make multiple versions of instance functions in
packages, based on whether the package's spec satisfies particular
criteria.
The ``@when`` annotation lets packages declare multiple versions of
-methods like install() that depend on the package's spec. For
+methods like ``install()`` that depend on the package's spec. For
example:
.. code-block:: python
@@ -2275,16 +2517,17 @@ example:
def install(self, prefix):
# Do default install
- @when('arch=linux-debian7-x86_64')
+ @when('arch=chaos_5_x86_64_ib')
def install(self, prefix):
# This will be executed instead of the default install if
# the package's sys_type() is chaos_5_x86_64_ib.
- @when('arch=linux-debian7-x86_64")
+ @when('arch=linux-debian7-x86_64')
def install(self, prefix):
- # This will be executed if the package's sys_type is bgqos_0
+ # This will be executed if the package's sys_type() is
+ # linux-debian7-x86_64.
-In the above code there are three versions of install(), two of which
+In the above code there are three versions of ``install()``, two of which
are specialized for particular platforms. The version that is called
depends on the architecture of the package spec.
@@ -2356,22 +2599,15 @@ method (the one without the ``@when`` decorator) will be called.
.. _shell-wrappers:
+-----------------------
Shell command functions
-----------------------------
+-----------------------
Recall the install method from ``libelf``:
-.. code-block:: python
-
- def install(self, spec, prefix):
- configure("--prefix=" + prefix,
- "--enable-shared",
- "--disable-dependency-tracking",
- "--disable-debug")
- make()
-
- # The mkdir commands in libelf's install can fail in parallel
- make("install", parallel=False)
+.. literalinclude:: ../../../var/spack/repos/builtin/packages/libelf/package.py
+ :pyobject: Libelf.install
+ :linenos:
Normally in Python, you'd have to write something like this in order
to execute shell commands:
@@ -2379,7 +2615,7 @@ to execute shell commands:
.. code-block:: python
import subprocess
- subprocess.check_call('configure', '--prefix=' + prefix)
+ subprocess.check_call('configure', '--prefix={0}'.format(prefix))
We've tried to make this a bit easier by providing callable wrapper
objects for some shell commands. By default, ``configure``,
@@ -2399,17 +2635,17 @@ Callable wrappers also allow spack to provide some special features.
For example, in Spack, ``make`` is parallel by default, and Spack
figures out the number of cores on your machine and passes an
appropriate value for ``-j<numjobs>`` when it calls ``make`` (see the
-``parallel`` package attribute under :ref:`metadata <metadata>`). In
+``parallel`` `package attribute <attribute_parallel>`). In
a package file, you can supply a keyword argument, ``parallel=False``,
to the ``make`` wrapper to disable parallel make. In the ``libelf``
package, this allows us to avoid race conditions in the library's
build system.
-
.. _sanity-checks:
+-------------------------------
Sanity checking an installation
---------------------------------
+-------------------------------
By default, Spack assumes that a build has failed if nothing is
written to the install prefix, and that it has succeeded if anything
@@ -2421,7 +2657,7 @@ Consider a simple autotools build like this:
.. code-block:: python
def install(self, spec, prefix):
- configure("--prefix=" + prefix)
+ configure("--prefix={0}".format(prefix))
make()
make("install")
@@ -2434,9 +2670,9 @@ 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:
@@ -2458,14 +2694,14 @@ 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
+succeed, Spack considers the build successful and keeps the prefix in
place.
-
.. _file-manipulation:
+---------------------------
File manipulation functions
-------------------------------
+---------------------------
Many builds are not perfect. If a build lacks an install target, or if
it does not use systems like CMake or autotools, which have standard
@@ -2487,9 +2723,9 @@ running:
This is already part of the boilerplate for packages created with
``spack create`` or ``spack edit``.
-
+^^^^^^^^^^^^^^^^^^^
Filtering functions
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^
:py:func:`filter_file(regex, repl, *filenames, **kwargs) <spack.filter_file>`
Works like ``sed`` but with Python regular expression syntax. Takes
@@ -2547,9 +2783,9 @@ Filtering functions
change_sed_delimiter('@', ';', 'utils/FixMakefile')
change_sed_delimiter('@', ';', 'utils/FixMakefile.sed.default')
-
+^^^^^^^^^^^^^^
File functions
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^
:py:func:`ancestor(dir, n=1) <spack.ancestor>`
Get the n\ :sup:`th` ancestor of the directory ``dir``.
@@ -2566,8 +2802,8 @@ File functions
install('my-header.h', join_path(prefix.include))
-:py:func:`join_path(prefix, *args) <spack.join_path>` Like
- ``os.path.join``, this joins paths using the OS path separator.
+:py:func:`join_path(prefix, *args) <spack.join_path>`
+ Like ``os.path.join``, this joins paths using the OS path separator.
However, this version allows an arbitrary number of arguments, so
you can string together many path components.
@@ -2617,22 +2853,21 @@ File functions
The ``create=True`` keyword argument causes the command to create
the directory if it does not exist.
-
:py:func:`touch(path) <spack.touch>`
Create an empty file at ``path``.
-
.. _package-lifecycle:
+-----------------------
Coding Style Guidelines
----------------------------
+-----------------------
The following guidelines are provided, in the interests of making
Spack packages work in a consistent manner:
-
+^^^^^^^^^^^^^
Variant Names
-~~~~~~~~~~~~~~
+^^^^^^^^^^^^^
Spack packages with variants similar to already-existing Spack
packages should use the same name for their variants. Standard
@@ -2642,43 +2877,54 @@ variant names are:
Name Default Description
======= ======== ========================
shared True Build shared libraries
- static Build static libraries
- mpi Use MPI
- python Build Python extension
+ static True Build static libraries
+ mpi True Use MPI
+ python False Build Python extension
======= ======== ========================
If specified in this table, the corresponding default should be used
when declaring a variant.
-
+^^^^^^^^^^^^^
Version Lists
-~~~~~~~~~~~~~~
+^^^^^^^^^^^^^
-Spack packges should list supported versions with the newest first.
+Spack packages should list supported versions with the newest first.
+^^^^^^^^^^^^^^^^
Special Versions
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^
The following *special* version names may be used when building a package:
-* *@system*: Indicates a hook to the OS-installed version of the
- package. This is useful, for example, to tell Spack to use the
- OS-installed version in ``packages.yaml``::
+"""""""""""
+``@system``
+"""""""""""
+
+Indicates a hook to the OS-installed version of the
+package. This is useful, for example, to tell Spack to use the
+OS-installed version in ``packages.yaml``:
- openssl:
- paths:
- openssl@system: /usr
- buildable: False
+.. code-block:: yaml
- Certain Spack internals look for the *@system* version and do
- appropriate things in that case.
+ openssl:
+ paths:
+ openssl@system: /usr
+ buildable: False
-* *@local*: Indicates the version was built manually from some source
- tree of unknown provenance (see ``spack setup``).
+Certain Spack internals look for the ``@system`` version and do
+appropriate things in that case.
+""""""""""
+``@local``
+""""""""""
+Indicates the version was built manually from some source
+tree of unknown provenance (see ``spack setup``).
+
+---------------------------
Packaging workflow commands
----------------------------------
+---------------------------
When you are building packages, you will likely not get things
completely right the first time.
@@ -2693,7 +2939,7 @@ of the build.
A typical package workflow might look like this:
-.. code-block:: sh
+.. code-block:: console
$ spack edit mypackage
$ spack install mypackage
@@ -2706,10 +2952,11 @@ A typical package workflow might look like this:
Below are some commands that will allow you some finer-grained
control over the install process.
-.. _spack-fetch:
+.. _cmd-spack-fetch:
+^^^^^^^^^^^^^^^
``spack fetch``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
The first step of ``spack install``. Takes a spec and determines the
correct download URL to use for the requested package version, then
@@ -2720,20 +2967,22 @@ directory will be located under ``$SPACK_HOME/var/spack``.
When run after the archive has already been downloaded, ``spack
fetch`` is idempotent and will not download the archive again.
-.. _spack-stage:
+.. _cmd-spack-stage:
+^^^^^^^^^^^^^^^
``spack stage``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
The second step in ``spack install`` after ``spack fetch``. Expands
the downloaded archive in its temporary directory, where it will be
built by ``spack install``. Similar to ``fetch``, if the archive has
already been expanded, ``stage`` is idempotent.
-.. _spack-patch:
+.. _cmd-spack-patch:
+^^^^^^^^^^^^^^^
``spack patch``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
After staging, Spack applies patches to downloaded packages, if any
have been specified in the package file. This command will run the
@@ -2743,187 +2992,167 @@ this step if they have been. If Spack discovers that patches didn't
apply cleanly on some previous run, then it will restage the entire
package before patching.
-.. _spack-restage:
+.. _cmd-spack-restage:
+^^^^^^^^^^^^^^^^^
``spack restage``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^
+
Restores the source code to pristine state, as it was before building.
Does this in one of two ways:
- 1. If the source was fetched as a tarball, deletes the entire build
- directory and re-expands the tarball.
+#. If the source was fetched as a tarball, deletes the entire build
+ directory and re-expands the tarball.
- 2. If the source was checked out from a repository, this deletes the
- build directory and checks it out again.
+#. If the source was checked out from a repository, this deletes the
+ build directory and checks it out again.
-.. _spack-clean:
+.. _cmd-spack-clean:
+^^^^^^^^^^^^^^^
``spack clean``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
+
Cleans up temporary files for a particular package, by deleting the
expanded/checked out source code *and* any downloaded archive. If
``fetch``, ``stage``, or ``install`` are run again after this, Spack's
build process will start from scratch.
+.. _cmd-spack-purge:
-.. _spack-purge:
-
+^^^^^^^^^^^^^^^
``spack purge``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
+
Cleans up all of Spack's temporary and cached files. This can be used to
recover disk space if temporary files from interrupted or failed installs
accumulate in the staging area.
-When called with ``--stage`` or ``--all`` (or without arguments, in which case
-the default is ``--all``) this removes all staged files; this is equivalent to
-running ``spack clean`` for every package you have fetched or staged.
+When called with ``--stage`` or without arguments this removes all staged
+files and will be equivalent to running ``spack clean`` for every package
+you have fetched or staged.
-When called with ``--cache`` or ``--all`` this will clear all resources
+When called with ``--downloads`` this will clear all resources
:ref:`cached <caching>` during installs.
+When called with ``--user-cache`` this will remove caches in the user home
+directory, including cached virtual indices.
+
+To remove all of the above, the command can be called with ``--all``.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Keeping the stage directory on success
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, ``spack install`` will delete the staging area once a
package has been successfully built and installed. Use
``--keep-stage`` to leave the build directory intact:
-.. code-block:: sh
+.. code-block:: console
- spack install --keep-stage <spec>
+ $ spack install --keep-stage <spec>
This allows you to inspect the build directory and potentially debug
the build. You can use ``purge`` or ``clean`` later to get rid of the
unwanted temporary files.
-
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Keeping the install prefix on failure
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, ``spack install`` will delete any partially constructed
install prefix if anything fails during ``install()``. If you want to
keep the prefix anyway (e.g. to diagnose a bug), you can use
``--keep-prefix``:
-.. code-block:: sh
+.. code-block:: console
- spack install --keep-prefix <spec>
+ $ spack install --keep-prefix <spec>
Note that this may confuse Spack into thinking that the package has
-been installed properly, so you may need to use ``spack uninstall -f``
+been installed properly, so you may need to use ``spack uninstall --force``
to get rid of the install prefix before you build again:
-.. code-block:: sh
-
- spack uninstall -f <spec>
+.. code-block:: console
+ $ spack uninstall --force <spec>
+---------------------
Graphing dependencies
---------------------------
+---------------------
-.. _spack-graph:
+.. _cmd-spack-graph:
+^^^^^^^^^^^^^^^
``spack graph``
-~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^
Spack provides the ``spack graph`` command for graphing dependencies.
The command by default generates an ASCII rendering of a spec's
-dependency graph. For example::
-
- $ spack graph mpileaks
- o mpileaks
- |\
- | |\
- | o | callpath
- |/| |
- | |\|
- | |\ \
- | | |\ \
- | | | | o adept-utils
- | |_|_|/|
- |/| | | |
- o | | | | mpi
- / / / /
- | | o | dyninst
- | |/| |
- |/|/| |
- | | |/
- | o | libdwarf
- |/ /
- o | libelf
- /
- o boost
-
-At the top is the root package in the DAG, with dependency edges
-emerging from it. On a color terminal, the edges are colored by which
-dependency they lead to.
+dependency graph. For example:
+
+.. command-output:: spack graph mpileaks
+
+At the top is the root package in the DAG, with dependency edges emerging
+from it. On a color terminal, the edges are colored by which dependency
+they lead to.
+
+.. command-output:: spack graph --deptype=all mpileaks
+
+The ``deptype`` argument tells Spack what types of dependencies to graph.
+By default it includes link and run dependencies but not build
+dependencies. Supplying ``--deptype=all`` will show the build
+dependencies as well. This is equivalent to
+``--deptype=build,link,run``. Options for ``deptype`` include:
+
+* Any combination of ``build``, ``link``, and ``run`` separated by
+ commas.
+* ``nobuild``, ``nolink``, ``norun`` to omit one type.
+* ``all`` or ``alldeps`` for all types of dependencies.
You can also use ``spack graph`` to generate graphs in the widely used
`Dot <http://www.graphviz.org/doc/info/lang.html>`_ format. For
-example::
-
- $ spack graph --dot mpileaks
- digraph G {
- label = "Spack Dependencies"
- labelloc = "b"
- rankdir = "LR"
- ranksep = "5"
-
- "boost" [label="boost"]
- "callpath" [label="callpath"]
- "libdwarf" [label="libdwarf"]
- "mpileaks" [label="mpileaks"]
- "mpi" [label="mpi"]
- "adept-utils" [label="adept-utils"]
- "dyninst" [label="dyninst"]
- "libelf" [label="libelf"]
-
- "callpath" -> "dyninst"
- "callpath" -> "adept-utils"
- "callpath" -> "mpi"
- "callpath" -> "libelf"
- "callpath" -> "libdwarf"
- "libdwarf" -> "libelf"
- "mpileaks" -> "adept-utils"
- "mpileaks" -> "callpath"
- "mpileaks" -> "mpi"
- "adept-utils" -> "boost"
- "adept-utils" -> "mpi"
- "dyninst" -> "boost"
- "dyninst" -> "libelf"
- "dyninst" -> "libdwarf"
- }
+example:
+
+.. command-output:: spack graph --dot mpileaks
This graph can be provided as input to other graphing tools, such as
those in `Graphviz <http://www.graphviz.org>`_.
+-------------------------
Interactive shell support
---------------------------
+-------------------------
Spack provides some limited shell support to make life easier for
packagers. You can enable these commands by sourcing a setup file in
-the ``/share/spack`` directory. For ``bash`` or ``ksh``, run::
+the ``share/spack`` directory. For ``bash`` or ``ksh``, run:
+
+.. code-block:: sh
- . $SPACK_ROOT/share/spack/setup-env.sh
+ export SPACK_ROOT=/path/to/spack
+ . $SPACK_ROOT/share/spack/setup-env.sh
For ``csh`` and ``tcsh`` run:
- setenv SPACK_ROOT /path/to/spack
- source $SPACK_ROOT/share/spack/setup-env.csh
+.. code-block:: csh
+
+ setenv SPACK_ROOT /path/to/spack
+ source $SPACK_ROOT/share/spack/setup-env.csh
``spack cd`` will then be available.
-.. _spack-cd:
+.. _cmd-spack-cd:
+^^^^^^^^^^^^
``spack cd``
-~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^
``spack cd`` allows you to quickly cd to pertinent directories in Spack.
Suppose you've staged a package but you want to modify it before you
build it:
-.. code-block:: sh
+.. code-block:: console
$ spack stage libelf
==> Trying to fetch from http://www.mr511.de/software/libelf-0.8.13.tar.gz
@@ -2934,37 +3163,39 @@ build it:
$ pwd
/Users/gamblin2/src/spack/var/spack/stage/libelf@0.8.13%gcc@4.8.3 arch=linux-debian7-x86_64/libelf-0.8.13
-``spack cd`` here changed he current working directory to the
+``spack cd`` here changed the current working directory to the
directory containing the expanded ``libelf`` source code. There are a
number of other places you can cd to in the spack directory hierarchy:
-.. command-output:: spack cd -h
+.. command-output:: spack cd --help
Some of these change directory into package-specific locations (stage
directory, install directory, package directory) and others change to
-core spack locations. For example, ``spack cd -m`` will take you to
+core spack locations. For example, ``spack cd --module-dir`` will take you to
the main python source directory of your spack install.
-.. _spack-env:
+.. _cmd-spack-env:
+^^^^^^^^^^^^^
``spack env``
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^
``spack env`` functions much like the standard unix ``env`` command,
but it takes a spec as an argument. You can use it to see the
environment variables that will be set when a particular build runs,
for example:
-.. code-block:: sh
+.. code-block:: console
$ spack env mpileaks@1.1%intel
This will display the entire environment that will be set when the
``mpileaks@1.1%intel`` build runs.
-To run commands in a package's build environment, you can simply provided them after the spec argument to ``spack env``:
+To run commands in a package's build environment, you can simply
+provide them after the spec argument to ``spack env``:
-.. code-block:: sh
+.. code-block:: console
$ spack cd mpileaks@1.1%intel
$ spack env mpileaks@1.1%intel ./configure
@@ -2972,21 +3203,25 @@ To run commands in a package's build environment, you can simply provided them a
This will cd to the build directory and then run ``configure`` in the
package's build environment.
+.. _cmd-spack-location:
-.. _spack-location:
-
+^^^^^^^^^^^^^^^^^^
``spack location``
-~~~~~~~~~~~~~~~~~~~~~~
+^^^^^^^^^^^^^^^^^^
``spack location`` is the same as ``spack cd`` but it does not require
shell support. It simply prints out the path you ask for, rather than
-cd'ing to it. In bash, this::
+cd'ing to it. In bash, this:
+
+.. code-block:: console
- cd $(spack location -b <spec>)
+ $ cd $(spack location --build-dir <spec>)
-is the same as::
+is the same as:
- spack cd -b <spec>
+.. code-block:: console
+
+ $ spack cd --build-dir <spec>
``spack location`` is intended for use in scripts or makefiles that
need to know where packages are installed. e.g., in a makefile you
@@ -2994,59 +3229,62 @@ might write:
.. code-block:: makefile
- DWARF_PREFIX = $(spack location -i libdwarf)
+ DWARF_PREFIX = $(spack location --install-dir libdwarf)
CXXFLAGS += -I$DWARF_PREFIX/include
CXXFLAGS += -L$DWARF_PREFIX/lib
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Build System Configuration Support
-----------------------------------
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Imagine a developer creating a CMake-based (or Autotools) project in a local
-directory, which depends on libraries A-Z. Once Spack has installed
-those dependencies, one would like to run ``cmake`` with appropriate
-command line and environment so CMake can find them. The ``spack
-setup`` command does this conveniently, producing a CMake
+Imagine a developer creating a CMake or Autotools-based project in a
+local directory, which depends on libraries A-Z. Once Spack has
+installed those dependencies, one would like to run ``cmake`` with
+appropriate command line and environment so CMake can find them. The
+``spack setup`` command does this conveniently, producing a CMake
configuration that is essentially the same as how Spack *would have*
configured the project. This can be demonstrated with a usage
example:
-.. code-block:: bash
+.. code-block:: console
- cd myproject
- spack setup myproject@local
- mkdir build; cd build
- ../spconfig.py ..
- make
- make install
+ $ cd myproject
+ $ spack setup myproject@local
+ $ mkdir build; cd build
+ $ ../spconfig.py ..
+ $ make
+ $ make install
Notes:
- * Spack must have ``myproject/package.py`` in its repository for
- this to work.
- * ``spack setup`` produces the executable script ``spconfig.py`` in
- the local directory, and also creates the module file for the
- package. ``spconfig.py`` is normally run from the user's
- out-of-source build directory.
- * The version number given to ``spack setup`` is arbitrary, just
- like ``spack diy``. ``myproject/package.py`` does not need to
- have any valid downloadable versions listed (typical when a
- project is new).
- * spconfig.py produces a CMake configuration that *does not* use the
- Spack wrappers. Any resulting binaries *will not* use RPATH,
- unless the user has enabled it. This is recommended for
- development purposes, not production.
- * ``spconfig.py`` is human readable, and can serve as a developer
- reference of what dependencies are being used.
- * ``make install`` installs the package into the Spack repository,
- where it may be used by other Spack packages.
- * CMake-generated makefiles re-run CMake in some circumstances. Use
- of ``spconfig.py`` breaks this behavior, requiring the developer
- to manually re-run ``spconfig.py`` when a ``CMakeLists.txt`` file
- has changed.
+* Spack must have ``myproject/package.py`` in its repository for
+ this to work.
+* ``spack setup`` produces the executable script ``spconfig.py`` in
+ the local directory, and also creates the module file for the
+ package. ``spconfig.py`` is normally run from the user's
+ out-of-source build directory.
+* The version number given to ``spack setup`` is arbitrary, just
+ like ``spack diy``. ``myproject/package.py`` does not need to
+ have any valid downloadable versions listed (typical when a
+ project is new).
+* spconfig.py produces a CMake configuration that *does not* use the
+ Spack wrappers. Any resulting binaries *will not* use RPATH,
+ unless the user has enabled it. This is recommended for
+ development purposes, not production.
+* ``spconfig.py`` is human readable, and can serve as a developer
+ reference of what dependencies are being used.
+* ``make install`` installs the package into the Spack repository,
+ where it may be used by other Spack packages.
+* CMake-generated makefiles re-run CMake in some circumstances. Use
+ of ``spconfig.py`` breaks this behavior, requiring the developer
+ to manually re-run ``spconfig.py`` when a ``CMakeLists.txt`` file
+ has changed.
+
+^^^^^^^^^^^^
CMakePackage
-~~~~~~~~~~~~
+^^^^^^^^^^^^
-In order ot enable ``spack setup`` functionality, the author of
+In order to enable ``spack setup`` functionality, the author of
``myproject/package.py`` must subclass from ``CMakePackage`` instead
of the standard ``Package`` superclass. Because CMake is
standardized, the packager does not need to tell Spack how to run
@@ -3058,21 +3296,22 @@ translate variant flags into CMake definitions. For example:
.. code-block:: python
- def configure_args(self):
- spec = self.spec
- return [
- '-DUSE_EVERYTRACE=%s' % ('YES' if '+everytrace' in spec else 'NO'),
- '-DBUILD_PYTHON=%s' % ('YES' if '+python' in spec else 'NO'),
- '-DBUILD_GRIDGEN=%s' % ('YES' if '+gridgen' in spec else 'NO'),
- '-DBUILD_COUPLER=%s' % ('YES' if '+coupler' in spec else 'NO'),
- '-DUSE_PISM=%s' % ('YES' if '+pism' in spec else 'NO')]
+ def configure_args(self):
+ spec = self.spec
+ return [
+ '-DUSE_EVERYTRACE=%s' % ('YES' if '+everytrace' in spec else 'NO'),
+ '-DBUILD_PYTHON=%s' % ('YES' if '+python' in spec else 'NO'),
+ '-DBUILD_GRIDGEN=%s' % ('YES' if '+gridgen' in spec else 'NO'),
+ '-DBUILD_COUPLER=%s' % ('YES' if '+coupler' in spec else 'NO'),
+ '-DUSE_PISM=%s' % ('YES' if '+pism' in spec else 'NO')
+ ]
If needed, a packager may also override methods defined in
``StagedPackage`` (see below).
-
+^^^^^^^^^^^^^
StagedPackage
-~~~~~~~~~~~~~
+^^^^^^^^^^^^^
``CMakePackage`` is implemented by subclassing the ``StagedPackage``
superclass, which breaks down the standard ``Package.install()``
@@ -3094,8 +3333,9 @@ and ``install``. Details:
``install()`` method may be accessed via ``self.spec`` and
``self.prefix``.
+^^^^^^^^^^^^^
GNU Autotools
-~~~~~~~~~~~~~
+^^^^^^^^^^^^^
The ``setup`` functionality is currently only available for
CMake-based packages. Extending this functionality to GNU
diff --git a/lib/spack/docs/packaging_tutorial/0.package.py b/lib/spack/docs/packaging_tutorial/0.package.py
new file mode 100644
index 0000000000..ad6fd4d077
--- /dev/null
+++ b/lib/spack/docs/packaging_tutorial/0.package.py
@@ -0,0 +1,64 @@
+# flake8: noqa
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 is a template package file for Spack. We've put "FIXME"
+# next to all the things you'll want to change. Once you've handled
+# them, you can save this file and test your package like this:
+#
+# spack install mpileaks
+#
+# You can edit this file again by typing:
+#
+# spack edit mpileaks
+#
+# See the Spack documentation for more information on packaging.
+# If you submit this package back to Spack as a pull request,
+# please first remove this boilerplate and all FIXME comments.
+#
+from spack import *
+
+
+class Mpileaks(AutotoolsPackage):
+ """FIXME: Put a proper description of your package here."""
+
+ # FIXME: Add a proper url for your package's homepage here.
+ homepage = "http://www.example.com"
+ url = "https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz"
+
+ version('1.0', '8838c574b39202a57d7c2d68692718aa')
+
+ # FIXME: Add dependencies if required.
+ # depends_on('m4', type='build')
+ # depends_on('autoconf', type='build')
+ # depends_on('automake', type='build')
+ # depends_on('libtool', type='build')
+ # depends_on('foo')
+
+ def configure_args(self):
+ # FIXME: Add arguments other than --prefix
+ # FIXME: If not needed delete the function
+ args = []
+ return args
diff --git a/lib/spack/spack/schema/targets.py b/lib/spack/docs/packaging_tutorial/1.package.py
index 312474cab4..afacf3bbe8 100644
--- a/lib/spack/spack/schema/targets.py
+++ b/lib/spack/docs/packaging_tutorial/1.package.py
@@ -22,24 +22,28 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for target configuration files."""
+# flake8: noqa
+from spack import *
-schema = {
- '$schema': 'http://json-schema.org/schema#',
- 'title': 'Spack target configuration file schema',
- 'type': 'object',
- 'additionalProperties': False,
- 'patternProperties': {
- r'targets:?': {
- 'type': 'object',
- 'default': {},
- 'additionalProperties': False,
- 'patternProperties': {
- r'\w[\w-]*': { # target name
- 'type': 'string',
- },
- },
- },
- },
-}
+
+class Mpileaks(AutotoolsPackage):
+ """Tool to detect and report MPI objects like MPI_Requests and MPI_Datatypes"""
+
+ homepage = "https://github.com/hpc/mpileaks"
+ url = "https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz"
+
+ version('1.0', '8838c574b39202a57d7c2d68692718aa')
+
+ # FIXME: Add dependencies if required.
+ # depends_on('m4', type='build')
+ # depends_on('autoconf', type='build')
+ # depends_on('automake', type='build')
+ # depends_on('libtool', type='build')
+ # depends_on('foo')
+
+ def configure_args(self):
+ # FIXME: Add arguments other than --prefix
+ # FIXME: If not needed delete the function
+ args = []
+ return args
diff --git a/lib/spack/docs/packaging_tutorial/2.package.py b/lib/spack/docs/packaging_tutorial/2.package.py
new file mode 100644
index 0000000000..f7000cd1b6
--- /dev/null
+++ b/lib/spack/docs/packaging_tutorial/2.package.py
@@ -0,0 +1,21 @@
+# flake8: noqa
+
+from spack import *
+
+class Mpileaks(AutotoolsPackage):
+ """Tool to detect and report MPI objects like MPI_Requests and MPI_Datatypes"""
+
+ homepage = "https://github.com/hpc/mpileaks"
+ url = "https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz"
+
+ version('1.0', '8838c574b39202a57d7c2d68692718aa')
+
+ depends_on('mpi')
+ depends_on('adept-utils')
+ depends_on('callpath')
+
+ def configure_args(self):
+ # FIXME: Add arguments other than --prefix
+ # FIXME: If not needed delete the function
+ args = []
+ return args
diff --git a/lib/spack/docs/packaging_tutorial/3.package.py b/lib/spack/docs/packaging_tutorial/3.package.py
new file mode 100644
index 0000000000..6e7e5e2f3c
--- /dev/null
+++ b/lib/spack/docs/packaging_tutorial/3.package.py
@@ -0,0 +1,17 @@
+# flake8: noqa
+
+class Mpileaks(AutotoolsPackage):
+ """Tool to detect and report MPI objects like MPI_Requests and MPI_Datatypes"""
+ homepage = "https://github.com/hpc/mpileaks"
+ url = "https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz"
+
+ version('1.0', '8838c574b39202a57d7c2d68692718aa')
+
+ depends_on('mpi')
+ depends_on('adept-utils')
+ depends_on('callpath')
+
+ def configure_args(self):
+ args = ['--with-adept-utils=%s' % self.spec['adept-utils'].prefix,
+ '--with-callpath=%s' % self.spec['callpath'].prefix]
+ return args
diff --git a/lib/spack/docs/packaging_tutorial/4.package.py b/lib/spack/docs/packaging_tutorial/4.package.py
new file mode 100644
index 0000000000..3294b86e25
--- /dev/null
+++ b/lib/spack/docs/packaging_tutorial/4.package.py
@@ -0,0 +1,26 @@
+# flake8: noqa
+
+from spack import *
+
+class Mpileaks(AutotoolsPackage):
+ """Tool to detect and report MPI objects like MPI_Requests and MPI_Datatypes"""
+
+ homepage = "https://github.com/hpc/mpileaks"
+ url = "https://github.com/hpc/mpileaks/releases/download/v1.0/mpileaks-1.0.tar.gz"
+
+ version('1.0', '8838c574b39202a57d7c2d68692718aa')
+
+ variant('stackstart', default=0, description='Specify the number of stack frames to truncate.')
+
+ depends_on('mpi')
+ depends_on('adept-utils')
+ depends_on('callpath')
+
+ def configure_args(self):
+ args = ['--with-adept-utils=%s' % self.spec['adept-utils'].prefix,
+ '--with-callpath=%s' % self.spec['callpath'].prefix]
+ stackstart = int(self.spec.variants['stackstart'].value)
+ if stackstart:
+ args.extend(['--with-stack-start-c=%s' % stackstart,
+ '--with-stack-start-fortran=%s' % stackstart])
+ return args
diff --git a/lib/spack/docs/workflows.rst b/lib/spack/docs/workflows.rst
new file mode 100644
index 0000000000..314f57c8b5
--- /dev/null
+++ b/lib/spack/docs/workflows.rst
@@ -0,0 +1,1198 @@
+=========
+Workflows
+=========
+
+The process of using Spack involves building packages, running
+binaries from those packages, and developing software that depends on
+those packages. For example, one might use Spack to build the
+``netcdf`` package, use ``spack load`` to run the ``ncdump`` binary, and
+finally, write a small C program to read/write a particular NetCDF file.
+
+Spack supports a variety of workflows to suit a variety of situations
+and user preferences, there is no single way to do all these things.
+This chapter demonstrates different workflows that have been
+developed, pointing out the pros and cons of them.
+
+-----------
+Definitions
+-----------
+
+First some basic definitions.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Package, Concrete Spec, Installed Package
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In Spack, a package is an abstract recipe to build one piece of software.
+Spack packages may be used to build, in principle, any version of that
+software with any set of variants. Examples of packages include
+``curl`` and ``zlib``.
+
+A package may be *instantiated* to produce a concrete spec; one
+possible realization of a particular package, out of combinatorially
+many other realizations. For example, here is a concrete spec
+instantiated from ``curl``:
+
+.. code-block:: console
+
+ curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^openssl@system%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+
+Spack's core concretization algorithm generates concrete specs by
+instantiating packages from its repo, based on a set of "hints",
+including user input and the ``packages.yaml`` file. This algorithm
+may be accessed at any time with the ``spack spec`` command. For
+example:
+
+.. code-block:: console
+
+ $ spack spec curl
+ curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^openssl@system%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+
+Every time Spack installs a package, that installation corresponds to
+a concrete spec. Only a vanishingly small fraction of possible
+concrete specs will be installed at any one Spack site.
+
+^^^^^^^^^^^^^^^
+Consistent Sets
+^^^^^^^^^^^^^^^
+
+A set of Spack specs is said to be *consistent* if each package is
+only instantiated one way within it --- that is, if two specs in the
+set have the same package, then they must also have the same version,
+variant, compiler, etc. For example, the following set is consistent:
+
+.. code-block:: console
+
+ curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^openssl@system%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+
+The following set is not consistent:
+
+.. code-block:: console
+
+ curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^openssl@system%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ ^zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ zlib@1.2.7%gcc@5.3.0 arch=linux-SuSE11-x86_64
+
+The compatibility of a set of installed packages determines what may
+be done with it. It is always possible to ``spack load`` any set of
+installed packages, whether or not they are consistent, and run their
+binaries from the command line. However, a set of installed packages
+can only be linked together in one binary if it is consistent.
+
+If the user produces a series of ``spack spec`` or ``spack load``
+commands, in general there is no guarantee of consistency between
+them. Spack's concretization procedure guarantees that the results of
+any *single* ``spack spec`` call will be consistent. Therefore, the
+best way to ensure a consistent set of specs is to create a Spack
+package with dependencies, and then instantiate that package. We will
+use this technique below.
+
+-----------------
+Building Packages
+-----------------
+
+Suppose you are tasked with installing a set of software packages on a
+system in order to support one application -- both a core application
+program, plus software to prepare input and analyze output. The
+required software might be summed up as a series of ``spack install``
+commands placed in a script. If needed, this script can always be run
+again in the future. For example:
+
+.. code-block:: sh
+
+ #!/bin/sh
+ spack install modele-utils
+ spack install emacs
+ spack install ncview
+ spack install nco
+ spack install modele-control
+ spack install py-numpy
+
+In most cases, this script will not correctly install software
+according to your specific needs: choices need to be made for
+variants, versions and virtual dependency choices may be needed. It
+*is* possible to specify these choices by extending specs on the
+command line; however, the same choices must be specified repeatedly.
+For example, if you wish to use ``openmpi`` to satisfy the ``mpi``
+dependency, then ``^openmpi`` will have to appear on *every* ``spack
+install`` line that uses MPI. It can get repetitive fast.
+
+Customizing Spack installation options is easier to do in the
+``~/.spack/packages.yaml`` file. In this file, you can specify
+preferred versions and variants to use for packages. For example:
+
+.. code-block:: yaml
+
+ packages:
+ python:
+ version: [3.5.1]
+ modele-utils:
+ version: [cmake]
+
+ everytrace:
+ version: [develop]
+ eigen:
+ variants: ~suitesparse
+ netcdf:
+ variants: +mpi
+
+ all:
+ compiler: [gcc@5.3.0]
+ providers:
+ mpi: [openmpi]
+ blas: [openblas]
+ lapack: [openblas]
+
+
+This approach will work as long as you are building packages for just
+one application.
+
+^^^^^^^^^^^^^^^^^^^^^
+Multiple Applications
+^^^^^^^^^^^^^^^^^^^^^
+
+Suppose instead you're building multiple inconsistent applications.
+For example, users want package A to be built with ``openmpi`` and
+package B with ``mpich`` --- but still share many other lower-level
+dependencies. In this case, a single ``packages.yaml`` file will not
+work. Plans are to implement *per-project* ``packages.yaml`` files.
+In the meantime, one could write shell scripts to switch
+``packages.yaml`` between multiple versions as needed, using symlinks.
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Combinatorial Sets of Installs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Suppose that you are now tasked with systematically building many
+incompatible versions of packages. For example, you need to build
+``petsc`` 9 times for 3 different MPI implementations on 3 different
+compilers, in order to support user needs. In this case, you will
+need to either create 9 different ``packages.yaml`` files; or more
+likely, create 9 different ``spack install`` command lines with the
+correct options in the spec. Here is a real-life example of this kind
+of usage:
+
+.. code-block:: sh
+
+ #!/bin/sh
+
+ compilers=(
+ %gcc
+ %intel
+ %pgi
+ )
+
+ mpis=(
+ openmpi+psm~verbs
+ openmpi~psm+verbs
+ mvapich2+psm~mrail
+ mvapich2~psm+mrail
+ mpich+verbs
+ )
+
+ for compiler in "${compilers[@]}"
+ do
+ # Serial installs
+ spack install szip $compiler
+ spack install hdf $compiler
+ spack install hdf5 $compiler
+ spack install netcdf $compiler
+ spack install netcdf-fortran $compiler
+ spack install ncview $compiler
+
+ # Parallel installs
+ for mpi in "${mpis[@]}"
+ do
+ spack install $mpi $compiler
+ spack install hdf5~cxx+mpi $compiler ^$mpi
+ spack install parallel-netcdf $compiler ^$mpi
+ done
+ done
+
+------------------------------
+Running Binaries from Packages
+------------------------------
+
+Once Spack packages have been built, the next step is to use them. As
+with building packages, there are many ways to use them, depending on
+the use case.
+
+^^^^^^^^^^^^
+Find and Run
+^^^^^^^^^^^^
+
+The simplest way to run a Spack binary is to find it and run it!
+In many cases, nothing more is needed because Spack builds binaries
+with RPATHs. Spack installation directories may be found with ``spack
+location --install-dir`` commands. For example:
+
+.. code-block:: console
+
+ $ spack location --install-dir cmake
+ /home/me/spack2/opt/spack/linux-SuSE11-x86_64/gcc-5.3.0/cmake-3.6.0-7cxrynb6esss6jognj23ak55fgxkwtx7
+
+This gives the root of the Spack package; relevant binaries may be
+found within it. For example:
+
+.. code-block:: console
+
+ $ CMAKE=`spack location --install-dir cmake`/bin/cmake
+
+
+Standard UNIX tools can find binaries as well. For example:
+
+.. code-block:: console
+
+ $ find ~/spack2/opt -name cmake | grep bin
+ /home/me/spack2/opt/spack/linux-SuSE11-x86_64/gcc-5.3.0/cmake-3.6.0-7cxrynb6esss6jognj23ak55fgxkwtx7/bin/cmake
+
+These methods are suitable, for example, for setting up build
+processes or GUIs that need to know the location of particular tools.
+However, other more powerful methods are generally preferred for user
+environments.
+
+
+^^^^^^^^^^^^^^^^^^^^^^^
+Spack-Generated Modules
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Suppose that Spack has been used to install a set of command-line
+programs, which users now wish to use. One can in principle put a
+number of ``spack load`` commands into ``.bashrc``, for example, to
+load a set of Spack-generated modules:
+
+.. code-block:: sh
+
+ spack load modele-utils
+ spack load emacs
+ spack load ncview
+ spack load nco
+ spack load modele-control
+
+Although simple load scripts like this are useful in many cases, they
+have some drawbacks:
+
+1. The set of modules loaded by them will in general not be
+ consistent. They are a decent way to load commands to be called
+ from command shells. See below for better ways to assemble a
+ consistent set of packages for building application programs.
+
+2. The ``spack spec`` and ``spack install`` commands use a
+ sophisticated concretization algorithm that chooses the "best"
+ among several options, taking into account ``packages.yaml`` file.
+ The ``spack load`` and ``spack module loads`` commands, on the
+ other hand, are not very smart: if the user-supplied spec matches
+ more than one installed package, then ``spack module loads`` will
+ fail. This may change in the future. For now, the workaround is to
+ be more specific on any ``spack module loads`` lines that fail.
+
+
+""""""""""""""""""""""
+Generated Load Scripts
+""""""""""""""""""""""
+
+Another problem with using `spack load` is, it is slow; a typical user
+environment could take several seconds to load, and would not be
+appropriate to put into ``.bashrc`` directly. It is preferable to use
+a series of ``spack module loads`` commands to pre-compute which
+modules to load. These can be put in a script that is run whenever
+installed Spack packages change. For example:
+
+.. code-block:: sh
+
+ #!/bin/sh
+ #
+ # Generate module load commands in ~/env/spackenv
+
+ cat <<EOF | /bin/sh >$HOME/env/spackenv
+ FIND='spack module loads --prefix linux-SuSE11-x86_64/'
+
+ \$FIND modele-utils
+ \$FIND emacs
+ \$FIND ncview
+ \$FIND nco
+ \$FIND modele-control
+ EOF
+
+The output of this file is written in ``~/env/spackenv``:
+
+.. code-block:: sh
+
+ # binutils@2.25%gcc@5.3.0+gold~krellpatch~libiberty arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/binutils-2.25-gcc-5.3.0-6w5d2t4
+ # python@2.7.12%gcc@5.3.0~tk~ucs4 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/python-2.7.12-gcc-5.3.0-2azoju2
+ # ncview@2.1.7%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/ncview-2.1.7-gcc-5.3.0-uw3knq2
+ # nco@4.5.5%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/nco-4.5.5-gcc-5.3.0-7aqmimu
+ # modele-control@develop%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/modele-control-develop-gcc-5.3.0-7rddsij
+ # zlib@1.2.8%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/zlib-1.2.8-gcc-5.3.0-fe5onbi
+ # curl@7.50.1%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/curl-7.50.1-gcc-5.3.0-4vlev55
+ # hdf5@1.10.0-patch1%gcc@5.3.0+cxx~debug+fortran+mpi+shared~szip~threadsafe arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/hdf5-1.10.0-patch1-gcc-5.3.0-pwnsr4w
+ # netcdf@4.4.1%gcc@5.3.0~hdf4+mpi arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/netcdf-4.4.1-gcc-5.3.0-rl5canv
+ # netcdf-fortran@4.4.4%gcc@5.3.0 arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/netcdf-fortran-4.4.4-gcc-5.3.0-stdk2xq
+ # modele-utils@cmake%gcc@5.3.0+aux+diags+ic arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/modele-utils-cmake-gcc-5.3.0-idyjul5
+ # everytrace@develop%gcc@5.3.0+fortran+mpi arch=linux-SuSE11-x86_64
+ module load linux-SuSE11-x86_64/everytrace-develop-gcc-5.3.0-p5wmb25
+
+Users may now put ``source ~/env/spackenv`` into ``.bashrc``.
+
+.. note ::
+
+ Some module systems put a prefix on the names of modules created
+ by Spack. For example, that prefix is ``linux-SuSE11-x86_64/`` in
+ the above case. If a prefix is not needed, you may omit the
+ ``--prefix`` flag from ``spack module loads``.
+
+
+"""""""""""""""""""""""
+Transitive Dependencies
+"""""""""""""""""""""""
+
+In the script above, each ``spack module loads`` command generates a
+*single* ``module load`` line. Transitive dependencies do not usually
+need to be loaded, only modules the user needs in in ``$PATH``. This is
+because Spack builds binaries with RPATH. Spack's RPATH policy has
+some nice features:
+
+#. Modules for multiple inconsistent applications may be loaded
+ simultaneously. In the above example (Multiple Applications),
+ package A and package B can coexist together in the user's $PATH,
+ even though they use different MPIs.
+
+#. RPATH eliminates a whole class of strange errors that can happen
+ in non-RPATH binaries when the wrong ``LD_LIBRARY_PATH`` is
+ loaded.
+
+#. Recursive module systems such as LMod are not necessary.
+
+#. Modules are not needed at all to execute binaries. If a path to a
+ binary is known, it may be executed. For example, the path for a
+ Spack-built compiler can be given to an IDE without requiring the
+ IDE to load that compiler's module.
+
+Unfortunately, Spack's RPATH support does not work in all case. For example:
+
+#. Software comes in many forms --- not just compiled ELF binaries,
+ but also as interpreted code in Python, R, JVM bytecode, etc.
+ Those systems almost universally use an environment variable
+ analogous to ``LD_LIBRARY_PATH`` to dynamically load libraries.
+
+#. Although Spack generally builds binaries with RPATH, it does not
+ currently do so for compiled Python extensions (for example,
+ ``py-numpy``). Any libraries that these extensions depend on
+ (``blas`` in this case, for example) must be specified in the
+ ``LD_LIBRARY_PATH``.`
+
+#. In some cases, Spack-generated binaries end up without a
+ functional RPATH for no discernible reason.
+
+In cases where RPATH support doesn't make things "just work," it can
+be necessary to load a module's dependencies as well as the module
+itself. This is done by adding the ``--dependencies`` flag to the
+``spack module loads`` command. For example, the following line,
+added to the script above, would be used to load SciPy, along with
+Numpy, core Python, BLAS/LAPACK and anything else needed:
+
+.. code-block:: sh
+
+ spack module loads --dependencies py-scipy
+
+^^^^^^^^^^^^^^^^^^
+Extension Packages
+^^^^^^^^^^^^^^^^^^
+
+:ref:`packaging_extensions` may be used as an alternative to loading
+Python (and similar systems) packages directly. If extensions are
+activated, then ``spack load python`` will also load all the
+extensions activated for the given ``python``. This reduces the need
+for users to load a large number of modules.
+
+However, Spack extensions have two potential drawbacks:
+
+#. Activated packages that involve compiled C extensions may still
+ need their dependencies to be loaded manually. For example,
+ ``spack load openblas`` might be required to make ``py-numpy``
+ work.
+
+#. Extensions "break" a core feature of Spack, which is that multiple
+ versions of a package can co-exist side-by-side. For example,
+ suppose you wish to run a Python package in two different
+ environments but the same basic Python --- one with
+ ``py-numpy@1.7`` and one with ``py-numpy@1.8``. Spack extensions
+ will not support this potential debugging use case.
+
+
+^^^^^^^^^^^^^^
+Dummy Packages
+^^^^^^^^^^^^^^
+
+As an alternative to a series of ``module load`` commands, one might
+consider dummy packages as a way to create a *consistent* set of
+packages that may be loaded as one unit. The idea here is pretty
+simple:
+
+#. Create a package (say, ``mydummy``) with no URL and no
+ ``install()`` method, just dependencies.
+
+#. Run ``spack install mydummy`` to install.
+
+An advantage of this method is the set of packages produced will be
+consistent. This means that you can reliably build software against
+it. A disadvantage is the set of packages will be consistent; this
+means you cannot load up two applications this way if they are not
+consistent with each other.
+
+^^^^^^^^^^^^^^^^
+Filesystem Views
+^^^^^^^^^^^^^^^^
+
+Filesystem views offer an alternative to environment modules, another
+way to assemble packages in a useful way and load them into a user's
+environment.
+
+A filesystem view is a single directory tree that is the union of the
+directory hierarchies of a number of installed packages; it is similar
+to the directory hiearchy that might exist under ``/usr/local``. The
+files of the view's installed packages are brought into the view by
+symbolic or hard links, referencing the original Spack installation.
+
+When software is built and installed, absolute paths are frequently
+"baked into" the software, making it non-relocatable. This happens
+not just in RPATHs, but also in shebangs, configuration files, and
+assorted other locations.
+
+Therefore, programs run out of a Spack view will typically still look
+in the original Spack-installed location for shared libraries and
+other resources. This behavior is not easily changed; in general,
+there is no way to know where absolute paths might be written into an
+installed package, and how to relocate it. Therefore, the original
+Spack tree must be kept in place for a filesystem view to work, even
+if the view is built with hardlinks.
+
+.. FIXME: reference the relocation work of Hegner and Gartung (PR #1013)
+
+
+""""""""""""""""""""""
+Using Filesystem Views
+""""""""""""""""""""""
+
+A filesystem view is created, and packages are linked in, by the ``spack
+view`` command's ``symlink`` and ``hardlink`` sub-commands. The
+``spack view remove`` command can be used to unlink some or all of the
+filesystem view.
+
+The following example creates a filesystem view based
+on an installed ``cmake`` package and then removes from the view the
+files in the ``cmake`` package while retaining its dependencies.
+
+.. code-block:: console
+
+ $ spack view --verbose symlink myview cmake@3.5.2
+ ==> Linking package: "ncurses"
+ ==> Linking package: "zlib"
+ ==> Linking package: "openssl"
+ ==> Linking package: "cmake"
+
+ $ ls myview/
+ bin doc etc include lib share
+
+ $ ls myview/bin/
+ captoinfo clear cpack ctest infotocap openssl tabs toe tset
+ ccmake cmake c_rehash infocmp ncurses6-config reset tic tput
+
+ $ spack view --verbose --dependencies false rm myview cmake@3.5.2
+ ==> Removing package: "cmake"
+
+ $ ls myview/bin/
+ captoinfo c_rehash infotocap openssl tabs toe tset
+ clear infocmp ncurses6-config reset tic tput
+
+.. note::
+
+ If the set of packages being included in a view is inconsistent,
+ then it is possible that two packages will provide the same file. Any
+ conflicts of this type are handled on a first-come-first-served basis,
+ and a warning is printed.
+
+.. note::
+
+ When packages are removed from a view, empty directories are
+ purged.
+
+""""""""""""""""""
+Fine-Grain Control
+""""""""""""""""""
+
+The ``--exclude`` and ``--dependencies`` option flags allow for
+fine-grained control over which packages and dependencies do or not
+get included in a view. For example, suppose you are developing the
+``appsy`` package. You wish to build against a view of all ``appsy``
+dependencies, but not ``appsy`` itself:
+
+.. code-block:: console
+
+ $ spack view symlink --dependencies yes --exclude appsy appsy
+
+Alternately, you wish to create a view whose purpose is to provide
+binary executables to end users. You only need to include
+applications they might want, and not those applications'
+dependencies. In this case, you might use:
+
+.. code-block:: console
+
+ $ spack view symlink --dependencies no cmake
+
+
+"""""""""""""""""""""""
+Hybrid Filesystem Views
+"""""""""""""""""""""""
+
+Although filesystem views are usually created by Spack, users are free
+to add to them by other means. For example, imagine a filesystem
+view, created by Spack, that looks something like:
+
+.. code-block:: console
+
+ /path/to/MYVIEW/bin/programA -> /path/to/spack/.../bin/programA
+ /path/to/MYVIEW/lib/libA.so -> /path/to/spack/.../lib/libA.so
+
+Now, the user may add to this view by non-Spack means; for example, by
+running a classic install script. For example:
+
+.. code-block:: console
+
+ $ tar -xf B.tar.gz
+ $ cd B/
+ $ ./configure --prefix=/path/to/MYVIEW \
+ --with-A=/path/to/MYVIEW
+ $ make && make install
+
+The result is a hybrid view:
+
+.. code-block:: console
+
+ /path/to/MYVIEW/bin/programA -> /path/to/spack/.../bin/programA
+ /path/to/MYVIEW/bin/programB
+ /path/to/MYVIEW/lib/libA.so -> /path/to/spack/.../lib/libA.so
+ /path/to/MYVIEW/lib/libB.so
+
+In this case, real files coexist, interleaved with the "view"
+symlinks. At any time one can delete ``/path/to/MYVIEW`` or use
+``spack view`` to manage it surgically. None of this will affect the
+real Spack install area.
+
+
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Discussion: Running Binaries
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Modules, extension packages and filesystem views are all ways to
+assemble sets of Spack packages into a useful environment. They are
+all semantically similar, in that conflicting installed packages
+cannot simultaneously be loaded, activated or included in a view.
+
+With all of these approaches, there is no guarantee that the
+environment created will be consistent. It is possible, for example,
+to simultaneously load application A that uses OpenMPI and application
+B that uses MPICH. Both applications will run just fine in this
+inconsistent environment because they rely on RPATHs, not the
+environment, to find their dependencies.
+
+In general, environments set up using modules vs. views will work
+similarly. Both can be used to set up ephemeral or long-lived
+testing/development environments. Operational differences between the
+two approaches can make one or the other preferable in certain
+environments:
+
+* Filesystem views do not require environment module infrastructure.
+ Although Spack can install ``environment-modules``, users might be
+ hostile to its use. Filesystem views offer a good solution for
+ sysadmins serving users who just "want all the stuff I need in one
+ place" and don't want to hear about Spack.
+
+* Although modern build systems will find dependencies wherever they
+ might be, some applications with hand-built make files expect their
+ dependencies to be in one place. One common problem is makefiles
+ that assume that ``netcdf`` and ``netcdf-fortran`` are installed in
+ the same tree. Or, one might use an IDE that requires tedious
+ configuration of dependency paths; and it's easier to automate that
+ administration in a view-building script than in the IDE itself.
+ For all these cases, a view will be preferable to other ways to
+ assemble an environment.
+
+* On systems with I-node quotas, modules might be preferable to views
+ and extension packages.
+
+* Views and activated extensions maintain state that is semantically
+ equivalent to the information in a ``spack module loads`` script.
+ Administrators might find things easier to maintain without the
+ added "heavyweight" state of a view.
+
+------------------------------
+Developing Software with Spack
+------------------------------
+
+For any project, one needs to assemble an
+environment of that application's dependencies. You might consider
+loading a series of modules or creating a filesystem view. This
+approach, while obvious, has some serious drawbacks:
+
+1. There is no guarantee that an environment created this way will be
+ consistent. Your application could end up with dependency A
+ expecting one version of MPI, and dependency B expecting another.
+ The linker will not be happy...
+
+2. Suppose you need to debug a package deep within your software DAG.
+ If you build that package with a manual environment, then it
+ becomes difficult to have Spack auto-build things that depend on
+ it. That could be a serious problem, depending on how deep the
+ package in question is in your dependency DAG.
+
+3. At its core, Spack is a sophisticated concretization algorithm that
+ matches up packages with appropriate dependencies and creates a
+ *consistent* environment for the package it's building. Writing a
+ list of ``spack load`` commands for your dependencies is at least
+ as hard as writing the same list of ``depends_on()`` declarations
+ in a Spack package. But it makes no use of Spack concretization
+ and is more error-prone.
+
+4. Spack provides an automated, systematic way not just to find a
+ packages's dependencies --- but also to build other packages on
+ top. Any Spack package can become a dependency for another Spack
+ package, offering a powerful vision of software re-use. If you
+ build your package A outside of Spack, then your ability to use it
+ as a building block for other packages in an automated way is
+ diminished: other packages depending on package A will not
+ be able to use Spack to fulfill that dependency.
+
+5. If you are reading this manual, you probably love Spack. You're
+ probably going to write a Spack package for your software so
+ prospective users can install it with the least amount of pain.
+ Why should you go to additional work to find dependencies in your
+ development environment? Shouldn't Spack be able to help you build
+ your software based on the package you've already written?
+
+In this section, we show how Spack can be used in the software
+development process to greatest effect, and how development packages
+can be seamlessly integrated into the Spack ecosystem. We will show
+how this process works by example, assuming the software you are
+creating is called ``mylib``.
+
+^^^^^^^^^^^^^^^^^^^^^
+Write the CMake Build
+^^^^^^^^^^^^^^^^^^^^^
+
+For now, the techniques in this section only work for CMake-based
+projects, although they could be easily extended to other build
+systems in the future. We will therefore assume you are using CMake
+to build your project.
+
+The ``CMakeLists.txt`` file should be written as normal. A few caveats:
+
+1. Your project should produce binaries with RPATHs. This will ensure
+ that they work the same whether built manually or automatically by
+ Spack. For example:
+
+.. code-block:: cmake
+
+ # enable @rpath in the install name for any shared library being built
+ # note: it is planned that a future version of CMake will enable this by default
+ set(CMAKE_MACOSX_RPATH 1)
+
+ # Always use full RPATH
+ # http://www.cmake.org/Wiki/CMake_RPATH_handling
+ # http://www.kitware.com/blog/home/post/510
+
+ # use, i.e. don't skip the full RPATH for the build tree
+ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
+
+ # when building, don't use the install RPATH already
+ # (but later on when installing)
+ SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+
+ # add the automatically determined parts of the RPATH
+ # which point to directories outside the build tree to the install RPATH
+ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+
+ # the RPATH to be used when installing, but only if it's not a system directory
+ LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
+ IF("${isSystemDir}" STREQUAL "-1")
+ SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+ ENDIF("${isSystemDir}" STREQUAL "-1")
+
+
+2. Spack provides a CMake variable called
+ ``SPACK_TRANSITIVE_INCLUDE_PATH``, which contains the ``include/``
+ directory for all of your project's transitive dependencies. It
+ can be useful if your project ``#include``s files from package B,
+ which ``#include`` files from package C, but your project only
+ lists project B as a dependency. This works in traditional
+ single-tree build environments, in which B and C's include files
+ live in the same place. In order to make it work with Spack as
+ well, you must add the following to ``CMakeLists.txt``. It will
+ have no effect when building without Spack:
+
+ .. code-block:: cmake
+
+ # Include all the transitive dependencies determined by Spack.
+ # If we're not running with Spack, this does nothing...
+ include_directories($ENV{SPACK_TRANSITIVE_INCLUDE_PATH})
+
+ .. note::
+
+ Note that this feature is controversial and could break with
+ future versions of GNU ld. The best practice is to make sure
+ anything you ``#include`` is listed as a dependency in your
+ CMakeLists.txt (and Spack package).
+
+.. _write-the-spack-package:
+
+^^^^^^^^^^^^^^^^^^^^^^^
+Write the Spack Package
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The Spack package also needs to be written, in tandem with setting up
+the build (for example, CMake). The most important part of this task
+is declaring dependencies. Here is an example of the Spack package
+for the ``mylib`` package (ellipses for brevity):
+
+.. code-block:: python
+
+ class Mylib(CMakePackage):
+ """Misc. reusable utilities used by Myapp."""
+
+ homepage = "https://github.com/citibeth/mylib"
+ url = "https://github.com/citibeth/mylib/tarball/123"
+
+ version('0.1.2', '3a6acd70085e25f81b63a7e96c504ef9')
+ version('develop', git='https://github.com/citibeth/mylib.git',
+ branch='develop')
+
+ variant('everytrace', default=False,
+ description='Report errors through Everytrace')
+ ...
+
+ extends('python')
+
+ depends_on('eigen')
+ depends_on('everytrace', when='+everytrace')
+ depends_on('proj', when='+proj')
+ ...
+ depends_on('cmake', type='build')
+ depends_on('doxygen', type='build')
+
+ def configure_args(self):
+ spec = self.spec
+ return [
+ '-DUSE_EVERYTRACE=%s' % ('YES' if '+everytrace' in spec else 'NO'),
+ '-DUSE_PROJ4=%s' % ('YES' if '+proj' in spec else 'NO'),
+ ...
+ '-DUSE_UDUNITS2=%s' % ('YES' if '+udunits2' in spec else 'NO'),
+ '-DUSE_GTEST=%s' % ('YES' if '+googletest' in spec else 'NO')]
+
+This is a standard Spack package that can be used to install
+``mylib`` in a production environment. The list of dependencies in
+the Spack package will generally be a repeat of the list of CMake
+dependencies. This package also has some features that allow it to be
+used for development:
+
+1. It subclasses ``CMakePackage`` instead of ``Package``. This
+ eliminates the need to write an ``install()`` method, which is
+ defined in the superclass. Instead, one just needs to write the
+ ``configure_args()`` method. That method should return the
+ arguments needed for the ``cmake`` command (beyond the standard
+ CMake arguments, which Spack will include already). These
+ arguments are typically used to turn features on/off in the build.
+
+2. It specifies a non-checksummed version ``develop``. Running
+ ``spack install mylib@develop`` the ``@develop`` version will
+ install the latest version off the develop branch. This method of
+ download is useful for the developer of a project while it is in
+ active development; however, it should only be used by developers
+ who control and trust the repository in question!
+
+3. The ``url``, ``url_for_version()`` and ``homepage`` attributes are
+ not used in development. Don't worry if you don't have any, or if
+ they are behind a firewall.
+
+^^^^^^^^^^^^^^^^
+Build with Spack
+^^^^^^^^^^^^^^^^
+
+Now that you have a Spack package, you can use Spack to find its
+dependencies automatically. For example:
+
+.. code-block:: console
+
+ $ cd mylib
+ $ spack setup mylib@local
+
+The result will be a file ``spconfig.py`` in the top-level
+``mylib/`` directory. It is a short script that calls CMake with the
+dependencies and options determined by Spack --- similar to what
+happens in ``spack install``, but now written out in script form.
+From a developer's point of view, you can think of ``spconfig.py`` as
+a stand-in for the ``cmake`` command.
+
+.. note::
+
+ You can invent any "version" you like for the ``spack setup``
+ command.
+
+.. note::
+
+ Although ``spack setup`` does not build your package, it does
+ create and install a module file, and mark in the database that
+ your package has been installed. This can lead to errors, of
+ course, if you don't subsequently install your package.
+ Also... you will need to ``spack uninstall`` before you run
+ ``spack setup`` again.
+
+
+You can now build your project as usual with CMake:
+
+.. code-block:: console
+
+ $ mkdir build; cd build
+ $ ../spconfig.py .. # Instead of cmake ..
+ $ make
+ $ make install
+
+Once your ``make install`` command is complete, your package will be
+installed, just as if you'd run ``spack install``. Except you can now
+edit, re-build and re-install as often as needed, without checking
+into Git or downloading tarballs.
+
+.. note::
+
+ The build you get this way will be *almost* the same as the build
+ from ``spack install``. The only difference is, you will not be
+ using Spack's compiler wrappers. This difference has not caused
+ problems in our experience, as long as your project sets
+ RPATHs as shown above. You DO use RPATHs, right?
+
+^^^^^^^^^^^^^^^^^^^^
+Build Other Software
+^^^^^^^^^^^^^^^^^^^^
+
+Now that you've built ``mylib`` with Spack, you might want to build
+another package that depends on it --- for example, ``myapp``. This
+is accomplished easily enough:
+
+.. code-block:: console
+
+ $ spack install myapp ^mylib@local
+
+Note that auto-built software has now been installed *on top of*
+manually-built software, without breaking Spack's "web." This
+property is useful if you need to debug a package deep in the
+dependency hierarchy of your application. It is a *big* advantage of
+using ``spack setup`` to build your package's environment.
+
+If you feel your software is stable, you might wish to install it with
+``spack install`` and skip the source directory. You can just use,
+for example:
+
+.. code-block:: console
+
+ $ spack install mylib@develop
+
+.. _release-your-software:
+
+^^^^^^^^^^^^^^^^^^^^^
+Release Your Software
+^^^^^^^^^^^^^^^^^^^^^
+
+You are now ready to release your software as a tarball with a
+numbered version, and a Spack package that can build it. If you're
+hosted on GitHub, this process will be a bit easier.
+
+#. Put tag(s) on the version(s) in your GitHub repo you want to be
+ release versions. For example, a tag ``v0.1.0`` for version 0.1.0.
+
+#. Set the ``url`` in your ``package.py`` to download a tarball for
+ the appropriate version. GitHub will give you a tarball for any
+ commit in the repo, if you tickle it the right way. For example:
+
+ .. code-block:: python
+
+ url = 'https://github.com/citibeth/mylib/tarball/v0.1.2'
+
+#. Use Spack to determine your version's hash, and cut'n'paste it into
+ your ``package.py``:
+
+ .. code-block:: console
+
+ $ spack checksum mylib 0.1.2
+ ==> Found 1 versions of mylib
+ 0.1.2 https://github.com/citibeth/mylib/tarball/v0.1.2
+
+ How many would you like to checksum? (default is 5, q to abort)
+ ==> Downloading...
+ ==> Trying to fetch from https://github.com/citibeth/mylib/tarball/v0.1.2
+ ######################################################################## 100.0%
+ ==> Checksummed new versions of mylib:
+ version('0.1.2', '3a6acd70085e25f81b63a7e96c504ef9')
+
+#. You should now be able to install released version 0.1.2 of your package with:
+
+ .. code-block:: console
+
+ $ spack install mylib@0.1.2
+
+#. There is no need to remove the `develop` version from your package.
+ Spack concretization will always prefer numbered version to
+ non-numeric versions. Users will only get it if they ask for it.
+
+^^^^^^^^^^^^^^^^^^^^^^^^
+Distribute Your Software
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Once you've released your software, other people will want to build
+it; and you will need to tell them how. In the past, that has meant a
+few paragraphs of pros explaining which dependencies to install. But
+now you use Spack, and those instructions are written in executable
+Python code. But your software has many dependencies, and you know
+Spack is the best way to install it:
+
+#. First, you will want to fork Spack's ``develop`` branch. Your aim
+ is to provide a stable version of Spack that you KNOW will install
+ your software. If you make changes to Spack in the process, you
+ will want to submit pull requests to Spack core.
+
+#. Add your software's ``package.py`` to that fork. You should submit
+ a pull request for this as well, unless you don't want the public
+ to know about your software.
+
+#. Prepare instructions that read approximately as follows:
+
+ #. Download Spack from your forked repo.
+
+ #. Install Spack; see :ref:`getting_started`.
+
+ #. Set up an appropriate ``packages.yaml`` file. You should tell
+ your users to include in this file whatever versions/variants
+ are needed to make your software work correctly (assuming those
+ are not already in your ``packages.yaml``).
+
+ #. Run ``spack install mylib``.
+
+ #. Run this script to generate the ``module load`` commands or
+ filesystem view needed to use this software.
+
+#. Be aware that your users might encounter unexpected bootstrapping
+ issues on their machines, especially if they are running on older
+ systems. The :ref:`getting_started` section should cover this, but
+ there could always be issues.
+
+^^^^^^^^^^^^^^^^^^^
+Other Build Systems
+^^^^^^^^^^^^^^^^^^^
+
+``spack setup`` currently only supports CMake-based builds, in
+packages that subclass ``CMakePackage``. The intent is that this
+mechanism should support a wider range of build systems; for example,
+GNU Autotools. Someone well-versed in Autotools is needed to develop
+this patch and test it out.
+
+Python Distutils is another popular build system that should get
+``spack setup`` support. For non-compiled languages like Python,
+``spack diy`` may be used. Even better is to put the source directory
+directly in the user's ``PYTHONPATH``. Then, edits in source files
+are immediately available to run without any install process at all!
+
+^^^^^^^^^^
+Conclusion
+^^^^^^^^^^
+
+The ``spack setup`` development workflow provides better automation,
+flexibility and safety than workflows relying on environment modules
+or filesystem views. However, it has some drawbacks:
+
+#. It currently works only with projects that use the CMake build
+ system. Support for other build systems is not hard to build, but
+ will require a small amount of effort for each build system to be
+ supported. It might not work well with some IDEs.
+
+#. It only works with packages that sub-class ``StagedPackage``.
+ Currently, most Spack packages do not. Converting them is not
+ hard; but must be done on a package-by-package basis.
+
+#. It requires that users are comfortable with Spack, as they
+ integrate Spack explicitly in their workflow. Not all users are
+ willing to do this.
+
+------------------
+Upstream Bug Fixes
+------------------
+
+It is not uncommon to discover a bug in an upstream project while
+trying to build with Spack. Typically, the bug is in a package that
+serves a dependency to something else. This section describes
+procedure to work around and ultimately resolve these bugs, while not
+delaying the Spack user's main goal.
+
+^^^^^^^^^^^^^^^^^
+Buggy New Version
+^^^^^^^^^^^^^^^^^
+
+Sometimes, the old version of a package works fine, but a new version
+is buggy. For example, it was once found that `Adios did not build
+with hdf5@1.10 <https://github.com/LLNL/spack/issues/1683>`_. If the
+old version of ``hdf5`` will work with ``adios``, the suggested
+procedure is:
+
+#. Revert ``adios`` to the old version of ``hdf5``. Put in its
+ ``adios/package.py``:
+
+ .. code-block:: python
+
+ # Adios does not build with HDF5 1.10
+ # See: https://github.com/LLNL/spack/issues/1683
+ depends_on('hdf5@:1.9')
+
+#. Determine whether the problem is with ``hdf5`` or ``adios``, and
+ report the problem to the appropriate upstream project. In this
+ case, the problem was with ``adios``.
+
+#. Once a new version of ``adios`` comes out with the bugfix, modify
+ ``adios/package.py`` to reflect it:
+
+ .. code-block:: python
+
+ # Adios up to v1.10.0 does not build with HDF5 1.10
+ # See: https://github.com/LLNL/spack/issues/1683
+ depends_on('hdf5@:1.9', when='@:1.10.0')
+ depends_on('hdf5', when='@1.10.1:')
+
+^^^^^^^^^^^^^^^^
+No Version Works
+^^^^^^^^^^^^^^^^
+
+Sometimes, *no* existing versions of a dependency work for a build.
+This typically happens when developing a new project: only then does
+the developer notice that existing versions of a dependency are all
+buggy, or the non-buggy versions are all missing a critical feature.
+
+In the long run, the upstream project will hopefully fix the bug and
+release a new version. But that could take a while, even if a bugfix
+has already been pushed to the project's repository. In the meantime,
+the Spack user needs things to work.
+
+The solution is to create an unofficial Spack release of the project,
+as soon as the bug is fixed in *some* repository. A study of the `Git
+history <https://github.com/citibeth/spack/commits/efischer/develop/var/spack/repos/builtin/packages/py-proj/package.py>`_
+of ``py-proj/package.py`` is instructive here:
+
+#. On `April 1 <https://github.com/citibeth/spack/commit/44a1d6a96706affe6ef0a11c3a780b91d21d105a>`_, an initial bugfix was identified for the PyProj project
+ and a pull request submitted to PyProj. Because the upstream
+ authors had not yet fixed the bug, the ``py-proj`` Spack package
+ downloads from a forked repository, set up by the package's author.
+ A non-numeric version number is used to make it easy to upgrade the
+ package without recomputing checksums; however, this is an
+ untrusted download method and should not be distributed. The
+ package author has now become, temporarily, a maintainer of the
+ upstream project:
+
+ .. code-block:: python
+
+ # We need the benefits of this PR
+ # https://github.com/jswhit/pyproj/pull/54
+ version('citibeth-latlong2',
+ git='https://github.com/citibeth/pyproj.git',
+ branch='latlong2')
+
+
+#. By May 14, the upstream project had accepted a pull request with
+ the required bugfix. At this point, the forked repository was
+ deleted. However, the upstream project still had not released a
+ new version with a bugfix. Therefore, a Spack-only release was
+ created by specifying the desired hash in the main project
+ repository. The version number ``@1.9.5.1.1`` was chosen for this
+ "release" because it's a descendent of the officially released
+ version ``@1.9.5.1``. This is a trusted download method, and can
+ be released to the Spack community:
+
+ .. code-block:: python
+
+ # This is not a tagged release of pyproj.
+ # The changes in this "version" fix some bugs, especially with Python3 use.
+ version('1.9.5.1.1', 'd035e4bc704d136db79b43ab371b27d2',
+ url='https://www.github.com/jswhit/pyproj/tarball/0be612cc9f972e38b50a90c946a9b353e2ab140f')
+
+ .. note::
+
+ It would have been simpler to use Spack's Git download method,
+ which is also a trusted download in this case:
+
+ .. code-block:: python
+
+ # This is not a tagged release of pyproj.
+ # The changes in this "version" fix some bugs, especially with Python3 use.
+ version('1.9.5.1.1',
+ git='https://github.com/jswhit/pyproj.git',
+ commit='0be612cc9f972e38b50a90c946a9b353e2ab140f')
+
+ .. note::
+
+ In this case, the upstream project fixed the bug in its
+ repository in a relatively timely manner. If that had not been
+ the case, the numbered version in this step could have been
+ released from the forked repository.
+
+
+#. The author of the Spack package has now become an unofficial
+ release engineer for the upstream project. Depending on the
+ situation, it may be advisable to put ``preferred=True`` on the
+ latest *officially released* version.
+
+#. As of August 31, the upstream project still had not made a new
+ release with the bugfix. In the meantime, Spack-built ``py-proj``
+ provides the bugfix needed by packages depending on it. As long as
+ this works, there is no particular need for the upstream project to
+ make a new official release.
+
+#. If the upstream project releases a new official version with the
+ bugfix, then the unofficial ``version()`` line should be removed
+ from the Spack package.
+
+^^^^^^^
+Patches
+^^^^^^^
+
+Spack's source patching mechanism provides another way to fix bugs in
+upstream projects. This has advantages and disadvantages compared to the procedures above.
+
+Advantages:
+
+ 1. It can fix bugs in existing released versions, and (probably)
+ future releases as well.
+
+ 2. It is lightweight, does not require a new fork to be set up.
+
+Disadvantages:
+
+ 1. It is harder to develop and debug a patch, vs. a branch in a
+ repository. The user loses the automation provided by version
+ control systems.
+
+ 2. Although patches of a few lines work OK, large patch files can be
+ hard to create and maintain.
+
diff --git a/lib/spack/env/cc b/lib/spack/env/cc
index c6bb50d261..c4e51834a5 100755
--- a/lib/spack/env/cc
+++ b/lib/spack/env/cc
@@ -175,6 +175,18 @@ if [[ -z $command ]]; then
fi
#
+# Set paths as defined in the 'environment' section of the compiler config
+# names are stored in SPACK_ENV_TO_SET
+# values are stored in SPACK_ENV_SET_<varname>
+#
+IFS=':' read -ra env_set_varnames <<< "$SPACK_ENV_TO_SET"
+for varname in "${env_set_varnames[@]}"; do
+ spack_varname="SPACK_ENV_SET_$varname"
+ export $varname=${!spack_varname}
+ unset $spack_varname
+done
+
+#
# Filter '.' and Spack environment directories out of PATH so that
# this script doesn't just call itself
#
@@ -204,9 +216,9 @@ fi
# It doesn't work with -rpath.
# This variable controls whether they are added.
add_rpaths=true
-if [[ $mode == ld && "$SPACK_SHORT_SPEC" =~ "darwin" ]]; then
+if [[ ($mode == ld || $mode == ccld) && "$SPACK_SHORT_SPEC" =~ "darwin" ]]; then
for arg in "$@"; do
- if [[ $arg == -r ]]; then
+ if [[ ($arg == -r && $mode == ld) || ($arg == -Wl,-r && $mode == ccld) ]]; then
add_rpaths=false
break
fi
@@ -266,22 +278,38 @@ for dep in "${deps[@]}"; do
# Prepend lib and RPATH directories
if [[ -d $dep/lib ]]; then
if [[ $mode == ccld ]]; then
- $add_rpaths && args=("$rpath$dep/lib" "${args[@]}")
- args=("-L$dep/lib" "${args[@]}")
+ if [[ $SPACK_RPATH_DEPS == *$dep* ]]; then
+ $add_rpaths && args=("$rpath$dep/lib" "${args[@]}")
+ fi
+ if [[ $SPACK_LINK_DEPS == *$dep* ]]; then
+ args=("-L$dep/lib" "${args[@]}")
+ fi
elif [[ $mode == ld ]]; then
- $add_rpaths && args=("-rpath" "$dep/lib" "${args[@]}")
- args=("-L$dep/lib" "${args[@]}")
+ if [[ $SPACK_RPATH_DEPS == *$dep* ]]; then
+ $add_rpaths && args=("-rpath" "$dep/lib" "${args[@]}")
+ fi
+ if [[ $SPACK_LINK_DEPS == *$dep* ]]; then
+ args=("-L$dep/lib" "${args[@]}")
+ fi
fi
fi
# Prepend lib64 and RPATH directories
if [[ -d $dep/lib64 ]]; then
if [[ $mode == ccld ]]; then
- $add_rpaths && args=("$rpath$dep/lib64" "${args[@]}")
- args=("-L$dep/lib64" "${args[@]}")
+ if [[ $SPACK_RPATH_DEPS == *$dep* ]]; then
+ $add_rpaths && args=("$rpath$dep/lib64" "${args[@]}")
+ fi
+ if [[ $SPACK_LINK_DEPS == *$dep* ]]; then
+ args=("-L$dep/lib64" "${args[@]}")
+ fi
elif [[ $mode == ld ]]; then
- $add_rpaths && args=("-rpath" "$dep/lib64" "${args[@]}")
- args=("-L$dep/lib64" "${args[@]}")
+ if [[ $SPACK_RPATH_DEPS == *$dep* ]]; then
+ $add_rpaths && args=("-rpath" "$dep/lib64" "${args[@]}")
+ fi
+ if [[ $SPACK_LINK_DEPS == *$dep* ]]; then
+ args=("-L$dep/lib64" "${args[@]}")
+ fi
fi
fi
done
@@ -295,19 +323,22 @@ elif [[ $mode == ld ]]; then
$add_rpaths && args=("-rpath" "$SPACK_PREFIX/lib" "${args[@]}")
fi
+# Set extra RPATHs
+IFS=':' read -ra extra_rpaths <<< "$SPACK_COMPILER_EXTRA_RPATHS"
+for extra_rpath in "${extra_rpaths[@]}"; do
+ if [[ $mode == ccld ]]; then
+ $add_rpaths && args=("$rpath$extra_rpath" "${args[@]}")
+ elif [[ $mode == ld ]]; then
+ $add_rpaths && args=("-rpath" "$extra_rpath" "${args[@]}")
+ fi
+done
+
# Add SPACK_LDLIBS to args
case "$mode" in
ld|ccld)
args=("${args[@]}" ${SPACK_LDLIBS[@]}) ;;
esac
-#
-# Unset pesky environment variables that could affect build sanity.
-#
-unset LD_LIBRARY_PATH
-unset LD_RUN_PATH
-unset DYLD_LIBRARY_PATH
-
full_command=("$command" "${args[@]}")
# In test command mode, write out full command for Spack tests.
diff --git a/lib/spack/env/clang/gfortran b/lib/spack/env/clang/gfortran
new file mode 120000
index 0000000000..82c2b8e90a
--- /dev/null
+++ b/lib/spack/env/clang/gfortran
@@ -0,0 +1 @@
+../cc \ No newline at end of file
diff --git a/lib/spack/external/distro.py b/lib/spack/external/distro.py
new file mode 100644
index 0000000000..091bba3db8
--- /dev/null
+++ b/lib/spack/external/distro.py
@@ -0,0 +1,1049 @@
+# Copyright 2015,2016 Nir Cohen
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+The ``distro`` package (``distro`` stands for Linux Distribution) provides
+information about the Linux distribution it runs on, such as a reliable
+machine-readable distro ID, or version information.
+
+It is a renewed alternative implementation for Python's original
+:py:func:`platform.linux_distribution` function, but it provides much more
+functionality. An alternative implementation became necessary because Python
+3.5 deprecated this function, and Python 3.7 is expected to remove it
+altogether. Its predecessor function :py:func:`platform.dist` was already
+deprecated since Python 2.6 and is also expected to be removed in Python 3.7.
+Still, there are many cases in which access to Linux distribution information
+is needed. See `Python issue 1322 <https://bugs.python.org/issue1322>`_ for
+more information.
+"""
+
+import os
+import re
+import sys
+import shlex
+import subprocess
+
+
+if not sys.platform.startswith('linux'):
+ raise ImportError('Unsupported platform: {0}'.format(sys.platform))
+
+
+_UNIXCONFDIR = '/etc'
+_OS_RELEASE_BASENAME = 'os-release'
+
+#: Translation table for normalizing the "ID" attribute defined in os-release
+#: files, for use by the :func:`distro.id` method.
+#:
+#: * Key: Value as defined in the os-release file, translated to lower case,
+#: with blanks translated to underscores.
+#:
+#: * Value: Normalized value.
+NORMALIZED_OS_ID = {}
+
+#: Translation table for normalizing the "Distributor ID" attribute returned by
+#: the lsb_release command, for use by the :func:`distro.id` method.
+#:
+#: * Key: Value as returned by the lsb_release command, translated to lower
+#: case, with blanks translated to underscores.
+#:
+#: * Value: Normalized value.
+NORMALIZED_LSB_ID = {
+ 'enterpriseenterprise': 'oracle', # Oracle Enterprise Linux
+ 'redhatenterpriseworkstation': 'rhel', # RHEL 6, 7 Workstation
+ 'redhatenterpriseserver': 'rhel', # RHEL 6, 7 Server
+}
+
+#: Translation table for normalizing the distro ID derived from the file name
+#: of distro release files, for use by the :func:`distro.id` method.
+#:
+#: * Key: Value as derived from the file name of a distro release file,
+#: translated to lower case, with blanks translated to underscores.
+#:
+#: * Value: Normalized value.
+NORMALIZED_DISTRO_ID = {
+ 'redhat': 'rhel', # RHEL 6.x, 7.x
+}
+
+
+# Pattern for content of distro release file (reversed)
+_DISTRO_RELEASE_CONTENT_REVERSED_PATTERN = re.compile(
+ r'(?:[^)]*\)(.*)\()? *(?:STL )?([\d.+\-a-z]*\d) *(?:esaeler *)?(.+)')
+
+# Pattern for base file name of distro release file
+_DISTRO_RELEASE_BASENAME_PATTERN = re.compile(
+ r'(\w+)[-_](release|version)$')
+
+# Base file names to be ignored when searching for distro release file
+_DISTRO_RELEASE_IGNORE_BASENAMES = (
+ 'debian_version',
+ 'lsb-release',
+ 'oem-release',
+ _OS_RELEASE_BASENAME,
+ 'system-release'
+)
+
+
+def linux_distribution(full_distribution_name=True):
+ """
+ Return information about the current Linux distribution as a tuple
+ ``(id_name, version, codename)`` with items as follows:
+
+ * ``id_name``: If *full_distribution_name* is false, the result of
+ :func:`distro.id`. Otherwise, the result of :func:`distro.name`.
+
+ * ``version``: The result of :func:`distro.version`.
+
+ * ``codename``: The result of :func:`distro.codename`.
+
+ The interface of this function is compatible with the original
+ :py:func:`platform.linux_distribution` function, supporting a subset of
+ its parameters.
+
+ The data it returns may not exactly be the same, because it uses more data
+ sources than the original function, and that may lead to different data if
+ the Linux distribution is not consistent across multiple data sources it
+ provides (there are indeed such distributions ...).
+
+ Another reason for differences is the fact that the :func:`distro.id`
+ method normalizes the distro ID string to a reliable machine-readable value
+ for a number of popular Linux distributions.
+ """
+ return _distroi.linux_distribution(full_distribution_name)
+
+
+def id():
+ """
+ Return the distro ID of the current Linux distribution, as a
+ machine-readable string.
+
+ For a number of Linux distributions, the returned distro ID value is
+ *reliable*, in the sense that it is documented and that it does not change
+ across releases of the distribution.
+
+ This package maintains the following reliable distro ID values:
+
+ ============== =========================================
+ Distro ID Distribution
+ ============== =========================================
+ "ubuntu" Ubuntu
+ "debian" Debian
+ "rhel" RedHat Enterprise Linux
+ "centos" CentOS
+ "fedora" Fedora
+ "sles" SUSE Linux Enterprise Server
+ "opensuse" openSUSE
+ "amazon" Amazon Linux
+ "arch" Arch Linux
+ "cloudlinux" CloudLinux OS
+ "exherbo" Exherbo Linux
+ "gentoo" GenToo Linux
+ "ibm_powerkvm" IBM PowerKVM
+ "kvmibm" KVM for IBM z Systems
+ "linuxmint" Linux Mint
+ "mageia" Mageia
+ "mandriva" Mandriva Linux
+ "parallels" Parallels
+ "pidora" Pidora
+ "raspbian" Raspbian
+ "oracle" Oracle Linux (and Oracle Enterprise Linux)
+ "scientific" Scientific Linux
+ "slackware" Slackware
+ "xenserver" XenServer
+ ============== =========================================
+
+ If you have a need to get distros for reliable IDs added into this set,
+ or if you find that the :func:`distro.id` function returns a different
+ distro ID for one of the listed distros, please create an issue in the
+ `distro issue tracker`_.
+
+ **Lookup hierarchy and transformations:**
+
+ First, the ID is obtained from the following sources, in the specified
+ order. The first available and non-empty value is used:
+
+ * the value of the "ID" attribute of the os-release file,
+
+ * the value of the "Distributor ID" attribute returned by the lsb_release
+ command,
+
+ * the first part of the file name of the distro release file,
+
+ The so determined ID value then passes the following transformations,
+ before it is returned by this method:
+
+ * it is translated to lower case,
+
+ * blanks (which should not be there anyway) are translated to underscores,
+
+ * a normalization of the ID is performed, based upon
+ `normalization tables`_. The purpose of this normalization is to ensure
+ that the ID is as reliable as possible, even across incompatible changes
+ in the Linux distributions. A common reason for an incompatible change is
+ the addition of an os-release file, or the addition of the lsb_release
+ command, with ID values that differ from what was previously determined
+ from the distro release file name.
+ """
+ return _distroi.id()
+
+
+def name(pretty=False):
+ """
+ Return the name of the current Linux distribution, as a human-readable
+ string.
+
+ If *pretty* is false, the name is returned without version or codename.
+ (e.g. "CentOS Linux")
+
+ If *pretty* is true, the version and codename are appended.
+ (e.g. "CentOS Linux 7.1.1503 (Core)")
+
+ **Lookup hierarchy:**
+
+ The name is obtained from the following sources, in the specified order.
+ The first available and non-empty value is used:
+
+ * If *pretty* is false:
+
+ - the value of the "NAME" attribute of the os-release file,
+
+ - the value of the "Distributor ID" attribute returned by the lsb_release
+ command,
+
+ - the value of the "<name>" field of the distro release file.
+
+ * If *pretty* is true:
+
+ - the value of the "PRETTY_NAME" attribute of the os-release file,
+
+ - the value of the "Description" attribute returned by the lsb_release
+ command,
+
+ - the value of the "<name>" field of the distro release file, appended
+ with the value of the pretty version ("<version_id>" and "<codename>"
+ fields) of the distro release file, if available.
+ """
+ return _distroi.name(pretty)
+
+
+def version(pretty=False, best=False):
+ """
+ Return the version of the current Linux distribution, as a human-readable
+ string.
+
+ If *pretty* is false, the version is returned without codename (e.g.
+ "7.0").
+
+ If *pretty* is true, the codename in parenthesis is appended, if the
+ codename is non-empty (e.g. "7.0 (Maipo)").
+
+ Some distributions provide version numbers with different precisions in
+ the different sources of distribution information. Examining the different
+ sources in a fixed priority order does not always yield the most precise
+ version (e.g. for Debian 8.2, or CentOS 7.1).
+
+ The *best* parameter can be used to control the approach for the returned
+ version:
+
+ If *best* is false, the first non-empty version number in priority order of
+ the examined sources is returned.
+
+ If *best* is true, the most precise version number out of all examined
+ sources is returned.
+
+ **Lookup hierarchy:**
+
+ In all cases, the version number is obtained from the following sources.
+ If *best* is false, this order represents the priority order:
+
+ * the value of the "VERSION_ID" attribute of the os-release file,
+ * the value of the "Release" attribute returned by the lsb_release
+ command,
+ * the version number parsed from the "<version_id>" field of the first line
+ of the distro release file,
+ * the version number parsed from the "PRETTY_NAME" attribute of the
+ os-release file, if it follows the format of the distro release files.
+ * the version number parsed from the "Description" attribute returned by
+ the lsb_release command, if it follows the format of the distro release
+ files.
+ """
+ return _distroi.version(pretty, best)
+
+
+def version_parts(best=False):
+ """
+ Return the version of the current Linux distribution as a tuple
+ ``(major, minor, build_number)`` with items as follows:
+
+ * ``major``: The result of :func:`distro.major_version`.
+
+ * ``minor``: The result of :func:`distro.minor_version`.
+
+ * ``build_number``: The result of :func:`distro.build_number`.
+
+ For a description of the *best* parameter, see the :func:`distro.version`
+ method.
+ """
+ return _distroi.version_parts(best)
+
+
+def major_version(best=False):
+ """
+ Return the major version of the current Linux distribution, as a string,
+ if provided.
+ Otherwise, the empty string is returned. The major version is the first
+ part of the dot-separated version string.
+
+ For a description of the *best* parameter, see the :func:`distro.version`
+ method.
+ """
+ return _distroi.major_version(best)
+
+
+def minor_version(best=False):
+ """
+ Return the minor version of the current Linux distribution, as a string,
+ if provided.
+ Otherwise, the empty string is returned. The minor version is the second
+ part of the dot-separated version string.
+
+ For a description of the *best* parameter, see the :func:`distro.version`
+ method.
+ """
+ return _distroi.minor_version(best)
+
+
+def build_number(best=False):
+ """
+ Return the build number of the current Linux distribution, as a string,
+ if provided.
+ Otherwise, the empty string is returned. The build number is the third part
+ of the dot-separated version string.
+
+ For a description of the *best* parameter, see the :func:`distro.version`
+ method.
+ """
+ return _distroi.build_number(best)
+
+
+def like():
+ """
+ Return a space-separated list of distro IDs of distributions that are
+ closely related to the current Linux distribution in regards to packaging
+ and programming interfaces, for example distributions the current
+ distribution is a derivative from.
+
+ **Lookup hierarchy:**
+
+ This information item is only provided by the os-release file.
+ For details, see the description of the "ID_LIKE" attribute in the
+ `os-release man page
+ <http://www.freedesktop.org/software/systemd/man/os-release.html>`_.
+ """
+ return _distroi.like()
+
+
+def codename():
+ """
+ Return the codename for the release of the current Linux distribution,
+ as a string.
+
+ If the distribution does not have a codename, an empty string is returned.
+
+ Note that the returned codename is not always really a codename. For
+ example, openSUSE returns "x86_64". This function does not handle such
+ cases in any special way and just returns the string it finds, if any.
+
+ **Lookup hierarchy:**
+
+ * the codename within the "VERSION" attribute of the os-release file, if
+ provided,
+
+ * the value of the "Codename" attribute returned by the lsb_release
+ command,
+
+ * the value of the "<codename>" field of the distro release file.
+ """
+ return _distroi.codename()
+
+
+def info(pretty=False, best=False):
+ """
+ Return certain machine-readable information items about the current Linux
+ distribution in a dictionary, as shown in the following example:
+
+ .. sourcecode:: python
+
+ {
+ 'id': 'rhel',
+ 'version': '7.0',
+ 'version_parts': {
+ 'major': '7',
+ 'minor': '0',
+ 'build_number': ''
+ },
+ 'like': 'fedora',
+ 'codename': 'Maipo'
+ }
+
+ The dictionary structure and keys are always the same, regardless of which
+ information items are available in the underlying data sources. The values
+ for the various keys are as follows:
+
+ * ``id``: The result of :func:`distro.id`.
+
+ * ``version``: The result of :func:`distro.version`.
+
+ * ``version_parts -> major``: The result of :func:`distro.major_version`.
+
+ * ``version_parts -> minor``: The result of :func:`distro.minor_version`.
+
+ * ``version_parts -> build_number``: The result of
+ :func:`distro.build_number`.
+
+ * ``like``: The result of :func:`distro.like`.
+
+ * ``codename``: The result of :func:`distro.codename`.
+
+ For a description of the *pretty* and *best* parameters, see the
+ :func:`distro.version` method.
+ """
+ return _distroi.info(pretty, best)
+
+
+def os_release_info():
+ """
+ Return a dictionary containing key-value pairs for the information items
+ from the os-release file data source of the current Linux distribution.
+
+ See `os-release file`_ for details about these information items.
+ """
+ return _distroi.os_release_info()
+
+
+def lsb_release_info():
+ """
+ Return a dictionary containing key-value pairs for the information items
+ from the lsb_release command data source of the current Linux distribution.
+
+ See `lsb_release command output`_ for details about these information
+ items.
+ """
+ return _distroi.lsb_release_info()
+
+
+def distro_release_info():
+ """
+ Return a dictionary containing key-value pairs for the information items
+ from the distro release file data source of the current Linux distribution.
+
+ See `distro release file`_ for details about these information items.
+ """
+ return _distroi.distro_release_info()
+
+
+def os_release_attr(attribute):
+ """
+ Return a single named information item from the os-release file data source
+ of the current Linux distribution.
+
+ Parameters:
+
+ * ``attribute`` (string): Key of the information item.
+
+ Returns:
+
+ * (string): Value of the information item, if the item exists.
+ The empty string, if the item does not exist.
+
+ See `os-release file`_ for details about these information items.
+ """
+ return _distroi.os_release_attr(attribute)
+
+
+def lsb_release_attr(attribute):
+ """
+ Return a single named information item from the lsb_release command output
+ data source of the current Linux distribution.
+
+ Parameters:
+
+ * ``attribute`` (string): Key of the information item.
+
+ Returns:
+
+ * (string): Value of the information item, if the item exists.
+ The empty string, if the item does not exist.
+
+ See `lsb_release command output`_ for details about these information
+ items.
+ """
+ return _distroi.lsb_release_attr(attribute)
+
+
+def distro_release_attr(attribute):
+ """
+ Return a single named information item from the distro release file
+ data source of the current Linux distribution.
+
+ Parameters:
+
+ * ``attribute`` (string): Key of the information item.
+
+ Returns:
+
+ * (string): Value of the information item, if the item exists.
+ The empty string, if the item does not exist.
+
+ See `distro release file`_ for details about these information items.
+ """
+ return _distroi.distro_release_attr(attribute)
+
+
+class LinuxDistribution(object):
+ """
+ Provides information about a Linux distribution.
+
+ This package creates a private module-global instance of this class with
+ default initialization arguments, that is used by the
+ `consolidated accessor functions`_ and `single source accessor functions`_.
+ By using default initialization arguments, that module-global instance
+ returns data about the current Linux distribution (i.e. the distro this
+ package runs on).
+
+ Normally, it is not necessary to create additional instances of this class.
+ However, in situations where control is needed over the exact data sources
+ that are used, instances of this class can be created with a specific
+ distro release file, or a specific os-release file, or without invoking the
+ lsb_release command.
+ """
+
+ def __init__(self,
+ include_lsb=True,
+ os_release_file='',
+ distro_release_file=''):
+ """
+ The initialization method of this class gathers information from the
+ available data sources, and stores that in private instance attributes.
+ Subsequent access to the information items uses these private instance
+ attributes, so that the data sources are read only once.
+
+ Parameters:
+
+ * ``include_lsb`` (bool): Controls whether the
+ `lsb_release command output`_ is included as a data source.
+
+ If the lsb_release command is not available in the program execution
+ path, the data source for the lsb_release command will be empty.
+
+ * ``os_release_file`` (string): The path name of the
+ `os-release file`_ that is to be used as a data source.
+
+ An empty string (the default) will cause the default path name to
+ be used (see `os-release file`_ for details).
+
+ If the specified or defaulted os-release file does not exist, the
+ data source for the os-release file will be empty.
+
+ * ``distro_release_file`` (string): The path name of the
+ `distro release file`_ that is to be used as a data source.
+
+ An empty string (the default) will cause a default search algorithm
+ to be used (see `distro release file`_ for details).
+
+ If the specified distro release file does not exist, or if no default
+ distro release file can be found, the data source for the distro
+ release file will be empty.
+
+ Public instance attributes:
+
+ * ``os_release_file`` (string): The path name of the
+ `os-release file`_ that is actually used as a data source. The
+ empty string if no distro release file is used as a data source.
+
+ * ``distro_release_file`` (string): The path name of the
+ `distro release file`_ that is actually used as a data source. The
+ empty string if no distro release file is used as a data source.
+
+ Raises:
+
+ * :py:exc:`IOError`: Some I/O issue with an os-release file or distro
+ release file.
+
+ * :py:exc:`subprocess.CalledProcessError`: The lsb_release command had
+ some issue (other than not being available in the program execution
+ path).
+
+ * :py:exc:`UnicodeError`: A data source has unexpected characters or
+ uses an unexpected encoding.
+ """
+ self.os_release_file = os_release_file or \
+ os.path.join(_UNIXCONFDIR, _OS_RELEASE_BASENAME)
+ self.distro_release_file = distro_release_file or '' # updated later
+ self._os_release_info = self._os_release_info()
+ self._lsb_release_info = self._lsb_release_info() \
+ if include_lsb else {}
+ self._distro_release_info = self._distro_release_info()
+
+ def __repr__(self):
+ return \
+ "LinuxDistribution(" \
+ "os_release_file={0!r}, " \
+ "distro_release_file={1!r}, " \
+ "_os_release_info={2!r}, " \
+ "_lsb_release_info={3!r}, " \
+ "_distro_release_info={4!r})".format(
+ self.os_release_file,
+ self.distro_release_file,
+ self._os_release_info,
+ self._lsb_release_info,
+ self._distro_release_info)
+
+ def linux_distribution(self, full_distribution_name=True):
+ """
+ Return information about the Linux distribution that is compatible
+ with Python's :func:`platform.linux_distribution`, supporting a subset
+ of its parameters.
+
+ For details, see :func:`distro.linux_distribution`.
+ """
+ return (
+ self.name() if full_distribution_name else self.id(),
+ self.version(),
+ self.codename()
+ )
+
+ def id(self):
+ """
+ Return the distro ID of the Linux distribution, as a string.
+
+ For details, see :func:`distro.id`.
+ """
+ distro_id = self.os_release_attr('id')
+ if distro_id:
+ distro_id = distro_id.lower().replace(' ', '_')
+ return NORMALIZED_OS_ID.get(distro_id, distro_id)
+
+ distro_id = self.lsb_release_attr('distributor_id')
+ if distro_id:
+ distro_id = distro_id.lower().replace(' ', '_')
+ return NORMALIZED_LSB_ID.get(distro_id, distro_id)
+
+ distro_id = self.distro_release_attr('id')
+ if distro_id:
+ distro_id = distro_id.lower().replace(' ', '_')
+ return NORMALIZED_DISTRO_ID.get(distro_id, distro_id)
+
+ return ''
+
+ def name(self, pretty=False):
+ """
+ Return the name of the Linux distribution, as a string.
+
+ For details, see :func:`distro.name`.
+ """
+ name = self.os_release_attr('name') \
+ or self.lsb_release_attr('distributor_id') \
+ or self.distro_release_attr('name')
+ if pretty:
+ name = self.os_release_attr('pretty_name') \
+ or self.lsb_release_attr('description')
+ if not name:
+ name = self.distro_release_attr('name')
+ version = self.version(pretty=True)
+ if version:
+ name = name + ' ' + version
+ return name or ''
+
+ def version(self, pretty=False, best=False):
+ """
+ Return the version of the Linux distribution, as a string.
+
+ For details, see :func:`distro.version`.
+ """
+ versions = [
+ self.os_release_attr('version_id'),
+ self.lsb_release_attr('release'),
+ self.distro_release_attr('version_id'),
+ self._parse_distro_release_content(
+ self.os_release_attr('pretty_name')).get('version_id', ''),
+ self._parse_distro_release_content(
+ self.lsb_release_attr('description')).get('version_id', '')
+ ]
+ version = ''
+ if best:
+ # This algorithm uses the last version in priority order that has
+ # the best precision. If the versions are not in conflict, that
+ # does not matter; otherwise, using the last one instead of the
+ # first one might be considered a surprise.
+ for v in versions:
+ if v.count(".") > version.count(".") or version == '':
+ version = v
+ else:
+ for v in versions:
+ if v != '':
+ version = v
+ break
+ if pretty and version and self.codename():
+ version = u'{0} ({1})'.format(version, self.codename())
+ return version
+
+ def version_parts(self, best=False):
+ """
+ Return the version of the Linux distribution, as a tuple of version
+ numbers.
+
+ For details, see :func:`distro.version_parts`.
+ """
+ version_str = self.version(best=best)
+ if version_str:
+ g = re.compile(r'(\d+)\.?(\d+)?\.?(\d+)?')
+ m = g.match(version_str)
+ if m:
+ major, minor, build_number = m.groups()
+ return major, minor or '', build_number or ''
+ return '', '', ''
+
+ def major_version(self, best=False):
+ """
+ Return the major version number of the current distribution.
+
+ For details, see :func:`distro.major_version`.
+ """
+ return self.version_parts(best)[0]
+
+ def minor_version(self, best=False):
+ """
+ Return the minor version number of the Linux distribution.
+
+ For details, see :func:`distro.minor_version`.
+ """
+ return self.version_parts(best)[1]
+
+ def build_number(self, best=False):
+ """
+ Return the build number of the Linux distribution.
+
+ For details, see :func:`distro.build_number`.
+ """
+ return self.version_parts(best)[2]
+
+ def like(self):
+ """
+ Return the IDs of distributions that are like the Linux distribution.
+
+ For details, see :func:`distro.like`.
+ """
+ return self.os_release_attr('id_like') or ''
+
+ def codename(self):
+ """
+ Return the codename of the Linux distribution.
+
+ For details, see :func:`distro.codename`.
+ """
+ return self.os_release_attr('codename') \
+ or self.lsb_release_attr('codename') \
+ or self.distro_release_attr('codename') \
+ or ''
+
+ def info(self, pretty=False, best=False):
+ """
+ Return certain machine-readable information about the Linux
+ distribution.
+
+ For details, see :func:`distro.info`.
+ """
+ return dict(
+ id=self.id(),
+ version=self.version(pretty, best),
+ version_parts=dict(
+ major=self.major_version(best),
+ minor=self.minor_version(best),
+ build_number=self.build_number(best)
+ ),
+ like=self.like(),
+ codename=self.codename(),
+ )
+
+ def os_release_info(self):
+ """
+ Return a dictionary containing key-value pairs for the information
+ items from the os-release file data source of the Linux distribution.
+
+ For details, see :func:`distro.os_release_info`.
+ """
+ return self._os_release_info
+
+ def lsb_release_info(self):
+ """
+ Return a dictionary containing key-value pairs for the information
+ items from the lsb_release command data source of the Linux
+ distribution.
+
+ For details, see :func:`distro.lsb_release_info`.
+ """
+ return self._lsb_release_info
+
+ def distro_release_info(self):
+ """
+ Return a dictionary containing key-value pairs for the information
+ items from the distro release file data source of the Linux
+ distribution.
+
+ For details, see :func:`distro.distro_release_info`.
+ """
+ return self._distro_release_info
+
+ def os_release_attr(self, attribute):
+ """
+ Return a single named information item from the os-release file data
+ source of the Linux distribution.
+
+ For details, see :func:`distro.os_release_attr`.
+ """
+ return self._os_release_info.get(attribute, '')
+
+ def lsb_release_attr(self, attribute):
+ """
+ Return a single named information item from the lsb_release command
+ output data source of the Linux distribution.
+
+ For details, see :func:`distro.lsb_release_attr`.
+ """
+ return self._lsb_release_info.get(attribute, '')
+
+ def distro_release_attr(self, attribute):
+ """
+ Return a single named information item from the distro release file
+ data source of the Linux distribution.
+
+ For details, see :func:`distro.distro_release_attr`.
+ """
+ return self._distro_release_info.get(attribute, '')
+
+ def _os_release_info(self):
+ """
+ Get the information items from the specified os-release file.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ if os.path.isfile(self.os_release_file):
+ with open(self.os_release_file, 'r') as f:
+ return self._parse_os_release_content(f)
+ return {}
+
+ @staticmethod
+ def _parse_os_release_content(lines):
+ """
+ Parse the lines of an os-release file.
+
+ Parameters:
+
+ * lines: Iterable through the lines in the os-release file.
+ Each line must be a unicode string or a UTF-8 encoded byte
+ string.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ props = {}
+ lexer = shlex.shlex(lines, posix=True)
+ lexer.whitespace_split = True
+
+ # The shlex module defines its `wordchars` variable using literals,
+ # making it dependent on the encoding of the Python source file.
+ # In Python 2.6 and 2.7, the shlex source file is encoded in
+ # 'iso-8859-1', and the `wordchars` variable is defined as a byte
+ # string. This causes a UnicodeDecodeError to be raised when the
+ # parsed content is a unicode object. The following fix resolves that
+ # (... but it should be fixed in shlex...):
+ if sys.version_info[0] == 2 and isinstance(lexer.wordchars, bytes):
+ lexer.wordchars = lexer.wordchars.decode('iso-8859-1')
+
+ tokens = list(lexer)
+ for token in tokens:
+ # At this point, all shell-like parsing has been done (i.e.
+ # comments processed, quotes and backslash escape sequences
+ # processed, multi-line values assembled, trailing newlines
+ # stripped, etc.), so the tokens are now either:
+ # * variable assignments: var=value
+ # * commands or their arguments (not allowed in os-release)
+ if '=' in token:
+ k, v = token.split('=', 1)
+ if isinstance(v, bytes):
+ v = v.decode('utf-8')
+ props[k.lower()] = v
+ if k == 'VERSION':
+ # this handles cases in which the codename is in
+ # the `(CODENAME)` (rhel, centos, fedora) format
+ # or in the `, CODENAME` format (Ubuntu).
+ codename = re.search(r'(\(\D+\))|,(\s+)?\D+', v)
+ if codename:
+ codename = codename.group()
+ codename = codename.strip('()')
+ codename = codename.strip(',')
+ codename = codename.strip()
+ # codename appears within paranthese.
+ props['codename'] = codename
+ else:
+ props['codename'] = ''
+ else:
+ # Ignore any tokens that are not variable assignments
+ pass
+ return props
+
+ def _lsb_release_info(self):
+ """
+ Get the information items from the lsb_release command output.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ cmd = 'lsb_release -a'
+ p = subprocess.Popen(
+ cmd,
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ out, err = p.communicate()
+ out, err = out.decode('ascii'), err.decode('ascii')
+ rc = p.returncode
+ if rc == 0:
+ content = out.splitlines()
+ return self._parse_lsb_release_content(content)
+ elif rc == 127: # Command not found
+ return {}
+ else:
+ if sys.version_info[:2] >= (3, 5):
+ raise subprocess.CalledProcessError(rc, cmd, out, err)
+ elif sys.version_info[:2] >= (2, 7):
+ raise subprocess.CalledProcessError(rc, cmd, out)
+ elif sys.version_info[:2] == (2, 6):
+ raise subprocess.CalledProcessError(rc, cmd)
+
+ @staticmethod
+ def _parse_lsb_release_content(lines):
+ """
+ Parse the output of the lsb_release command.
+
+ Parameters:
+
+ * lines: Iterable through the lines of the lsb_release output.
+ Each line must be a unicode string or a UTF-8 encoded byte
+ string.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ props = {}
+ for line in lines:
+ if isinstance(line, bytes):
+ line = line.decode('utf-8')
+ kv = line.strip('\n').split(':', 1)
+ if len(kv) != 2:
+ # Ignore lines without colon.
+ continue
+ k, v = kv
+ props.update({k.replace(' ', '_').lower(): v.strip()})
+ return props
+
+ def _distro_release_info(self):
+ """
+ Get the information items from the specified distro release file.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ if self.distro_release_file:
+ # If it was specified, we use it and parse what we can, even if
+ # its file name or content does not match the expected pattern.
+ distro_info = self._parse_distro_release_file(
+ self.distro_release_file)
+ basename = os.path.basename(self.distro_release_file)
+ # The file name pattern for user-specified distro release files
+ # is somewhat more tolerant (compared to when searching for the
+ # file), because we want to use what was specified as best as
+ # possible.
+ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
+ if match:
+ distro_info['id'] = match.group(1)
+ return distro_info
+ else:
+ basenames = os.listdir(_UNIXCONFDIR)
+ # We sort for repeatability in cases where there are multiple
+ # distro specific files; e.g. CentOS, Oracle, Enterprise all
+ # containing `redhat-release` on top of their own.
+ basenames.sort()
+ for basename in basenames:
+ if basename in _DISTRO_RELEASE_IGNORE_BASENAMES:
+ continue
+ match = _DISTRO_RELEASE_BASENAME_PATTERN.match(basename)
+ if match:
+ filepath = os.path.join(_UNIXCONFDIR, basename)
+ distro_info = self._parse_distro_release_file(filepath)
+ if 'name' in distro_info:
+ # The name is always present if the pattern matches
+ self.distro_release_file = filepath
+ distro_info['id'] = match.group(1)
+ return distro_info
+ return {}
+
+ def _parse_distro_release_file(self, filepath):
+ """
+ Parse a distro release file.
+
+ Parameters:
+
+ * filepath: Path name of the distro release file.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ if os.path.isfile(filepath):
+ with open(filepath, 'r') as fp:
+ # Only parse the first line. For instance, on SLES there
+ # are multiple lines. We don't want them...
+ return self._parse_distro_release_content(fp.readline())
+ return {}
+
+ @staticmethod
+ def _parse_distro_release_content(line):
+ """
+ Parse a line from a distro release file.
+
+ Parameters:
+ * line: Line from the distro release file. Must be a unicode string
+ or a UTF-8 encoded byte string.
+
+ Returns:
+ A dictionary containing all information items.
+ """
+ if isinstance(line, bytes):
+ line = line.decode('utf-8')
+ m = _DISTRO_RELEASE_CONTENT_REVERSED_PATTERN.match(
+ line.strip()[::-1])
+ distro_info = {}
+ if m:
+ distro_info['name'] = m.group(3)[::-1] # regexp ensures non-None
+ if m.group(2):
+ distro_info['version_id'] = m.group(2)[::-1]
+ if m.group(1):
+ distro_info['codename'] = m.group(1)[::-1]
+ elif line:
+ distro_info['name'] = line.strip()
+ return distro_info
+
+
+_distroi = LinuxDistribution()
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 22ca85abf9..31e09f2fe6 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -22,28 +22,49 @@
# 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 collections
+import errno
+import fileinput
import glob
+import numbers
+import os
import re
import shutil
import stat
-import errno
-import getpass
-from contextlib import contextmanager
import subprocess
-import fileinput
+import sys
+from contextlib import contextmanager
import llnl.util.tty as tty
-
-__all__ = ['set_install_permissions', 'install', 'install_tree',
- 'traverse_tree',
- 'expand_user', 'working_dir', 'touch', 'touchp', 'mkdirp',
- 'force_remove', 'join_path', 'ancestor', 'can_access',
- 'filter_file',
- 'FileFilter', 'change_sed_delimiter', 'is_exe', 'force_symlink',
- 'set_executable', 'copy_mode', 'unset_executable_mode',
- 'remove_dead_links', 'remove_linked_tree', 'find_library_path',
- 'fix_darwin_install_name', 'to_link_flags', 'to_lib_name']
+from llnl.util.lang import dedupe
+
+__all__ = [
+ 'FileFilter',
+ 'LibraryList',
+ 'ancestor',
+ 'can_access',
+ 'change_sed_delimiter',
+ 'copy_mode',
+ 'filter_file',
+ 'find_libraries',
+ 'fix_darwin_install_name',
+ 'force_remove',
+ 'force_symlink',
+ 'install',
+ 'install_tree',
+ 'is_exe',
+ 'join_path',
+ 'mkdirp',
+ 'remove_dead_links',
+ 'remove_if_dead_link',
+ 'remove_linked_tree',
+ 'set_executable',
+ 'set_install_permissions',
+ 'touch',
+ 'touchp',
+ 'traverse_tree',
+ 'unset_executable_mode',
+ 'working_dir']
def filter_file(regex, repl, *filenames, **kwargs):
@@ -206,16 +227,6 @@ def is_exe(path):
return os.path.isfile(path) and os.access(path, os.X_OK)
-def expand_user(path):
- """Find instances of '%u' in a path and replace with the current user's
- username."""
- username = getpass.getuser()
- if not username and '%u' in path:
- tty.die("Couldn't get username to complete path '%s'" % path)
-
- return path.replace('%u', username)
-
-
def mkdirp(*paths):
"""Creates a directory, as well as parent directories if needed."""
for path in paths:
@@ -326,7 +337,7 @@ def traverse_tree(source_root, dest_root, rel_path='', **kwargs):
follow_links = kwargs.get('follow_link', False)
# Yield in pre or post order?
- order = kwargs.get('order', 'pre')
+ order = kwargs.get('order', 'pre')
if order not in ('pre', 'post'):
raise ValueError("Order must be 'pre' or 'post'.")
@@ -338,7 +349,7 @@ def traverse_tree(source_root, dest_root, rel_path='', **kwargs):
return
source_path = os.path.join(source_root, rel_path)
- dest_path = os.path.join(dest_root, rel_path)
+ dest_path = os.path.join(dest_root, rel_path)
# preorder yields directories before children
if order == 'pre':
@@ -346,8 +357,8 @@ def traverse_tree(source_root, dest_root, rel_path='', **kwargs):
for f in os.listdir(source_path):
source_child = os.path.join(source_path, f)
- dest_child = os.path.join(dest_path, f)
- rel_child = os.path.join(rel_path, f)
+ dest_child = os.path.join(dest_path, f)
+ rel_child = os.path.join(rel_path, f)
# Treat as a directory
if os.path.isdir(source_child) and (
@@ -384,10 +395,20 @@ def remove_dead_links(root):
"""
for file in os.listdir(root):
path = join_path(root, file)
- if os.path.islink(path):
- real_path = os.path.realpath(path)
- if not os.path.exists(real_path):
- os.unlink(path)
+ remove_if_dead_link(path)
+
+
+def remove_if_dead_link(path):
+ """
+ Removes the argument if it is a dead link, does nothing otherwise
+
+ Args:
+ path: the potential dead link
+ """
+ if os.path.islink(path):
+ real_path = os.path.realpath(path)
+ if not os.path.exists(real_path):
+ os.unlink(path)
def remove_linked_tree(path):
@@ -440,35 +461,162 @@ def fix_darwin_install_name(path):
stdout=subprocess.PIPE).communicate()[0]
break
+# Utilities for libraries
-def to_lib_name(library):
- """Transforms a path to the library /path/to/lib<name>.xyz into <name>
+
+class LibraryList(collections.Sequence):
+ """Sequence of absolute paths to libraries
+
+ Provides a few convenience methods to manipulate library paths and get
+ commonly used compiler flags or names
"""
- # Assume libXYZ.suffix
- return os.path.basename(library)[3:].split(".")[0]
+ def __init__(self, libraries):
+ self.libraries = list(libraries)
-def to_link_flags(library):
- """Transforms a path to a <library> into linking flags -L<dir> -l<name>.
+ @property
+ def directories(self):
+ """Stable de-duplication of the directories where the libraries
+ reside
- Return:
- A string of linking flags.
- """
- dir = os.path.dirname(library)
- name = to_lib_name(library)
- res = '-L%s -l%s' % (dir, name)
- return res
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir1/libc.a'])
+ >>> assert l.directories == ['/dir1', '/dir2']
+ """
+ return list(dedupe(
+ os.path.dirname(x) for x in self.libraries if os.path.dirname(x)
+ ))
+
+ @property
+ def basenames(self):
+ """Stable de-duplication of the base-names in the list
+
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir3/liba.a'])
+ >>> assert l.basenames == ['liba.a', 'libb.a']
+ """
+ return list(dedupe(os.path.basename(x) for x in self.libraries))
+
+ @property
+ def names(self):
+ """Stable de-duplication of library names in the list
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir3/liba.so'])
+ >>> assert l.names == ['a', 'b']
+ """
+ return list(dedupe(x.split('.')[0][3:] for x in self.basenames))
-def find_library_path(libname, *paths):
- """Searches for a file called <libname> in each path.
+ @property
+ def search_flags(self):
+ """Search flags for the libraries
- Return:
- directory where the library was found, if found. None otherwise.
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir1/liba.so'])
+ >>> assert l.search_flags == '-L/dir1 -L/dir2'
+ """
+ return ' '.join(['-L' + x for x in self.directories])
+ @property
+ def link_flags(self):
+ """Link flags for the libraries
+
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir1/liba.so'])
+ >>> assert l.search_flags == '-la -lb'
+ """
+ return ' '.join(['-l' + name for name in self.names])
+
+ @property
+ def ld_flags(self):
+ """Search flags + link flags
+
+ >>> l = LibraryList(['/dir1/liba.a', '/dir2/libb.a', '/dir1/liba.so'])
+ >>> assert l.search_flags == '-L/dir1 -L/dir2 -la -lb'
+ """
+ return self.search_flags + ' ' + self.link_flags
+
+ def __getitem__(self, item):
+ cls = type(self)
+ if isinstance(item, numbers.Integral):
+ return self.libraries[item]
+ return cls(self.libraries[item])
+
+ def __add__(self, other):
+ return LibraryList(dedupe(self.libraries + list(other)))
+
+ def __radd__(self, other):
+ return self.__add__(other)
+
+ def __eq__(self, other):
+ return self.libraries == other.libraries
+
+ def __len__(self):
+ return len(self.libraries)
+
+ def joined(self, separator=' '):
+ return separator.join(self.libraries)
+
+ def __repr__(self):
+ return self.__class__.__name__ + '(' + repr(self.libraries) + ')'
+
+ def __str__(self):
+ return self.joined()
+
+
+def find_libraries(args, root, shared=True, recurse=False):
+ """Returns an iterable object containing a list of full paths to
+ libraries if found.
+
+ Args:
+ args: iterable object containing a list of library names to \
+ search for (e.g. 'libhdf5')
+ root: root folder where to start searching
+ shared: if True searches for shared libraries, otherwise for static
+ recurse: if False search only root folder, if True descends top-down \
+ from the root
+
+ Returns:
+ list of full paths to the libraries that have been found
"""
- for path in paths:
- library = join_path(path, libname)
- if os.path.exists(library):
- return path
- return None
+ if not isinstance(args, collections.Sequence) or isinstance(args, str):
+ message = '{0} expects a sequence of strings as first argument'
+ message += ' [got {1} instead]'
+ raise TypeError(message.format(find_libraries.__name__, type(args)))
+
+ # Construct the right suffix for the library
+ if shared is True:
+ suffix = 'dylib' if sys.platform == 'darwin' else 'so'
+ else:
+ suffix = 'a'
+ # List of libraries we are searching with suffixes
+ libraries = ['{0}.{1}'.format(lib, suffix) for lib in args]
+ # Search method
+ if recurse is False:
+ search_method = _find_libraries_non_recursive
+ else:
+ search_method = _find_libraries_recursive
+
+ return search_method(libraries, root)
+
+
+def _find_libraries_recursive(libraries, root):
+ library_dict = collections.defaultdict(list)
+ for path, _, files in os.walk(root):
+ for lib in libraries:
+ if lib in files:
+ library_dict[lib].append(
+ join_path(path, lib)
+ )
+ answer = []
+ for lib in libraries:
+ answer.extend(library_dict[lib])
+ return LibraryList(answer)
+
+
+def _find_libraries_non_recursive(libraries, root):
+
+ def lib_or_none(lib):
+ library = join_path(root, lib)
+ if not os.path.exists(library):
+ return None
+ return library
+
+ return LibraryList(
+ [lib_or_none(lib) for lib in libraries if lib_or_none(lib) is not None]
+ )
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index df32012e2d..253334c416 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -374,6 +374,22 @@ def DictWrapper(dictionary):
return wrapper()
+def dedupe(sequence):
+ """Yields a stable de-duplication of an hashable sequence
+
+ Args:
+ sequence: hashable sequence to be de-duplicated
+
+ Returns:
+ stable de-duplication of the sequence
+ """
+ seen = set()
+ for x in sequence:
+ if x not in seen:
+ yield x
+ seen.add(x)
+
+
class RequiredAttributeError(ValueError):
def __init__(self, message):
diff --git a/lib/spack/llnl/util/lock.py b/lib/spack/llnl/util/lock.py
index aa8272d5fe..2e44a94798 100644
--- a/lib/spack/llnl/util/lock.py
+++ b/lib/spack/llnl/util/lock.py
@@ -28,9 +28,13 @@ import errno
import time
import socket
+import llnl.util.tty as tty
+
+
__all__ = ['Lock', 'LockTransaction', 'WriteTransaction', 'ReadTransaction',
'LockError']
+
# Default timeout in seconds, after which locks will raise exceptions.
_default_timeout = 60
@@ -41,42 +45,86 @@ _sleep_time = 1e-5
class Lock(object):
"""This is an implementation of a filesystem lock using Python's lockf.
- In Python, `lockf` actually calls `fcntl`, so this should work with any
- filesystem implementation that supports locking through the fcntl calls.
- This includes distributed filesystems like Lustre (when flock is enabled)
- and recent NFS versions.
-
+ In Python, `lockf` actually calls `fcntl`, so this should work with
+ any filesystem implementation that supports locking through the fcntl
+ calls. This includes distributed filesystems like Lustre (when flock
+ is enabled) and recent NFS versions.
"""
- def __init__(self, file_path):
- self._file_path = file_path
- self._fd = None
+ def __init__(self, path, start=0, length=0):
+ """Construct a new lock on the file at ``path``.
+
+ By default, the lock applies to the whole file. Optionally,
+ caller can specify a byte range beginning ``start`` bytes from
+ the start of the file and extending ``length`` bytes from there.
+
+ This exposes a subset of fcntl locking functionality. It does
+ not currently expose the ``whence`` parameter -- ``whence`` is
+ always os.SEEK_SET and ``start`` is always evaluated from the
+ beginning of the file.
+ """
+ self.path = path
+ self._file = None
self._reads = 0
self._writes = 0
- def _lock(self, op, timeout):
+ # byte range parameters
+ self._start = start
+ self._length = length
+
+ # PID and host of lock holder
+ self.pid = self.old_pid = None
+ self.host = self.old_host = None
+
+ def _lock(self, op, timeout=_default_timeout):
"""This takes a lock using POSIX locks (``fnctl.lockf``).
- The lock is implemented as a spin lock using a nonblocking
- call to lockf().
+ The lock is implemented as a spin lock using a nonblocking call
+ to lockf().
On acquiring an exclusive lock, the lock writes this process's
- pid and host to the lock file, in case the holding process
- needs to be killed later.
+ pid and host to the lock file, in case the holding process needs
+ to be killed later.
If the lock times out, it raises a ``LockError``.
"""
start_time = time.time()
while (time.time() - start_time) < timeout:
try:
- if self._fd is None:
- self._fd = os.open(self._file_path, os.O_RDWR)
-
- fcntl.lockf(self._fd, op | fcntl.LOCK_NB)
+ # If we could write the file, we'd have opened it 'r+'.
+ # Raise an error when we attempt to upgrade to a write lock.
+ if op == fcntl.LOCK_EX:
+ if self._file and self._file.mode == 'r':
+ raise LockError(
+ "Can't take exclusive lock on read-only file: %s"
+ % self.path)
+
+ # Create file and parent directories if they don't exist.
+ if self._file is None:
+ self._ensure_parent_directory()
+
+ # Prefer to open 'r+' to allow upgrading to write
+ # lock later if possible. Open read-only if we can't
+ # write the lock file at all.
+ os_mode, fd_mode = (os.O_RDWR | os.O_CREAT), 'r+'
+ if os.path.exists(self.path) and not os.access(
+ self.path, os.W_OK):
+ os_mode, fd_mode = os.O_RDONLY, 'r'
+
+ fd = os.open(self.path, os_mode)
+ self._file = os.fdopen(fd, fd_mode)
+
+ # Try to get the lock (will raise if not available.)
+ fcntl.lockf(self._file, op | fcntl.LOCK_NB,
+ self._length, self._start, os.SEEK_SET)
+
+ # All locks read the owner PID and host
+ self._read_lock_data()
+
+ # Exclusive locks write their PID/host
if op == fcntl.LOCK_EX:
- os.write(
- self._fd,
- "pid=%s,host=%s" % (os.getpid(), socket.getfqdn()))
+ self._write_lock_data()
+
return
except IOError as error:
@@ -88,6 +136,40 @@ class Lock(object):
raise LockError("Timed out waiting for lock.")
+ def _ensure_parent_directory(self):
+ parent = os.path.dirname(self.path)
+ try:
+ os.makedirs(parent)
+ return True
+ except OSError as e:
+ # makedirs can fail when diretory already exists.
+ if not (e.errno == errno.EEXIST and os.path.isdir(parent) or
+ e.errno == errno.EISDIR):
+ raise
+
+ def _read_lock_data(self):
+ """Read PID and host data out of the file if it is there."""
+ line = self._file.read()
+ if line:
+ pid, host = line.strip().split(',')
+ _, _, self.pid = pid.rpartition('=')
+ _, _, self.host = host.rpartition('=')
+
+ def _write_lock_data(self):
+ """Write PID and host data to the file, recording old values."""
+ self.old_pid = self.pid
+ self.old_host = self.host
+
+ self.pid = os.getpid()
+ self.host = socket.getfqdn()
+
+ # write pid, host to disk to sync over FS
+ self._file.seek(0)
+ self._file.write("pid=%s,host=%s" % (self.pid, self.host))
+ self._file.truncate()
+ self._file.flush()
+ os.fsync(self._file.fileno())
+
def _unlock(self):
"""Releases a lock using POSIX locks (``fcntl.lockf``)
@@ -95,9 +177,10 @@ class Lock(object):
be masquerading as write locks, but this removes either.
"""
- fcntl.lockf(self._fd, fcntl.LOCK_UN)
- os.close(self._fd)
- self._fd = None
+ fcntl.lockf(self._file, fcntl.LOCK_UN,
+ self._length, self._start, os.SEEK_SET)
+ self._file.close()
+ self._file = None
def acquire_read(self, timeout=_default_timeout):
"""Acquires a recursive, shared lock for reading.
@@ -111,7 +194,9 @@ class Lock(object):
"""
if self._reads == 0 and self._writes == 0:
- self._lock(fcntl.LOCK_SH, timeout) # can raise LockError.
+ tty.debug('READ LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
+ .format(self))
+ self._lock(fcntl.LOCK_SH, timeout=timeout) # can raise LockError.
self._reads += 1
return True
else:
@@ -130,7 +215,10 @@ class Lock(object):
"""
if self._writes == 0:
- self._lock(fcntl.LOCK_EX, timeout) # can raise LockError.
+ tty.debug(
+ 'WRITE LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
+ .format(self))
+ self._lock(fcntl.LOCK_EX, timeout=timeout) # can raise LockError.
self._writes += 1
return True
else:
@@ -150,6 +238,8 @@ class Lock(object):
assert self._reads > 0
if self._reads == 1 and self._writes == 0:
+ tty.debug('READ LOCK: {0.path}[{0._start}:{0._length}] [Released]'
+ .format(self))
self._unlock() # can raise LockError.
self._reads -= 1
return True
@@ -170,6 +260,8 @@ class Lock(object):
assert self._writes > 0
if self._writes == 1 and self._reads == 0:
+ tty.debug('WRITE LOCK: {0.path}[{0._start}:{0._length}] [Released]'
+ .format(self))
self._unlock() # can raise LockError.
self._writes -= 1
return True
diff --git a/lib/spack/llnl/util/tty/log.py b/lib/spack/llnl/util/tty/log.py
index b67edcf9cc..3d4972b3ae 100644
--- a/lib/spack/llnl/util/tty/log.py
+++ b/lib/spack/llnl/util/tty/log.py
@@ -24,11 +24,11 @@
##############################################################################
"""Utility classes for logging the output of blocks of code.
"""
-import sys
+import multiprocessing
import os
import re
import select
-import inspect
+import sys
import llnl.util.tty as tty
import llnl.util.tty.color as color
@@ -100,25 +100,36 @@ class keyboard_input(object):
class log_output(object):
- """Redirects output and error of enclosed block to a file.
+ """Spawns a daemon that reads from a pipe and writes to a file
Usage:
- with log_output(open('logfile.txt', 'w')):
- # do things ... output will be logged.
+ # Spawns the daemon
+ with log_output('logfile.txt', 'w') as log_redirection:
+ # do things ... output is not redirected
+ with log_redirection:
+ # do things ... output will be logged
or:
- with log_output(open('logfile.txt', 'w'), echo=True):
- # do things ... output will be logged
- # and also printed to stdout.
-
- Closes the provided stream when done with the block.
- If echo is True, also prints the output to stdout.
+ with log_output('logfile.txt', echo=True) as log_redirection:
+ # do things ... output is not redirected
+ with log_redirection:
+ # do things ... output will be logged
+ # and also printed to stdout.
+
+ Opens a stream in 'w' mode at daemon spawning and closes it at
+ daemon joining. If echo is True, also prints the output to stdout.
"""
- def __init__(self, stream, echo=False, force_color=False, debug=False):
- self.stream = stream
-
- # various output options
+ def __init__(
+ self,
+ filename,
+ echo=False,
+ force_color=False,
+ debug=False,
+ input_stream=sys.stdin
+ ):
+ self.filename = filename
+ # Various output options
self.echo = echo
self.force_color = force_color
self.debug = debug
@@ -126,70 +137,92 @@ class log_output(object):
# Default is to try file-descriptor reassignment unless the system
# out/err streams do not have an associated file descriptor
self.directAssignment = False
+ self.read, self.write = os.pipe()
- def trace(self, frame, event, arg):
- """Jumps to __exit__ on the child process."""
- raise _SkipWithBlock()
+ # Needed to un-summon the daemon
+ self.parent_pipe, self.child_pipe = multiprocessing.Pipe()
+ # Input stream that controls verbosity interactively
+ self.input_stream = input_stream
def __enter__(self):
- """Redirect output from the with block to a file.
-
- This forks the with block as a separate process, with stdout
- and stderr redirected back to the parent via a pipe. If
- echo is set, also writes to standard out.
-
- """
- # remember these values for later.
- self._force_color = color._force_color
- self._debug = tty._debug
-
- read, write = os.pipe()
-
- self.pid = os.fork()
- if self.pid:
- # Parent: read from child, skip the with block.
- os.close(write)
-
- read_file = os.fdopen(read, 'r', 0)
- with self.stream as log_file:
- with keyboard_input(sys.stdin):
- while True:
- rlist, w, x = select.select(
- [read_file, sys.stdin], [], [])
- if not rlist:
- break
-
- # Allow user to toggle echo with 'v' key.
- # Currently ignores other chars.
- if sys.stdin in rlist:
- if sys.stdin.read(1) == 'v':
- self.echo = not self.echo
-
- # handle output from the with block process.
- if read_file in rlist:
- line = read_file.readline()
- if not line:
- break
-
- # Echo to stdout if requested.
- if self.echo:
- sys.stdout.write(line)
-
- # Stripped output to log file.
- log_file.write(_strip(line))
-
- read_file.flush()
- read_file.close()
-
- # Set a trace function to skip the with block.
- sys.settrace(lambda *args, **keys: None)
- frame = inspect.currentframe(1)
- frame.f_trace = self.trace
-
- else:
- # Child: redirect output, execute the with block.
- os.close(read)
-
+ # Sets a daemon that writes to file what it reads from a pipe
+ try:
+ fwd_input_stream = os.fdopen(
+ os.dup(self.input_stream.fileno())
+ )
+ self.p = multiprocessing.Process(
+ target=self._spawn_writing_daemon,
+ args=(self.read, fwd_input_stream),
+ name='logger_daemon'
+ )
+ self.p.daemon = True
+ self.p.start()
+ finally:
+ fwd_input_stream.close()
+ return log_output.OutputRedirection(self)
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.parent_pipe.send(True)
+ self.p.join(60.0) # 1 minute to join the child
+
+ def _spawn_writing_daemon(self, read, input_stream):
+ # Parent: read from child, skip the with block.
+ read_file = os.fdopen(read, 'r', 0)
+ with open(self.filename, 'w') as log_file:
+ with keyboard_input(input_stream):
+ while True:
+ # Without the last parameter (timeout) select will wait
+ # until at least one of the two streams are ready. This
+ # may cause the function to hang.
+ rlist, _, _ = select.select(
+ [read_file, input_stream], [], [], 0
+ )
+
+ # Allow user to toggle echo with 'v' key.
+ # Currently ignores other chars.
+ if input_stream in rlist:
+ if input_stream.read(1) == 'v':
+ self.echo = not self.echo
+
+ # Handle output from the with block process.
+ if read_file in rlist:
+ # If we arrive here it means that
+ # read_file was ready for reading : it
+ # should never happen that line is false-ish
+ line = read_file.readline()
+
+ # Echo to stdout if requested.
+ if self.echo:
+ sys.stdout.write(line)
+
+ # Stripped output to log file.
+ log_file.write(_strip(line))
+ log_file.flush()
+
+ if self.child_pipe.poll():
+ break
+
+ def __del__(self):
+ """Closes the pipes"""
+ os.close(self.write)
+ os.close(self.read)
+
+ class OutputRedirection(object):
+
+ def __init__(self, other):
+ self.__dict__.update(other.__dict__)
+
+ def __enter__(self):
+ """Redirect output from the with block to a file.
+
+ Hijacks stdout / stderr and writes to the pipe
+ connected to the logger daemon
+ """
+ # remember these values for later.
+ self._force_color = color._force_color
+ self._debug = tty._debug
+ # Redirect this output to a pipe
+ write = self.write
try:
# Save old stdout and stderr
self._stdout = os.dup(sys.stdout.fileno())
@@ -205,53 +238,26 @@ class log_output(object):
output_redirect = os.fdopen(write, 'w')
sys.stdout = output_redirect
sys.stderr = output_redirect
-
if self.force_color:
color._force_color = True
-
if self.debug:
tty._debug = True
- def __exit__(self, exc_type, exception, traceback):
- """Exits on child, handles skipping the with block on parent."""
- # Child should just exit here.
- if self.pid == 0:
+ def __exit__(self, exc_type, exception, traceback):
+ """Plugs back the original file descriptors
+ for stdout and stderr
+ """
# Flush the log to disk.
sys.stdout.flush()
sys.stderr.flush()
-
- if exception:
- # Restore stdout on the child if there's an exception,
- # and let it be raised normally.
- #
- # This assumes that even if the exception is caught,
- # the child will exit with a nonzero return code. If
- # it doesn't, the child process will continue running.
- #
- # TODO: think about how this works outside install.
- # TODO: ideally would propagate exception to parent...
- if self.directAssignment:
- sys.stdout = self._stdout
- sys.stderr = self._stderr
- else:
- os.dup2(self._stdout, sys.stdout.fileno())
- os.dup2(self._stderr, sys.stderr.fileno())
-
- return False
-
+ if self.directAssignment:
+ # We seem to need this only to pass test/install.py
+ sys.stdout = self._stdout
+ sys.stderr = self._stderr
else:
- # Die quietly if there was no exception.
- os._exit(0)
-
- else:
- # If the child exited badly, parent also should exit.
- pid, returncode = os.waitpid(self.pid, 0)
- if returncode != 0:
- os._exit(1)
-
- # restore output options.
- color._force_color = self._force_color
- tty._debug = self._debug
+ os.dup2(self._stdout, sys.stdout.fileno())
+ os.dup2(self._stderr, sys.stderr.fileno())
- # Suppresses exception if it's our own.
- return exc_type is _SkipWithBlock
+ # restore output options.
+ color._force_color = self._force_color
+ tty._debug = self._debug
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 3d508d0fde..0646f5cb32 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -30,7 +30,10 @@ import getpass
from llnl.util.filesystem import *
import llnl.util.tty as tty
-# This lives in $prefix/lib/spack/spack/__file__
+#-----------------------------------------------------------------------------
+# Variables describing how Spack is laid out in its prefix.
+#-----------------------------------------------------------------------------
+# This file lives in $prefix/lib/spack/spack/__file__
spack_root = ancestor(__file__, 4)
# The spack script itself
@@ -49,132 +52,100 @@ var_path = join_path(spack_root, "var", "spack")
stage_path = join_path(var_path, "stage")
repos_path = join_path(var_path, "repos")
share_path = join_path(spack_root, "share", "spack")
-cache_path = join_path(var_path, "cache")
+
+# Paths to built-in Spack repositories.
+packages_path = join_path(repos_path, "builtin")
+mock_packages_path = join_path(repos_path, "builtin.mock")
# User configuration location
user_config_path = os.path.expanduser('~/.spack')
-import spack.fetch_strategy
-fetch_cache = spack.fetch_strategy.FsCache(cache_path)
-
-from spack.file_cache import FileCache
-user_cache_path = join_path(user_config_path, 'cache')
-user_cache = FileCache(user_cache_path)
-
prefix = spack_root
opt_path = join_path(prefix, "opt")
-install_path = join_path(opt_path, "spack")
etc_path = join_path(prefix, "etc")
-#
-# Set up the default packages database.
-#
+
+#-----------------------------------------------------------------------------
+# Initial imports (only for use in this file -- see __all__ below.)
+#-----------------------------------------------------------------------------
+# These imports depend on the paths above, or on each other
+# Group them here so it's easy to understand the order.
+# TODO: refactor this stuff to be more init order agnostic.
import spack.repository
+import spack.error
+import spack.config
+import spack.fetch_strategy
+from spack.file_cache import FileCache
+from spack.preferred_packages import PreferredPackages
+from spack.abi import ABI
+from spack.concretize import DefaultConcretizer
+from spack.version import Version
+from spack.util.path import canonicalize_path
+
+
+#-----------------------------------------------------------------------------
+# Initialize various data structures & objects at the core of Spack.
+#-----------------------------------------------------------------------------
+# Version information
+spack_version = Version("0.9.1")
+
+
+# Set up the default packages database.
try:
repo = spack.repository.RepoPath()
sys.meta_path.append(repo)
except spack.error.SpackError, e:
tty.die('while initializing Spack RepoPath:', e.message)
-#
-# Set up the installed packages database
-#
-from spack.database import Database
-installed_db = Database(install_path)
-
-#
-# Paths to built-in Spack repositories.
-#
-packages_path = join_path(repos_path, "builtin")
-mock_packages_path = join_path(repos_path, "builtin.mock")
-
-#
-# This controls how spack lays out install prefixes and
-# stage directories.
-#
-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
+# PreferredPackages controls preference sort order during concretization.
+# More preferred packages are sorted first.
pkgsort = PreferredPackages()
-#
-# This tests ABI compatibility between packages
-#
-from spack.abi import ABI
+
+# Tests ABI compatibility between packages
abi = ABI()
-#
+
# This controls how things are concretized in spack.
# Replace it with a subclass if you want different
# policies.
-#
-from spack.concretize import DefaultConcretizer
concretizer = DefaultConcretizer()
-# Version information
-from spack.version import Version
-spack_version = Version("0.9.1")
+#-----------------------------------------------------------------------------
+# config.yaml options
+#-----------------------------------------------------------------------------
+_config = spack.config.get_config('config')
-#
-# Executables used by Spack
-#
-from spack.util.executable import Executable, which
-# User's editor from the environment
-editor = Executable(os.environ.get("EDITOR", "vi"))
+# Path where downloaded source code is cached
+cache_path = canonicalize_path(
+ _config.get('source_cache', join_path(var_path, "cache")))
+fetch_cache = spack.fetch_strategy.FsCache(cache_path)
-# Curl tool for fetching files.
-curl = which("curl", required=True)
-
-# Whether to build in tmp space or directly in the stage_path.
-# If this is true, then spack will make stage directories in
-# a tmp filesystem, and it will symlink them into stage_path.
-use_tmp_stage = True
-
-# Locations to use for staging and building, in order of preference
-# Use a %u to add a username to the stage paths here, in case this
-# is a shared filesystem. Spack will use the first of these paths
-# that it can create.
-tmp_dirs = []
-_default_tmp = tempfile.gettempdir()
-_tmp_user = getpass.getuser()
-
-_tmp_candidates = (_default_tmp, '/nfs/tmp2', '/tmp', '/var/tmp')
-for path in _tmp_candidates:
- # don't add a second username if it's already unique by user.
- if _tmp_user not in path:
- tmp_dirs.append(join_path(path, '%u', 'spack-stage'))
- else:
- tmp_dirs.append(join_path(path, 'spack-stage'))
-
-# Whether spack should allow installation of unsafe versions of
-# software. "Unsafe" versions are ones it doesn't have a checksum
-# for.
-do_checksum = True
-#
-# SYS_TYPE to use for the spack installation.
-# Value of this determines what platform spack thinks it is by
-# default. You can assign three types of values:
-# 1. None
-# Spack will try to determine the sys_type automatically.
-#
-# 2. A string
-# Spack will assume that the sys_type is hardcoded to the value.
-#
-# 3. A function that returns a string:
-# Spack will use this function to determine the sys_type.
-#
-sys_type = None
+# cache for miscellaneous stuff.
+misc_cache_path = canonicalize_path(
+ _config.get('misc_cache', join_path(user_config_path, 'cache')))
+misc_cache = FileCache(misc_cache_path)
-#
+# If this is enabled, tools that use SSL should not verify
+# certifiates. e.g., curl should use the -k option.
+insecure = not _config.get('verify_ssl', True)
+
+
+# Whether spack should allow installation of unsafe versions of software.
+# "Unsafe" versions are ones it doesn't have a checksum for.
+do_checksum = _config.get('checksum', True)
+
+
+# If this is True, spack will not clean the environment to remove
+# potentially harmful variables before builds.
+dirty = _config.get('dirty', False)
+
+
+#-----------------------------------------------------------------------------
# When packages call 'from spack import *', this extra stuff is brought in.
#
# Spack internal code should call 'import spack' and accesses other
@@ -185,10 +156,21 @@ sys_type = None
# packages should live. This file is overloaded for spack core vs.
# for packages.
#
-__all__ = ['Package', 'StagedPackage', 'CMakePackage',
- 'Version', 'when', 'ver', 'alldeps', 'nolink']
-from spack.package import Package, ExtensionConflictError
-from spack.package import StagedPackage, CMakePackage
+#-----------------------------------------------------------------------------
+__all__ = ['PackageBase',
+ 'Package',
+ 'CMakePackage',
+ 'AutotoolsPackage',
+ 'MakefilePackage',
+ 'Version',
+ 'when',
+ 'ver',
+ 'alldeps',
+ 'nolink']
+from spack.package import Package, PackageBase, ExtensionConflictError
+from spack.build_systems.makefile import MakefilePackage
+from spack.build_systems.autotools import AutotoolsPackage
+from spack.build_systems.cmake import CMakePackage
from spack.version import Version, ver
from spack.spec import DependencySpec, alldeps, nolink
from spack.multimethod import when
@@ -205,6 +187,9 @@ import spack.util.executable
from spack.util.executable import *
__all__ += spack.util.executable.__all__
+# User's editor from the environment
+editor = Executable(os.environ.get("EDITOR", "vi"))
+
from spack.package import \
install_dependency_symlinks, flatten_dependencies, \
DependencyConflictError, InstallError, ExternalPackageError
diff --git a/lib/spack/spack/abi.py b/lib/spack/spack/abi.py
index 064abb9782..b3b1dd6d27 100644
--- a/lib/spack/spack/abi.py
+++ b/lib/spack/spack/abi.py
@@ -26,6 +26,7 @@
import os
import spack
import spack.spec
+from spack.build_environment import dso_suffix
from spack.spec import CompilerSpec
from spack.util.executable import Executable, ProcessError
from llnl.util.lang import memoized
@@ -54,10 +55,10 @@ class ABI(object):
output = None
if compiler.cxx:
rungcc = Executable(compiler.cxx)
- libname = "libstdc++.so"
+ libname = "libstdc++." + dso_suffix
elif compiler.cc:
rungcc = Executable(compiler.cc)
- libname = "libgcc_s.so"
+ libname = "libgcc_s." + dso_suffix
else:
return None
try:
diff --git a/lib/spack/spack/architecture.py b/lib/spack/spack/architecture.py
index 0d210f9741..b14de35109 100644
--- a/lib/spack/spack/architecture.py
+++ b/lib/spack/spack/architecture.py
@@ -83,10 +83,10 @@ from llnl.util.filesystem import join_path
import llnl.util.tty as tty
import spack
-import spack.compilers
from spack.util.naming import mod_to_class
from spack.util.environment import get_path
from spack.util.multiproc import parmap
+from spack.util.spack_yaml import syaml_dict
import spack.error as serr
@@ -275,6 +275,8 @@ class OperatingSystem(object):
# Once the paths are cleaned up, do a search for each type of
# compiler. We can spawn a bunch of parallel searches to reduce
# the overhead of spelunking all these directories.
+ # NOTE: we import spack.compilers here to avoid init order cycles
+ import spack.compilers
types = spack.compilers.all_compiler_types()
compiler_lists = parmap(lambda cmp_cls:
self.find_compiler(cmp_cls, *filtered_path),
@@ -407,12 +409,13 @@ class Arch(object):
return (platform, platform_os, target)
def to_dict(self):
- d = {}
- d['platform'] = str(self.platform) if self.platform else None
- d['platform_os'] = str(self.platform_os) if self.platform_os else None
- d['target'] = str(self.target) if self.target else None
-
- return d
+ return syaml_dict((
+ ('platform',
+ str(self.platform) if self.platform else None),
+ ('platform_os',
+ str(self.platform_os) if self.platform_os else None),
+ ('target',
+ str(self.target) if self.target else None)))
def _target_from_dict(target_name, plat=None):
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index 5affd3c7c5..49df7a90b4 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -53,14 +53,15 @@ calls you can make from within the install() function.
"""
import os
import sys
-import shutil
import multiprocessing
-import platform
+import traceback
+import inspect
+import shutil
import llnl.util.tty as tty
-from llnl.util.filesystem import *
-
import spack
+import spack.store
+from llnl.util.filesystem import *
from spack.environment import EnvironmentModifications, validate
from spack.util.environment import *
from spack.util.executable import Executable, which
@@ -77,6 +78,8 @@ SPACK_NO_PARALLEL_MAKE = 'SPACK_NO_PARALLEL_MAKE'
#
SPACK_ENV_PATH = 'SPACK_ENV_PATH'
SPACK_DEPENDENCIES = 'SPACK_DEPENDENCIES'
+SPACK_RPATH_DEPS = 'SPACK_RPATH_DEPS'
+SPACK_LINK_DEPS = 'SPACK_LINK_DEPS'
SPACK_PREFIX = 'SPACK_PREFIX'
SPACK_INSTALL = 'SPACK_INSTALL'
SPACK_DEBUG = 'SPACK_DEBUG'
@@ -221,6 +224,8 @@ def set_compiler_environment_variables(pkg, env):
for mod in compiler.modules:
load_module(mod)
+ compiler.setup_custom_environment(env)
+
return env
@@ -249,14 +254,24 @@ def set_build_environment_variables(pkg, env, dirty=False):
if os.path.isdir(ci):
env_paths.append(ci)
+ env_paths = filter_system_paths(env_paths)
+
for item in reversed(env_paths):
env.prepend_path('PATH', item)
env.set_path(SPACK_ENV_PATH, env_paths)
# Prefixes of all of the package's dependencies go in SPACK_DEPENDENCIES
- dep_prefixes = [d.prefix
- for d in pkg.spec.traverse(root=False, deptype='build')]
+ dep_prefixes = [d.prefix for d in
+ pkg.spec.traverse(root=False, deptype=('build', 'link'))]
+ dep_prefixes = filter_system_paths(dep_prefixes)
env.set_path(SPACK_DEPENDENCIES, dep_prefixes)
+
+ # These variables control compiler wrapper behavior
+ env.set_path(SPACK_RPATH_DEPS, filter_system_paths([
+ d.prefix for d in get_rpath_deps(pkg)]))
+ env.set_path(SPACK_LINK_DEPS, filter_system_paths([
+ d.prefix for d in pkg.spec.traverse(root=False, deptype=('link'))]))
+
# Add dependencies to CMAKE_PREFIX_PATH
env.set_path('CMAKE_PREFIX_PATH', dep_prefixes)
@@ -264,7 +279,7 @@ def set_build_environment_variables(pkg, env, dirty=False):
env.set(SPACK_PREFIX, pkg.prefix)
# Install root prefix
- env.set(SPACK_INSTALL, spack.install_path)
+ env.set(SPACK_INSTALL, spack.store.root)
# Stuff in here sanitizes the build environemnt to eliminate
# anything the user has set that may interfere.
@@ -287,9 +302,27 @@ def set_build_environment_variables(pkg, env, dirty=False):
if '/macports/' in p:
env.remove_path('PATH', p)
+ # Set environment variables if specified for
+ # the given compiler
+ compiler = pkg.compiler
+ environment = compiler.environment
+ if 'set' in environment:
+ env_to_set = environment['set']
+ for key, value in env_to_set.iteritems():
+ env.set('SPACK_ENV_SET_%s' % key, value)
+ env.set('%s' % key, value)
+ # Let shell know which variables to set
+ env_variables = ":".join(env_to_set.keys())
+ env.set('SPACK_ENV_TO_SET', env_variables)
+
+ if compiler.extra_rpaths:
+ extra_rpaths = ':'.join(compiler.extra_rpaths)
+ env.set('SPACK_COMPILER_EXTRA_RPATHS', extra_rpaths)
+
# Add bin directories from dependencies to the PATH for the build.
- bin_dirs = reversed(
- filter(os.path.isdir, ['%s/bin' % prefix for prefix in dep_prefixes]))
+ bin_dirs = reversed(filter(os.path.isdir, [
+ '%s/bin' % d.prefix for d in pkg.spec.dependencies(deptype='build')]))
+ bin_dirs = filter_system_bin_paths(bin_dirs)
for item in bin_dirs:
env.prepend_path('PATH', item)
@@ -341,16 +374,8 @@ def set_module_variables_for_package(pkg, module):
m.cmake = Executable('cmake')
m.ctest = Executable('ctest')
- # standard CMake arguments
- m.std_cmake_args = ['-DCMAKE_INSTALL_PREFIX=%s' % pkg.prefix,
- '-DCMAKE_BUILD_TYPE=RelWithDebInfo']
- if platform.mac_ver()[0]:
- m.std_cmake_args.append('-DCMAKE_FIND_FRAMEWORK=LAST')
-
- # Set up CMake rpath
- m.std_cmake_args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE')
- m.std_cmake_args.append('-DCMAKE_INSTALL_RPATH=%s' %
- ":".join(get_rpaths(pkg)))
+ # Standard CMake arguments
+ m.std_cmake_args = spack.CMakePackage._std_args(pkg)
# Put spack compiler paths in module scope.
link_dir = spack.build_env_path
@@ -382,10 +407,18 @@ def set_module_variables_for_package(pkg, module):
m.dso_suffix = dso_suffix
+def get_rpath_deps(pkg):
+ """Return immediate or transitive RPATHs depending on the package."""
+ if pkg.transitive_rpaths:
+ return [d for d in pkg.spec.traverse(root=False, deptype=('link'))]
+ else:
+ return pkg.spec.dependencies(deptype='link')
+
+
def get_rpaths(pkg):
"""Get a list of all the rpaths for a package."""
rpaths = [pkg.prefix.lib, pkg.prefix.lib64]
- deps = pkg.spec.dependencies(deptype='link')
+ deps = get_rpath_deps(pkg)
rpaths.extend(d.prefix.lib for d in deps
if os.path.isdir(d.prefix.lib))
rpaths.extend(d.prefix.lib64 for d in deps
@@ -397,6 +430,21 @@ def get_rpaths(pkg):
return rpaths
+def get_std_cmake_args(cmake_pkg):
+ # standard CMake arguments
+ ret = ['-DCMAKE_INSTALL_PREFIX=%s' % cmake_pkg.prefix,
+ '-DCMAKE_BUILD_TYPE=RelWithDebInfo',
+ '-DCMAKE_VERBOSE_MAKEFILE=ON']
+ if platform.mac_ver()[0]:
+ ret.append('-DCMAKE_FIND_FRAMEWORK=LAST')
+
+ # Set up CMake rpath
+ ret.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE')
+ ret.append('-DCMAKE_INSTALL_RPATH=%s' % ":".join(get_rpaths(cmake_pkg)))
+
+ return ret
+
+
def parent_class_modules(cls):
"""
Get list of super class modules that are all descend from spack.Package
@@ -476,14 +524,12 @@ def setup_package(pkg, dirty=False):
def fork(pkg, function, dirty=False):
"""Fork a child process to do part of a spack build.
- Arguments:
+ :param pkg: pkg whose environemnt we should set up the forked process for.
+ :param function: arg-less function to run in the child process.
+ :param dirty: If True, do NOT clean the environment before building.
- pkg -- pkg whose environemnt we should set up the
- forked process for.
- function -- arg-less function to run in the child process.
- dirty -- If True, do NOT clean the environment before building.
+ Usage::
- Usage:
def child_fun():
# do stuff
build_env.fork(pkg, child_fun)
@@ -498,42 +544,170 @@ def fork(pkg, function, dirty=False):
well. If things go well, the child exits and the parent
carries on.
"""
- try:
- pid = os.fork()
- except OSError as e:
- raise InstallError("Unable to fork build process: %s" % e)
-
- if pid == 0:
- # Give the child process the package's build environment.
- setup_package(pkg, dirty=dirty)
+ def child_execution(child_connection, input_stream):
try:
- # call the forked function.
- function()
+ setup_package(pkg, dirty=dirty)
+ function(input_stream)
+ child_connection.send(None)
+ except:
+ # catch ANYTHING that goes wrong in the child process
+ exc_type, exc, tb = sys.exc_info()
- # Use os._exit here to avoid raising a SystemExit exception,
- # which interferes with unit tests.
- os._exit(0)
+ # Need to unwind the traceback in the child because traceback
+ # objects can't be sent to the parent.
+ tb_string = traceback.format_exc()
- except spack.error.SpackError as e:
- e.die()
+ # build up some context from the offending package so we can
+ # show that, too.
+ package_context = get_package_context(tb)
- except:
- # Child doesn't raise or return to main spack code.
- # Just runs default exception handler and exits.
- sys.excepthook(*sys.exc_info())
- os._exit(1)
+ build_log = None
+ if hasattr(pkg, 'log_path'):
+ build_log = pkg.log_path
- else:
- # Parent process just waits for the child to complete. If the
- # child exited badly, assume it already printed an appropriate
- # message. Just make the parent exit with an error code.
- pid, returncode = os.waitpid(pid, 0)
- if returncode != 0:
- message = "Installation process had nonzero exit code : {code}"
- strcode = str(returncode)
- raise InstallError(message.format(code=strcode))
+ # make a pickleable exception to send to parent.
+ msg = "%s: %s" % (str(exc_type.__name__), str(exc))
+
+ ce = ChildError(msg, tb_string, build_log, package_context)
+ child_connection.send(ce)
+
+ finally:
+ child_connection.close()
+
+ parent_connection, child_connection = multiprocessing.Pipe()
+ try:
+ # Forward sys.stdin to be able to activate / deactivate
+ # verbosity pressing a key at run-time
+ input_stream = os.fdopen(os.dup(sys.stdin.fileno()))
+ p = multiprocessing.Process(
+ target=child_execution,
+ args=(child_connection, input_stream)
+ )
+ p.start()
+ finally:
+ # Close the input stream in the parent process
+ input_stream.close()
+ child_exc = parent_connection.recv()
+ p.join()
+
+ if child_exc is not None:
+ raise child_exc
+
+
+def get_package_context(traceback):
+ """Return some context for an error message when the build fails.
+
+ Args:
+ traceback -- A traceback from some exception raised during install.
+
+ This function inspects the stack to find where we failed in the
+ package file, and it adds detailed context to the long_message
+ from there.
+
+ """
+ def make_stack(tb, stack=None):
+ """Tracebacks come out of the system in caller -> callee order. Return
+ an array in callee -> caller order so we can traverse it."""
+ if stack is None:
+ stack = []
+ if tb is not None:
+ make_stack(tb.tb_next, stack)
+ stack.append(tb)
+ return stack
+
+ stack = make_stack(traceback)
+
+ for tb in stack:
+ frame = tb.tb_frame
+ if 'self' in frame.f_locals:
+ # Find the first proper subclass of spack.PackageBase.
+ obj = frame.f_locals['self']
+ if isinstance(obj, spack.PackageBase):
+ break
+
+ # we found obj, the Package implementation we care about.
+ # point out the location in the install method where we failed.
+ lines = []
+ lines.append("%s:%d, in %s:" % (
+ inspect.getfile(frame.f_code), frame.f_lineno, frame.f_code.co_name
+ ))
+
+ # Build a message showing context in the install method.
+ sourcelines, start = inspect.getsourcelines(frame)
+ for i, line in enumerate(sourcelines):
+ mark = ">> " if start + i == frame.f_lineno else " "
+ lines.append(" %s%-5d%s" % (mark, start + i, line.rstrip()))
+
+ return lines
class InstallError(spack.error.SpackError):
- """Raised when a package fails to install"""
+ """Raised by packages when a package fails to install"""
+
+
+class ChildError(spack.error.SpackError):
+ """Special exception class for wrapping exceptions from child processes
+ in Spack's build environment.
+
+ The main features of a ChildError are:
+
+ 1. They're serializable, so when a child build fails, we can send one
+ of these to the parent and let the parent report what happened.
+
+ 2. They have a ``traceback`` field containing a traceback generated
+ on the child immediately after failure. Spack will print this on
+ failure in lieu of trying to run sys.excepthook on the parent
+ process, so users will see the correct stack trace from a child.
+
+ 3. They also contain package_context, which shows source code context
+ in the Package implementation where the error happened. To get
+ this, Spack searches the stack trace for the deepest frame where
+ ``self`` is in scope and is an instance of PackageBase. This will
+ generally find a useful spot in the ``package.py`` file.
+
+ The long_message of a ChildError displays all this stuff to the user,
+ and SpackError handles displaying the special traceback if we're in
+ debug mode with spack -d.
+
+ """
+ def __init__(self, msg, traceback_string, build_log, package_context):
+ super(ChildError, self).__init__(msg)
+ self.traceback = traceback_string
+ self.build_log = build_log
+ self.package_context = package_context
+
+ @property
+ def long_message(self):
+ msg = self._long_message if self._long_message else ''
+
+ if self.package_context:
+ if msg:
+ msg += "\n\n"
+ msg += '\n'.join(self.package_context)
+
+ if msg:
+ msg += "\n\n"
+
+ if self.build_log:
+ msg += "See build log for details:\n"
+ msg += " %s" % self.build_log
+
+ return msg
+
+ def __reduce__(self):
+ """__reduce__ is used to serialize (pickle) ChildErrors.
+
+ Return a function to reconstruct a ChildError, along with the
+ salient properties we'll need.
+ """
+ return _make_child_error, (
+ self.message,
+ self.traceback,
+ self.build_log,
+ self.package_context)
+
+
+def _make_child_error(msg, traceback, build_log, package_context):
+ """Used by __reduce__ in ChildError to reconstruct pickled errors."""
+ return ChildError(msg, traceback, build_log, package_context)
diff --git a/lib/spack/spack/build_systems/__init__.py b/lib/spack/spack/build_systems/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/spack/spack/build_systems/__init__.py
diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py
new file mode 100644
index 0000000000..8535c9d3e3
--- /dev/null
+++ b/lib/spack/spack/build_systems/autotools.py
@@ -0,0 +1,183 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+import os
+import os.path
+import shutil
+from subprocess import PIPE
+from subprocess import check_call
+
+import llnl.util.tty as tty
+from spack.package import PackageBase
+
+
+class AutotoolsPackage(PackageBase):
+ """Specialized class for packages that are built using GNU Autotools
+
+ This class provides four phases that can be overridden:
+ - autoreconf
+ - configure
+ - build
+ - install
+
+ They all have sensible defaults and for many packages the only thing
+ necessary will be to override `configure_args`
+ """
+ phases = ['autoreconf', 'configure', 'build', 'install']
+ # To be used in UI queries that require to know which
+ # build-system class we are using
+ build_system_class = 'AutotoolsPackage'
+ patch_config_guess = True
+
+ def do_patch_config_guess(self):
+ """Some packages ship with an older config.guess and need to have
+ this updated when installed on a newer architecture."""
+
+ my_config_guess = None
+ config_guess = None
+ if os.path.exists('config.guess'):
+ # First search the top-level source directory
+ my_config_guess = 'config.guess'
+ else:
+ # Then search in all sub directories.
+ # We would like to use AC_CONFIG_AUX_DIR, but not all packages
+ # ship with their configure.in or configure.ac.
+ d = '.'
+ dirs = [os.path.join(d, o) for o in os.listdir(d)
+ if os.path.isdir(os.path.join(d, o))]
+ for dirname in dirs:
+ path = os.path.join(dirname, 'config.guess')
+ if os.path.exists(path):
+ my_config_guess = path
+
+ if my_config_guess is not None:
+ try:
+ check_call([my_config_guess], stdout=PIPE, stderr=PIPE)
+ # The package's config.guess already runs OK, so just use it
+ return True
+ except:
+ pass
+
+ # Look for a spack-installed automake package
+ if 'automake' in self.spec:
+ automake_path = os.path.join(self.spec['automake'].prefix, 'share',
+ 'automake-' +
+ str(self.spec['automake'].version))
+ path = os.path.join(automake_path, 'config.guess')
+ if os.path.exists(path):
+ config_guess = path
+ if config_guess is not None:
+ try:
+ check_call([config_guess], stdout=PIPE, stderr=PIPE)
+ shutil.copyfile(config_guess, my_config_guess)
+ return True
+ except:
+ pass
+
+ # Look for the system's config.guess
+ if os.path.exists('/usr/share'):
+ automake_dir = [s for s in os.listdir('/usr/share') if
+ "automake" in s]
+ if automake_dir:
+ automake_path = os.path.join('/usr/share', automake_dir[0])
+ path = os.path.join(automake_path, 'config.guess')
+ if os.path.exists(path):
+ config_guess = path
+ if config_guess is not None:
+ try:
+ check_call([config_guess], stdout=PIPE, stderr=PIPE)
+ shutil.copyfile(config_guess, my_config_guess)
+ return True
+ except:
+ pass
+
+ return False
+
+ def patch(self):
+ """Perform any required patches."""
+
+ if self.patch_config_guess and self.spec.satisfies(
+ 'arch=linux-redhat7-ppc64le'):
+ if not self.do_patch_config_guess():
+ raise RuntimeError('Failed to find suitable config.guess')
+
+ def autoreconf(self, spec, prefix):
+ """Not needed usually, configure should be already there"""
+ pass
+
+ @PackageBase.sanity_check('autoreconf')
+ def is_configure_or_die(self):
+ """Checks the presence of a `configure` file after the
+ autoreconf phase"""
+ if not os.path.exists('configure'):
+ raise RuntimeError(
+ 'configure script not found in {0}'.format(os.getcwd()))
+
+ def configure_args(self):
+ """Method to be overridden. Should return an iterable containing
+ all the arguments that must be passed to configure, except --prefix
+ """
+ return []
+
+ def configure(self, spec, prefix):
+ """Runs configure with the arguments specified in `configure_args`
+ and an appropriately set prefix
+ """
+ options = ['--prefix={0}'.format(prefix)] + self.configure_args()
+ inspect.getmodule(self).configure(*options)
+
+ def build(self, spec, prefix):
+ """The usual `make` after configure"""
+ inspect.getmodule(self).make()
+
+ def install(self, spec, prefix):
+ """...and the final `make install` after configure"""
+ inspect.getmodule(self).make('install')
+
+ @PackageBase.sanity_check('build')
+ @PackageBase.on_package_attributes(run_tests=True)
+ def _run_default_function(self):
+ """This function is run after build if self.run_tests == True
+
+ It will search for a method named `check` and run it. A sensible
+ default is provided in the base class.
+ """
+ try:
+ fn = getattr(self, 'check')
+ tty.msg('Trying default sanity checks [check]')
+ fn()
+ except AttributeError:
+ tty.msg('Skipping default sanity checks [method `check` not implemented]') # NOQA: ignore=E501
+
+ def check(self):
+ """Default test : search the Makefile for targets `test` and `check`
+ and run them if found.
+ """
+ self._if_make_target_execute('test')
+ self._if_make_target_execute('check')
+
+ # Check that self.prefix is there after installation
+ PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/cmake.py b/lib/spack/spack/build_systems/cmake.py
new file mode 100644
index 0000000000..cb1076d7b7
--- /dev/null
+++ b/lib/spack/spack/build_systems/cmake.py
@@ -0,0 +1,143 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+import os
+import platform
+
+import llnl.util.tty as tty
+import spack.build_environment
+from llnl.util.filesystem import working_dir, join_path
+from spack.package import PackageBase
+
+
+class CMakePackage(PackageBase):
+ """Specialized class for packages that are built using cmake
+
+ This class provides three phases that can be overridden:
+ - cmake
+ - build
+ - install
+
+ They all have sensible defaults and for many packages the only thing
+ necessary will be to override `cmake_args`
+ """
+ phases = ['cmake', 'build', 'install']
+ # To be used in UI queries that require to know which
+ # build-system class we are using
+ build_system_class = 'CMakePackage'
+
+ def build_type(self):
+ """Override to provide the correct build_type in case a complex
+ logic is needed
+ """
+ return 'RelWithDebInfo'
+
+ def root_cmakelists_dir(self):
+ """Directory where to find the root CMakeLists.txt"""
+ return self.stage.source_path
+
+ @property
+ def std_cmake_args(self):
+ """Standard cmake arguments provided as a property for
+ convenience of package writers
+ """
+ # standard CMake arguments
+ return CMakePackage._std_args(self)
+
+ @staticmethod
+ def _std_args(pkg):
+ """Computes the standard cmake arguments for a generic package"""
+ try:
+ build_type = pkg.build_type()
+ except AttributeError:
+ build_type = 'RelWithDebInfo'
+
+ args = ['-DCMAKE_INSTALL_PREFIX:PATH={0}'.format(pkg.prefix),
+ '-DCMAKE_BUILD_TYPE:STRING={0}'.format(build_type),
+ '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON']
+ if platform.mac_ver()[0]:
+ args.append('-DCMAKE_FIND_FRAMEWORK:STRING=LAST')
+
+ # Set up CMake rpath
+ args.append('-DCMAKE_INSTALL_RPATH_USE_LINK_PATH:BOOL=FALSE')
+ rpaths = ':'.join(spack.build_environment.get_rpaths(pkg))
+ args.append('-DCMAKE_INSTALL_RPATH:STRING={0}'.format(rpaths))
+ return args
+
+ def build_directory(self):
+ """Override to provide another place to build the package"""
+ return join_path(self.stage.source_path, 'spack-build')
+
+ def cmake_args(self):
+ """Method to be overridden. Should return an iterable containing
+ all the arguments that must be passed to configure, except:
+ - CMAKE_INSTALL_PREFIX
+ - CMAKE_BUILD_TYPE
+ """
+ return []
+
+ def cmake(self, spec, prefix):
+ """Run cmake in the build directory"""
+ options = [self.root_cmakelists_dir()] + self.std_cmake_args + \
+ self.cmake_args()
+ create = not os.path.exists(self.build_directory())
+ with working_dir(self.build_directory(), create=create):
+ inspect.getmodule(self).cmake(*options)
+
+ def build(self, spec, prefix):
+ """The usual `make` after cmake"""
+ with working_dir(self.build_directory()):
+ inspect.getmodule(self).make()
+
+ def install(self, spec, prefix):
+ """...and the final `make install` after cmake"""
+ with working_dir(self.build_directory()):
+ inspect.getmodule(self).make('install')
+
+ @PackageBase.sanity_check('build')
+ @PackageBase.on_package_attributes(run_tests=True)
+ def _run_default_function(self):
+ """This function is run after build if self.run_tests == True
+
+ It will search for a method named `check` and run it. A sensible
+ default is provided in the base class.
+ """
+ try:
+ fn = getattr(self, 'check')
+ tty.msg('Trying default build sanity checks [check]')
+ fn()
+ except AttributeError:
+ tty.msg('Skipping default build sanity checks [method `check` not implemented]') # NOQA: ignore=E501
+
+ def check(self):
+ """Default test : search the Makefile for the target `test`
+ and run them if found.
+ """
+ with working_dir(self.build_directory()):
+ self._if_make_target_execute('test')
+
+ # Check that self.prefix is there after installation
+ PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/build_systems/makefile.py b/lib/spack/spack/build_systems/makefile.py
new file mode 100644
index 0000000000..dcddadeedc
--- /dev/null
+++ b/lib/spack/spack/build_systems/makefile.py
@@ -0,0 +1,77 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 inspect
+
+from llnl.util.filesystem import working_dir
+from spack.package import PackageBase
+
+
+class MakefilePackage(PackageBase):
+ """Specialized class for packages that are built using editable Makefiles
+
+ This class provides three phases that can be overridden:
+ - edit
+ - build
+ - install
+
+ It is necessary to override the 'edit' phase, while 'build' and 'install'
+ have sensible defaults.
+ """
+ phases = ['edit', 'build', 'install']
+ # To be used in UI queries that require to know which
+ # build-system class we are using
+ build_system_class = 'MakefilePackage'
+
+ def build_directory(self):
+ """Directory where the main Makefile is located"""
+ return self.stage.source_path
+
+ def build_args(self):
+ """List of arguments that should be passed to make at build time"""
+ return []
+
+ def install_args(self):
+ """List of arguments that should be passed to make at install time"""
+ return []
+
+ def edit(self, spec, prefix):
+ """This phase cannot be defaulted for obvious reasons..."""
+ raise NotImplementedError('\'edit\' function not implemented')
+
+ def build(self, spec, prefix):
+ """Default build phase : call make passing build_args"""
+ args = self.build_args()
+ with working_dir(self.build_directory()):
+ inspect.getmodule(self).make(*args)
+
+ def install(self, spec, prefix):
+ """Default install phase : call make passing install_args"""
+ args = self.install_args() + ['install']
+ with working_dir(self.build_directory()):
+ inspect.getmodule(self).make(*args)
+
+ # Check that self.prefix is there after installation
+ PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index f69f434afd..8000c9f1f2 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -27,13 +27,15 @@ import re
import sys
import llnl.util.tty as tty
-import spack
-import spack.config
-import spack.spec
from llnl.util.lang import *
from llnl.util.tty.colify import *
from llnl.util.tty.color import *
+import spack
+import spack.config
+import spack.spec
+import spack.store
+
#
# Settings for commands that modify configuration
#
@@ -69,17 +71,17 @@ def get_cmd_function_name(name):
def get_module(name):
"""Imports the module for a particular command name and returns it."""
module_name = "%s.%s" % (__name__, name)
- module = __import__(
- module_name, fromlist=[name, SETUP_PARSER, DESCRIPTION],
- level=0)
+ module = __import__(module_name,
+ fromlist=[name, SETUP_PARSER, DESCRIPTION],
+ level=0)
attr_setdefault(module, SETUP_PARSER, lambda *args: None) # null-op
attr_setdefault(module, DESCRIPTION, "")
fn_name = get_cmd_function_name(name)
if not hasattr(module, fn_name):
- tty.die("Command module %s (%s) must define function '%s'."
- % (module.__name__, module.__file__, fn_name))
+ tty.die("Command module %s (%s) must define function '%s'." %
+ (module.__name__, module.__file__, fn_name))
return module
@@ -135,7 +137,7 @@ def elide_list(line_list, max_num=10):
def disambiguate_spec(spec):
- matching_specs = spack.installed_db.query(spec)
+ matching_specs = spack.store.db.query(spec)
if not matching_specs:
tty.die("Spec '%s' matches no installed packages." % spec)
diff --git a/lib/spack/spack/cmd/arch.py b/lib/spack/spack/cmd/arch.py
index 1badd40f7f..4e29230c28 100644
--- a/lib/spack/spack/cmd/arch.py
+++ b/lib/spack/spack/cmd/arch.py
@@ -24,8 +24,18 @@
##############################################################################
import spack.architecture as architecture
-description = "Print the architecture for this machine"
+description = "Print architecture information about this machine."
+
+
+def setup_parser(subparser):
+ parts = subparser.add_mutually_exclusive_group()
+ parts.add_argument(
+ '-p', '--platform', action='store_true', default=False,
+ help="Print only the platform.")
def arch(parser, args):
- print architecture.sys_type()
+ if args.platform:
+ print architecture.platform()
+ else:
+ print architecture.sys_type()
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index 60e2bd3a11..a79ef4aa68 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -28,7 +28,7 @@ import llnl.util.tty as tty
from llnl.util.filesystem import join_path, mkdirp
import spack
-from spack.util.executable import which
+from spack.util.executable import ProcessError, which
_SPACK_UPSTREAM = 'https://github.com/llnl/spack'
diff --git a/lib/spack/spack/hooks/tclmodule.py b/lib/spack/spack/cmd/build.py
index 2c88810c97..703705a32c 100644
--- a/lib/spack/spack/hooks/tclmodule.py
+++ b/lib/spack/spack/cmd/build.py
@@ -22,14 +22,21 @@
# 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.modules
+import spack.cmd.configure as cfg
+from spack import *
-def post_install(pkg):
- dk = spack.modules.TclModule(pkg.spec)
- dk.write()
+description = 'Stops at build stage when installing a package, if possible'
+build_system_to_phase = {
+ CMakePackage: 'build',
+ AutotoolsPackage: 'build'
+}
-def post_uninstall(pkg):
- dk = spack.modules.TclModule(pkg.spec)
- dk.remove()
+
+def setup_parser(subparser):
+ cfg.setup_parser(subparser)
+
+
+def build(parser, args):
+ cfg._stop_at_phase_during_install(args, build, build_system_to_phase)
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index aedb0fd99c..2e24d0527e 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -113,6 +113,6 @@ def checksum(parser, args):
tty.die("Could not fetch any versions for %s" % pkg.name)
version_lines = [
- " version('%s', '%s')" % (v, h) for v, h in version_hashes
+ " version('%s', '%s')" % (v, h) for v, h in version_hashes
]
tty.msg("Checksummed new versions of %s:" % pkg.name, *version_lines)
diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
index afcba33714..1470ea035d 100644
--- a/lib/spack/spack/cmd/common/arguments.py
+++ b/lib/spack/spack/cmd/common/arguments.py
@@ -25,8 +25,9 @@
import argparse
+import spack.store
import spack.modules
-from spack.util.pattern import Bunch
+from spack.util.pattern import Args
__all__ = ['add_common_arguments']
_arguments = {}
@@ -45,52 +46,54 @@ class ConstraintAction(argparse.Action):
"""Constructs a list of specs based on a constraint given on the command line
An instance of this class is supposed to be used as an argument action
- in a parser. It will read a constraint and will attach a list of matching
- specs to the namespace
+ in a parser. It will read a constraint and will attach a function to the
+ arguments that accepts optional keyword arguments.
+
+ To obtain the specs from a command the function must be called.
"""
- qualifiers = {}
def __call__(self, parser, namespace, values, option_string=None):
# Query specs from command line
- d = self.qualifiers.get(namespace.subparser_name, {})
- specs = [s for s in spack.installed_db.query(**d)]
- values = ' '.join(values)
+ self.values = values
+ namespace.contraint = values
+ namespace.specs = self._specs
+
+ def _specs(self, **kwargs):
+ specs = [s for s in spack.store.db.query(**kwargs)]
+ values = ' '.join(self.values)
if values:
specs = [x for x in specs if x.satisfies(values, strict=True)]
- namespace.specs = specs
-
-parms = Bunch(
- flags=('constraint',),
- kwargs={
- 'nargs': '*',
- 'help': 'Constraint to select a subset of installed packages',
- 'action': ConstraintAction
- })
-_arguments['constraint'] = parms
-
-parms = Bunch(
- flags=('-m', '--module-type'),
- kwargs={
- 'help': 'Type of module files',
- 'default': 'tcl',
- 'choices': spack.modules.module_types
- })
-_arguments['module_type'] = parms
-
-parms = Bunch(
- flags=('-y', '--yes-to-all'),
- kwargs={
- 'action': 'store_true',
- 'dest': 'yes_to_all',
- 'help': 'Assume "yes" is the answer to every confirmation request.'
- })
-_arguments['yes_to_all'] = parms
-
-parms = Bunch(
- flags=('-r', '--dependencies'),
- kwargs={
- 'action': 'store_true',
- 'dest': 'recurse_dependencies',
- 'help': 'Recursively traverse spec dependencies'
- })
-_arguments['recurse_dependencies'] = parms
+ return specs
+
+
+_arguments['constraint'] = Args(
+ 'constraint', nargs='*', action=ConstraintAction,
+ help='Constraint to select a subset of installed packages')
+
+_arguments['module_type'] = Args(
+ '-m', '--module-type', help='Type of module files',
+ default='tcl', choices=spack.modules.module_types)
+
+_arguments['yes_to_all'] = Args(
+ '-y', '--yes-to-all', action='store_true', dest='yes_to_all',
+ help='Assume "yes" is the answer to every confirmation request.')
+
+_arguments['recurse_dependencies'] = Args(
+ '-r', '--dependencies', action='store_true', dest='recurse_dependencies',
+ help='Recursively traverse spec dependencies')
+
+_arguments['clean'] = Args(
+ '--clean', action='store_false', dest='dirty',
+ help='Clean environment before installing package.')
+
+_arguments['dirty'] = Args(
+ '--dirty', action='store_true', dest='dirty',
+ help='Do NOT clean environment before installing.')
+
+_arguments['long'] = Args(
+ '-l', '--long', action='store_true',
+ help='Show dependency hashes as well as versions.')
+
+_arguments['very_long'] = Args(
+ '-L', '--very-long', action='store_true',
+ help='Show full dependency hashes as well as versions.')
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
index ea91c71479..80a3c71d61 100644
--- a/lib/spack/spack/cmd/compiler.py
+++ b/lib/spack/spack/cmd/compiler.py
@@ -133,10 +133,23 @@ def compiler_info(args):
else:
for c in compilers:
print str(c.spec) + ":"
- print "\tcc = %s" % c.cc
- print "\tcxx = %s" % c.cxx
- print "\tf77 = %s" % c.f77
- print "\tfc = %s" % c.fc
+ print "\tpaths:"
+ for cpath in ['cc', 'cxx', 'f77', 'fc']:
+ print "\t\t%s = %s" % (cpath, getattr(c, cpath, None))
+ if c.flags:
+ print "\tflags:"
+ for flag, flag_value in c.flags.iteritems():
+ print "\t\t%s = %s" % (flag, flag_value)
+ if len(c.environment) != 0:
+ if len(c.environment['set']) != 0:
+ print "\tenvironment:"
+ print "\t set:"
+ for key, value in c.environment['set'].iteritems():
+ print "\t %s = %s" % (key, value)
+ if c.extra_rpaths:
+ print "\tExtra rpaths:"
+ for extra_rpath in c.extra_rpaths:
+ print "\t\t%s" % extra_rpath
print "\tmodules = %s" % c.modules
print "\toperating system = %s" % c.operating_system
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
index c189e37036..5e041192e2 100644
--- a/lib/spack/spack/cmd/config.py
+++ b/lib/spack/spack/cmd/config.py
@@ -29,13 +29,8 @@ description = "Get and set configuration options."
def setup_parser(subparser):
# User can only choose one
- scope_group = subparser.add_mutually_exclusive_group()
- scope_group.add_argument(
- '--user', action='store_const', const='user', dest='scope',
- help="Use config file in user home directory (default).")
- scope_group.add_argument(
- '--site', action='store_const', const='site', dest='scope',
- help="Use config file in spack prefix.")
+ subparser.add_argument('--scope', choices=spack.config.config_scopes,
+ help="Configuration scope to read/modify.")
sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='config_command')
diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py
new file mode 100644
index 0000000000..3eebe2584b
--- /dev/null
+++ b/lib/spack/spack/cmd/configure.py
@@ -0,0 +1,90 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 argparse
+
+import llnl.util.tty as tty
+import spack.cmd
+import spack.cmd.install as inst
+
+from spack import *
+
+description = 'Stops at configuration stage when installing a package, if possible' # NOQA: ignore=E501
+
+
+build_system_to_phase = {
+ CMakePackage: 'cmake',
+ AutotoolsPackage: 'configure'
+}
+
+
+def setup_parser(subparser):
+ subparser.add_argument(
+ 'package',
+ nargs=argparse.REMAINDER,
+ help="spec of the package to install"
+ )
+ subparser.add_argument(
+ '-v', '--verbose',
+ action='store_true',
+ help="Print additional output during builds"
+ )
+
+
+def _stop_at_phase_during_install(args, calling_fn, phase_mapping):
+ if not args.package:
+ tty.die("configure requires at least one package argument")
+
+ # TODO: to be refactored with code in install
+ specs = spack.cmd.parse_specs(args.package, concretize=True)
+ if len(specs) != 1:
+ tty.error('only one spec can be installed at a time.')
+ spec = specs.pop()
+ pkg = spec.package
+ try:
+ key = [cls for cls in phase_mapping if isinstance(pkg, cls)].pop()
+ phase = phase_mapping[key]
+ # Install package dependencies if needed
+ parser = argparse.ArgumentParser()
+ inst.setup_parser(parser)
+ tty.msg('Checking dependencies for {0}'.format(args.package))
+ cli_args = ['-v'] if args.verbose else []
+ install_args = parser.parse_args(cli_args + ['--only=dependencies'])
+ install_args.package = args.package
+ inst.install(parser, install_args)
+ # Install package and stop at the given phase
+ cli_args = ['-v'] if args.verbose else []
+ install_args = parser.parse_args(cli_args + ['--only=package'])
+ install_args.package = args.package
+ inst.install(parser, install_args, stop_at=phase)
+ except IndexError:
+ tty.error(
+ 'Package {0} has no {1} phase, or its {1} phase is not separated from install'.format( # NOQA: ignore=E501
+ spec.name, calling_fn.__name__)
+ )
+
+
+def configure(parser, args):
+ _stop_at_phase_during_install(args, configure, build_system_to_phase)
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index 52a82eb38f..feed3b8772 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -22,25 +22,24 @@
# 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 string
+from __future__ import print_function
+
import os
import re
+import string
-from ordereddict_backport import OrderedDict
import llnl.util.tty as tty
-from llnl.util.filesystem import mkdirp
-
import spack
import spack.cmd
import spack.cmd.checksum
import spack.url
import spack.util.web
-from spack.spec import Spec
-from spack.util.naming import *
+from llnl.util.filesystem import mkdirp
+from ordereddict_backport import OrderedDict
from spack.repository import Repo, RepoError
-
+from spack.spec import Spec
from spack.util.executable import which
-
+from spack.util.naming import *
description = "Create a new package file from an archive URL"
@@ -87,7 +86,7 @@ package_template = string.Template("""\
from spack import *
-class ${class_name}(Package):
+class ${class_name}(${base_class_name}):
""\"FIXME: Put a proper description of your package here.""\"
# FIXME: Add a proper url for your package's homepage here.
@@ -98,100 +97,160 @@ ${versions}
${dependencies}
- def install(self, spec, prefix):
-${install}
+${body}
""")
-# Build dependencies and extensions
-dependencies_dict = {
- 'autotools': """\
+
+class DefaultGuess(object):
+ """Provides the default values to be used for the package file template"""
+ base_class_name = 'Package'
+
+ dependencies = """\
# FIXME: Add dependencies if required.
- # depends_on('foo')""",
+ # depends_on('foo')"""
- 'cmake': """\
- # FIXME: Add additional dependencies if required.
- depends_on('cmake', type='build')""",
+ body = """\
+ def install(self, spec, prefix):
+ # FIXME: Unknown build system
+ make()
+ make('install')"""
- 'scons': """\
- # FIXME: Add additional dependencies if required.
- depends_on('scons', type='build')""",
+ def __init__(self, name, url, version_hash_tuples):
+ self.name = name
+ self.class_name = mod_to_class(name)
+ self.url = url
+ self.version_hash_tuples = version_hash_tuples
- 'python': """\
- extends('python')
+ @property
+ def versions(self):
+ """Adds a version() call to the package for each version found."""
+ max_len = max(len(str(v)) for v, h in self.version_hash_tuples)
+ format = " version(%%-%ds, '%%s')" % (max_len + 2)
+ return '\n'.join(
+ format % ("'%s'" % v, h) for v, h in self.version_hash_tuples
+ )
- # FIXME: Add additional dependencies if required.
- # depends_on('py-foo', type=nolink)""",
- 'R': """\
- extends('R')
+class AutotoolsGuess(DefaultGuess):
+ """Provides appropriate overrides for autotools-based packages"""
+ base_class_name = 'AutotoolsPackage'
- # FIXME: Add additional dependencies if required.
- # depends_on('r-foo', type=nolink)""",
+ dependencies = """\
+ # FIXME: Add dependencies if required.
+ # depends_on('m4', type='build')
+ # depends_on('autoconf', type='build')
+ # depends_on('automake', type='build')
+ # depends_on('libtool', type='build')
+ # depends_on('foo')"""
+
+ body = """\
+ def configure_args(self):
+ # FIXME: Add arguments other than --prefix
+ # FIXME: If not needed delete the function
+ args = []
+ return args"""
- 'octave': """\
- extends('octave')
+class CMakeGuess(DefaultGuess):
+ """Provides appropriate overrides for cmake-based packages"""
+ base_class_name = 'CMakePackage'
+
+ dependencies = """\
# FIXME: Add additional dependencies if required.
- # depends_on('octave-foo', type=nolink)""",
+ depends_on('cmake', type='build')"""
+
+ body = """\
+ def cmake_args(self):
+ # FIXME: Add arguments other than
+ # FIXME: CMAKE_INSTALL_PREFIX and CMAKE_BUILD_TYPE
+ # FIXME: If not needed delete the function
+ args = []
+ return args"""
- 'unknown': """\
- # FIXME: Add dependencies if required.
- # depends_on('foo')"""
-}
-# Default installation instructions
-install_dict = {
- 'autotools': """\
- # FIXME: Modify the configure line to suit your build system here.
- configure('--prefix={0}'.format(prefix))
+class SconsGuess(DefaultGuess):
+ """Provides appropriate overrides for scons-based packages"""
+ dependencies = """\
+ # FIXME: Add additional dependencies if required.
+ depends_on('scons', type='build')"""
+ body = """\
+ def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
- make()
- make('install')""",
+ scons('prefix={0}'.format(prefix))
+ scons('install')"""
- 'cmake': """\
- with working_dir('spack-build', create=True):
- # FIXME: Modify the cmake line to suit your build system here.
- cmake('..', *std_cmake_args)
- # FIXME: Add logic to build and install here.
- make()
- make('install')""",
+class BazelGuess(DefaultGuess):
+ """Provides appropriate overrides for bazel-based packages"""
+ dependencies = """\
+ # FIXME: Add additional dependencies if required.
+ depends_on('bazel', type='build')"""
- 'scons': """\
+ body = """\
+ def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
- scons('prefix={0}'.format(prefix))
- scons('install')""",
+ bazel()"""
+
+
+class PythonGuess(DefaultGuess):
+ """Provides appropriate overrides for python extensions"""
+ dependencies = """\
+ extends('python')
+
+ # FIXME: Add additional dependencies if required.
+ # depends_on('py-setuptools', type='build')
+ # depends_on('py-foo', type=nolink)"""
- 'python': """\
+ body = """\
+ def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
- setup_py('install', '--prefix={0}'.format(prefix))""",
+ setup_py('install', '--prefix={0}'.format(prefix))"""
+
+ def __init__(self, name, *args):
+ name = 'py-{0}'.format(name)
+ super(PythonGuess, self).__init__(name, *args)
- 'R': """\
+
+class RGuess(DefaultGuess):
+ """Provides appropriate overrides for R extensions"""
+ dependencies = """\
+ extends('R')
+
+ # FIXME: Add additional dependencies if required.
+ # depends_on('r-foo', type=nolink)"""
+
+ body = """\
+ def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
R('CMD', 'INSTALL', '--library={0}'.format(self.module.r_lib_dir),
- self.stage.source_path)""",
+ self.stage.source_path)"""
- 'octave': """\
+ def __init__(self, name, *args):
+ name = 'r-{0}'.format(name)
+ super(RGuess, self).__init__(name, *args)
+
+
+class OctaveGuess(DefaultGuess):
+ """Provides appropriate overrides for octave packages"""
+ dependencies = """\
+ extends('octave')
+
+ # FIXME: Add additional dependencies if required.
+ # depends_on('octave-foo', type=nolink)"""
+
+ body = """\
+ def install(self, spec, prefix):
# FIXME: Add logic to build and install here.
octave('--quiet', '--norc',
'--built-in-docstrings-file=/dev/null',
'--texi-macros-file=/dev/null',
'--eval', 'pkg prefix {0}; pkg install {1}'.format(
- prefix, self.stage.archive_file))""",
+ prefix, self.stage.archive_file))"""
- 'unknown': """\
- # FIXME: Unknown build system
- make()
- make('install')"""
-}
-
-
-def make_version_calls(ver_hash_tuples):
- """Adds a version() call to the package for each version found."""
- max_len = max(len(str(v)) for v, h in ver_hash_tuples)
- format = " version(%%-%ds, '%%s')" % (max_len + 2)
- return '\n'.join(format % ("'%s'" % v, h) for v, h in ver_hash_tuples)
+ def __init__(self, name, *args):
+ name = 'octave-{0}'.format(name)
+ super(OctaveGuess, self).__init__(name, *args)
def setup_parser(subparser):
@@ -218,6 +277,16 @@ def setup_parser(subparser):
class BuildSystemGuesser(object):
+ _choices = {
+ 'autotools': AutotoolsGuess,
+ 'cmake': CMakeGuess,
+ 'scons': SconsGuess,
+ 'bazel': BazelGuess,
+ 'python': PythonGuess,
+ 'R': RGuess,
+ 'octave': OctaveGuess
+ }
+
def __call__(self, stage, url):
"""Try to guess the type of build system used by a project based on
the contents of its archive or the URL it was downloaded from."""
@@ -237,7 +306,8 @@ class BuildSystemGuesser(object):
(r'/CMakeLists.txt$', 'cmake'),
(r'/SConstruct$', 'scons'),
(r'/setup.py$', 'python'),
- (r'/NAMESPACE$', 'R')
+ (r'/NAMESPACE$', 'R'),
+ (r'/WORKSPACE$', 'bazel')
]
# Peek inside the compressed file.
@@ -265,6 +335,10 @@ class BuildSystemGuesser(object):
self.build_system = build_system
+ def make_guess(self, name, url, ver_hash_tuples):
+ cls = self._choices.get(self.build_system, DefaultGuess)
+ return cls(name, url, ver_hash_tuples)
+
def guess_name_and_version(url, args):
# Try to deduce name and version of the new package from the URL
@@ -338,7 +412,7 @@ def fetch_tarballs(url, name, version):
tty.msg("Found %s versions of %s:" % (len(versions), name),
*spack.cmd.elide_list(
["%-10s%s" % (v, u) for v, u in versions.iteritems()]))
- print
+ print('')
archives_to_fetch = tty.get_number(
"Include how many checksums in the package file?",
default=5, abort='q')
@@ -379,16 +453,10 @@ def create(parser, args):
if not ver_hash_tuples:
tty.die("Could not fetch any tarballs for %s" % name)
- # Add prefix to package name if it is an extension.
- if guesser.build_system == 'python':
- name = 'py-{0}'.format(name)
- if guesser.build_system == 'R':
- name = 'r-{0}'.format(name)
- if guesser.build_system == 'octave':
- name = 'octave-{0}'.format(name)
+ guess = guesser.make_guess(name, url, ver_hash_tuples)
# Create a directory for the new package.
- pkg_path = repo.filename_for_package_name(name)
+ pkg_path = repo.filename_for_package_name(guess.name)
if os.path.exists(pkg_path) and not args.force:
tty.die("%s already exists." % pkg_path)
else:
@@ -398,12 +466,15 @@ def create(parser, args):
with open(pkg_path, "w") as pkg_file:
pkg_file.write(
package_template.substitute(
- name=name,
- class_name=mod_to_class(name),
- url=url,
- versions=make_version_calls(ver_hash_tuples),
- dependencies=dependencies_dict[guesser.build_system],
- install=install_dict[guesser.build_system]))
+ name=guess.name,
+ class_name=guess.class_name,
+ base_class_name=guess.base_class_name,
+ url=guess.url,
+ versions=guess.versions,
+ dependencies=guess.dependencies,
+ body=guess.body
+ )
+ )
# If everything checks out, go ahead and edit.
spack.editor(pkg_path)
diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py
index 2b15a0331e..fedd078972 100644
--- a/lib/spack/spack/cmd/deactivate.py
+++ b/lib/spack/spack/cmd/deactivate.py
@@ -27,6 +27,7 @@ import llnl.util.tty as tty
import spack
import spack.cmd
+import spack.store
from spack.graph import topological_sort
description = "Deactivate a package extension."
@@ -56,7 +57,7 @@ def deactivate(parser, args):
if args.all:
if pkg.extendable:
tty.msg("Deactivating all extensions of %s" % pkg.spec.short_spec)
- ext_pkgs = spack.installed_db.installed_extensions_for(spec)
+ ext_pkgs = spack.store.db.installed_extensions_for(spec)
for ext_pkg in ext_pkgs:
ext_pkg.spec.normalize()
diff --git a/lib/spack/spack/cmd/debug.py b/lib/spack/spack/cmd/debug.py
new file mode 100644
index 0000000000..c7e90cb210
--- /dev/null
+++ b/lib/spack/spack/cmd/debug.py
@@ -0,0 +1,100 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 re
+from datetime import datetime
+from glob import glob
+
+import llnl.util.tty as tty
+from llnl.util.filesystem import working_dir
+
+import spack
+from spack.util.executable import which
+
+description = "Debugging commands for troubleshooting Spack."
+
+
+def setup_parser(subparser):
+ sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='debug_command')
+ sp.add_parser('create-db-tarball',
+ help="Create a tarball of Spack's installation metadata.")
+
+
+def _debug_tarball_suffix():
+ now = datetime.now()
+ suffix = now.strftime('%Y-%m-%d-%H%M%S')
+
+ git = which('git')
+ if not git:
+ return 'nobranch-nogit-%s' % suffix
+
+ with working_dir(spack.spack_root):
+ if not os.path.isdir('.git'):
+ return 'nobranch.nogit.%s' % suffix
+
+ # Get symbolic branch name and strip any special chars (mainly '/')
+ symbolic = git(
+ 'rev-parse', '--abbrev-ref', '--short', 'HEAD', output=str).strip()
+ symbolic = re.sub(r'[^\w.-]', '-', symbolic)
+
+ # Get the commit hash too.
+ commit = git(
+ 'rev-parse', '--short', 'HEAD', output=str).strip()
+
+ if symbolic == commit:
+ return "nobranch.%s.%s" % (commit, suffix)
+ else:
+ return "%s.%s.%s" % (symbolic, commit, suffix)
+
+
+def create_db_tarball(args):
+ tar = which('tar')
+ tarball_name = "spack-db.%s.tar.gz" % _debug_tarball_suffix()
+ tarball_path = os.path.abspath(tarball_name)
+
+ base = os.path.basename(spack.store.root)
+ transform_args = []
+ if 'GNU' in tar('--version', output=str):
+ transform_args = ['--transform', 's/^%s/%s/' % (base, tarball_name)]
+ else:
+ transform_args = ['-s', '/^%s/%s/' % (base, tarball_name)]
+
+ wd = os.path.dirname(spack.store.root)
+ with working_dir(wd):
+ files = [spack.store.db._index_path]
+ files += glob('%s/*/*/*/.spack/spec.yaml' % base)
+ files = [os.path.relpath(f) for f in files]
+
+ args = ['-czf', tarball_path]
+ args += transform_args
+ args += files
+ tar(*args)
+
+ tty.msg('Created %s' % tarball_name)
+
+
+def debug(parser, args):
+ action = {'create-db-tarball': create_db_tarball}
+ action[args.debug_command](args)
diff --git a/lib/spack/spack/cmd/diy.py b/lib/spack/spack/cmd/diy.py
index 487654d261..22966a26eb 100644
--- a/lib/spack/spack/cmd/diy.py
+++ b/lib/spack/spack/cmd/diy.py
@@ -30,6 +30,7 @@ import llnl.util.tty as tty
import spack
import spack.cmd
+import spack.cmd.common.arguments as arguments
from spack.cmd.edit import edit_package
from spack.stage import DIYStage
@@ -53,6 +54,9 @@ def setup_parser(subparser):
'spec', nargs=argparse.REMAINDER,
help="specs to use for install. Must contain package AND version.")
+ cd_group = subparser.add_mutually_exclusive_group()
+ arguments.add_common_arguments(cd_group, ['clean', 'dirty'])
+
def diy(self, args):
if not args.spec:
@@ -62,42 +66,40 @@ def diy(self, args):
if len(specs) > 1:
tty.die("spack diy only takes one spec.")
- # Take a write lock before checking for existence.
- with spack.installed_db.write_transaction():
- spec = specs[0]
- if not spack.repo.exists(spec.name):
- tty.warn("No such package: %s" % spec.name)
- create = tty.get_yes_or_no("Create this package?", default=False)
- if not create:
- tty.msg("Exiting without creating.")
- sys.exit(1)
- else:
- tty.msg("Running 'spack edit -f %s'" % spec.name)
- edit_package(spec.name, spack.repo.first_repo(), None, True)
- return
-
- 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)
-
- if package.installed:
- tty.error("Already installed in %s" % package.prefix)
- tty.msg("Uninstall or try adding a version suffix for this "
- "DIY build.")
+ spec = specs[0]
+ if not spack.repo.exists(spec.name):
+ tty.warn("No such package: %s" % spec.name)
+ create = tty.get_yes_or_no("Create this package?", default=False)
+ if not create:
+ tty.msg("Exiting without creating.")
sys.exit(1)
+ else:
+ tty.msg("Running 'spack edit -f %s'" % spec.name)
+ edit_package(spec.name, spack.repo.first_repo(), None, True)
+ return
+
+ 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)
+
+ if package.installed:
+ tty.error("Already installed in %s" % package.prefix)
+ tty.msg("Uninstall or try adding a version suffix for this DIY build.")
+ sys.exit(1)
- # Forces the build to run out of the current directory.
- package.stage = DIYStage(os.getcwd())
+ # Forces the build to run out of the current directory.
+ package.stage = DIYStage(os.getcwd())
- # TODO: make this an argument, not a global.
- spack.do_checksum = False
+ # TODO: make this an argument, not a global.
+ spack.do_checksum = False
- package.do_install(
- keep_prefix=args.keep_prefix,
- ignore_deps=args.ignore_deps,
- verbose=not args.quiet,
- keep_stage=True) # don't remove source dir for DIY.
+ package.do_install(
+ keep_prefix=args.keep_prefix,
+ install_deps=not args.ignore_deps,
+ verbose=not args.quiet,
+ keep_stage=True, # don't remove source dir for DIY.
+ dirty=args.dirty)
diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py
index b5c484305f..bd149044ca 100644
--- a/lib/spack/spack/cmd/extensions.py
+++ b/lib/spack/spack/cmd/extensions.py
@@ -30,6 +30,7 @@ from llnl.util.tty.colify import colify
import spack
import spack.cmd
import spack.cmd.find
+import spack.store
description = "List extensions for package."
@@ -86,8 +87,9 @@ def extensions(parser, args):
#
# List specs of installed extensions.
#
- installed = [
- s.spec for s in spack.installed_db.installed_extensions_for(spec)]
+ installed = [s.spec
+ for s in spack.store.db.installed_extensions_for(spec)]
+
print
if not installed:
tty.msg("None installed.")
@@ -98,7 +100,7 @@ def extensions(parser, args):
#
# List specs of activated extensions.
#
- activated = spack.install_layout.extension_map(spec)
+ activated = spack.store.layout.extension_map(spec)
print
if not activated:
tty.msg("None activated.")
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index d3ea38c573..542114c8c1 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -22,15 +22,11 @@
# 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 argparse
import sys
import llnl.util.tty as tty
-import spack
-import spack.spec
-from llnl.util.lang import *
-from llnl.util.tty.colify import *
-from llnl.util.tty.color import *
+import spack.cmd.common.arguments as arguments
+
from spack.cmd import display_specs
description = "Find installed spack packages"
@@ -42,6 +38,7 @@ def setup_parser(subparser):
action='store_const',
dest='mode',
const='short',
+ default='short',
help='Show only specs (default)')
format_group.add_argument('-p', '--paths',
action='store_const',
@@ -55,24 +52,18 @@ def setup_parser(subparser):
const='deps',
help='Show full dependency DAG of installed packages')
- subparser.add_argument('-l', '--long',
- action='store_true',
- dest='long',
- help='Show dependency hashes as well as versions.')
- subparser.add_argument('-L', '--very-long',
- action='store_true',
- dest='very_long',
- help='Show dependency hashes as well as versions.')
+ arguments.add_common_arguments(subparser, ['long', 'very_long'])
+
subparser.add_argument('-f', '--show-flags',
action='store_true',
dest='show_flags',
help='Show spec compiler flags.')
-
- subparser.add_argument(
+ implicit_explicit = subparser.add_mutually_exclusive_group()
+ implicit_explicit.add_argument(
'-e', '--explicit',
action='store_true',
help='Show only specs that were installed explicitly')
- subparser.add_argument(
+ implicit_explicit.add_argument(
'-E', '--implicit',
action='store_true',
help='Show only specs that were installed as dependencies')
@@ -99,17 +90,10 @@ def setup_parser(subparser):
action='store_true',
help='Show fully qualified package names.')
- subparser.add_argument('query_specs',
- nargs=argparse.REMAINDER,
- help='optional specs to filter results')
+ arguments.add_common_arguments(subparser, ['constraint'])
def query_arguments(args):
- # Check arguments
- if args.explicit and args.implicit:
- tty.error('You can\'t pass -E and -e options simultaneously.')
- raise SystemExit(1)
-
# Set up query arguments.
installed, known = True, any
if args.only_missing:
@@ -128,35 +112,17 @@ def query_arguments(args):
def find(parser, args):
- # Filter out specs that don't exist.
- query_specs = spack.cmd.parse_specs(args.query_specs)
- query_specs, nonexisting = partition_list(
- query_specs, lambda s: spack.repo.exists(s.name) or not s.name)
-
- if nonexisting:
- msg = "No such package%s: " % ('s' if len(nonexisting) > 1 else '')
- msg += ", ".join(s.name for s in nonexisting)
- tty.msg(msg)
-
- if not query_specs:
- return
-
q_args = query_arguments(args)
-
- # Get all the specs the user asked for
- if not query_specs:
- specs = set(spack.installed_db.query(**q_args))
- else:
- results = [set(spack.installed_db.query(qs, **q_args))
- for qs in query_specs]
- specs = set.union(*results)
-
- if not args.mode:
- args.mode = 'short'
-
+ query_specs = args.specs(**q_args)
+ # Exit early if no package matches the constraint
+ if not query_specs and args.constraint:
+ msg = "No package matches the query: {0}".format(args.contraint)
+ tty.msg(msg)
+ return
+ # Display the result
if sys.stdout.isatty():
- tty.msg("%d installed packages." % len(specs))
- display_specs(specs,
+ tty.msg("%d installed packages." % len(query_specs))
+ display_specs(query_specs,
mode=args.mode,
long=args.long,
very_long=args.very_long,
diff --git a/lib/spack/spack/cmd/flake8.py b/lib/spack/spack/cmd/flake8.py
new file mode 100644
index 0000000000..a4c607a640
--- /dev/null
+++ b/lib/spack/spack/cmd/flake8.py
@@ -0,0 +1,184 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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 re
+import os
+import sys
+import shutil
+import tempfile
+import argparse
+
+from llnl.util.filesystem import *
+
+import spack
+from spack.util.executable import *
+
+description = "Runs source code style checks on Spack. Requires flake8."
+
+changed_files_path = os.path.join(spack.share_path, 'qa', 'changed_files')
+changed_files = Executable(changed_files_path)
+flake8 = None
+
+#
+# This is a dict that maps:
+# filename pattern ->
+# a flake8 exemption code ->
+# list of patterns, for which matching lines should have codes applied.
+#
+exemptions = {
+ # exemptions applied only to package.py files.
+ r'package.py$': {
+ # Exempt lines with urls and descriptions from overlong line errors.
+ 501: [r'^\s*homepage\s*=',
+ r'^\s*url\s*=',
+ r'^\s*git\s*=',
+ r'^\s*svn\s*=',
+ r'^\s*hg\s*=',
+ r'^\s*version\(.*\)',
+ r'^\s*variant\(.*\)',
+ r'^\s*depends_on\(.*\)',
+ r'^\s*extends\(.*\)'],
+ # Exempt '@when' decorated functions from redefinition errors.
+ 811: [r'^\s*\@when\(.*\)'],
+ },
+
+ # exemptions applied to all files.
+ r'.py$': {
+ # Exempt lines with URLs from overlong line errors.
+ 501: [r'(https?|file)\:']
+ },
+}
+
+# compile all regular expressions.
+exemptions = dict((re.compile(file_pattern),
+ dict((code, [re.compile(p) for p in patterns])
+ for code, patterns in error_dict.items()))
+ for file_pattern, error_dict in exemptions.items())
+
+
+def filter_file(source, dest, output=False):
+ """Filter a single file through all the patterns in exemptions."""
+ with open(source) as infile:
+ parent = os.path.dirname(dest)
+ mkdirp(parent)
+
+ with open(dest, 'w') as outfile:
+ for line in infile:
+ line = line.rstrip()
+
+ for file_pattern, errors in exemptions.items():
+ if not file_pattern.search(source):
+ continue
+
+ for code, patterns in errors.items():
+ for pattern in patterns:
+ if pattern.search(line):
+ line += (" # NOQA: ignore=%d" % code)
+ break
+
+ oline = line + '\n'
+ outfile.write(oline)
+ if output:
+ sys.stdout.write(oline)
+
+
+def setup_parser(subparser):
+ subparser.add_argument(
+ '-k', '--keep-temp', action='store_true',
+ help="Do not delete temporary directory where flake8 runs. "
+ "Use for debugging, to see filtered files.")
+ subparser.add_argument(
+ '-o', '--output', action='store_true',
+ help="Send filtered files to stdout as well as temp files.")
+ subparser.add_argument(
+ '-r', '--root-relative', action='store_true', default=False,
+ help="print root-relative paths (default is cwd-relative)")
+ subparser.add_argument(
+ 'files', nargs=argparse.REMAINDER, help="specific files to check")
+
+
+def flake8(parser, args):
+ # Just use this to check for flake8 -- we actually execute it with Popen.
+ global flake8
+ flake8 = which('flake8', required=True)
+
+ temp = tempfile.mkdtemp()
+ try:
+ file_list = args.files
+ if file_list:
+ def prefix_relative(path):
+ return os.path.relpath(
+ os.path.abspath(os.path.realpath(path)), spack.prefix)
+
+ file_list = [prefix_relative(p) for p in file_list]
+
+ with working_dir(spack.prefix):
+ if not file_list:
+ file_list = changed_files('*.py', output=str)
+ file_list = [x for x in file_list.split('\n') if x]
+
+ shutil.copy('.flake8', os.path.join(temp, '.flake8'))
+
+ print '======================================================='
+ print 'flake8: running flake8 code checks on spack.'
+ print
+ print 'Modified files:'
+ for filename in file_list:
+ print " %s" % filename.strip()
+ print('=======================================================')
+
+ # filter files into a temporary directory with exemptions added.
+ for filename in file_list:
+ src_path = os.path.join(spack.prefix, filename)
+ dest_path = os.path.join(temp, filename)
+ filter_file(src_path, dest_path, args.output)
+
+ # run flake8 on the temporary tree.
+ with working_dir(temp):
+ output = flake8('--format', 'pylint', *file_list,
+ fail_on_error=False, output=str)
+
+ if args.root_relative:
+ # print results relative to repo root.
+ print output
+ else:
+ # print results relative to current working directory
+ def cwd_relative(path):
+ return '%s: [' % os.path.relpath(
+ os.path.join(spack.prefix, path.group(1)), os.getcwd())
+
+ for line in output.split('\n'):
+ print re.sub(r'^(.*): \[', cwd_relative, line)
+
+ if flake8.returncode != 0:
+ print "Flake8 found errors."
+ sys.exit(1)
+ else:
+ print "Flake8 checks were clean."
+
+ finally:
+ if args.keep_temp:
+ print "temporary files are in ", temp
+ else:
+ shutil.rmtree(temp, ignore_errors=True)
diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py
index 8faabfbb7b..ed3aed1946 100644
--- a/lib/spack/spack/cmd/graph.py
+++ b/lib/spack/spack/cmd/graph.py
@@ -24,8 +24,11 @@
##############################################################################
import argparse
+import llnl.util.tty as tty
+
import spack
import spack.cmd
+from spack.spec import *
from spack.graph import *
description = "Generate graphs of package dependency relationships."
@@ -36,15 +39,28 @@ def setup_parser(subparser):
method = subparser.add_mutually_exclusive_group()
method.add_argument(
- '--ascii', action='store_true',
+ '-a', '--ascii', action='store_true',
help="Draw graph as ascii to stdout (default).")
method.add_argument(
- '--dot', action='store_true',
+ '-d', '--dot', action='store_true',
help="Generate graph in dot format and print to stdout.")
subparser.add_argument(
- '--concretize', action='store_true',
- help="Concretize specs before graphing.")
+ '-n', '--normalize', action='store_true',
+ help="Skip concretization; only print normalized spec.")
+
+ subparser.add_argument(
+ '-s', '--static', action='store_true',
+ help="Use static information from packages, not dynamic spec info.")
+
+ subparser.add_argument(
+ '-i', '--installed', action='store_true',
+ help="Graph all installed specs in dot format (implies --dot).")
+
+ subparser.add_argument(
+ '-t', '--deptype', action='store',
+ help="Comma-separated list of deptypes to traverse. default=%s."
+ % ','.join(alldeps))
subparser.add_argument(
'specs', nargs=argparse.REMAINDER,
@@ -52,18 +68,32 @@ def setup_parser(subparser):
def graph(parser, args):
- specs = spack.cmd.parse_specs(
- args.specs, normalize=True, concretize=args.concretize)
+ concretize = not args.normalize
+ if args.installed:
+ if args.specs:
+ tty.die("Can't specify specs with --installed")
+ args.dot = True
+ specs = spack.installed_db.query()
+
+ else:
+ specs = spack.cmd.parse_specs(
+ args.specs, normalize=True, concretize=concretize)
if not specs:
setup_parser.parser.print_help()
return 1
+ deptype = nobuild
+ if args.deptype:
+ deptype = tuple(args.deptype.split(','))
+ validate_deptype(deptype)
+ deptype = canonical_deptype(deptype)
+
if args.dot: # Dot graph only if asked for.
- graph_dot(*specs)
+ graph_dot(specs, static=args.static, deptype=deptype)
elif specs: # ascii is default: user doesn't need to provide it explicitly
- graph_ascii(specs[0], debug=spack.debug)
+ graph_ascii(specs[0], debug=spack.debug, deptype=deptype)
for spec in specs[1:]:
print # extra line bt/w independent graphs
graph_ascii(spec, debug=spack.debug)
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 498518057b..5366ad4aa8 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -48,8 +48,11 @@ def setup_parser(subparser):
def print_text_info(pkg):
"""Print out a plain text description of a package."""
- print "Package: ", pkg.name
- print "Homepage: ", pkg.homepage
+ header = "{0}: ".format(pkg.build_system_class)
+
+ print header, pkg.name
+ whitespaces = ''.join([' '] * (len(header) - len("Homepage: ")))
+ print "Homepage:", whitespaces, pkg.homepage
print
print "Safe versions: "
@@ -84,17 +87,24 @@ def print_text_info(pkg):
print " " + fmt % (name, default, desc)
+ print
+ print "Installation Phases:"
+ phase_str = ''
+ for phase in pkg.phases:
+ phase_str += " {0}".format(phase)
+ print phase_str
+
for deptype in ('build', 'link', 'run'):
print
print "%s Dependencies:" % deptype.capitalize()
- deps = pkg.dependencies_of_type(deptype)
+ deps = sorted(pkg.dependencies_of_type(deptype))
if deps:
colify(deps, indent=4)
else:
print " None"
print
- print "Virtual packages: "
+ print "Virtual Packages: "
if pkg.provided:
for spec, when in pkg.provided.items():
print " %s provides %s" % (when, spec)
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 7663a97a28..de45c7ca4f 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -23,19 +23,36 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import argparse
+import codecs
+import functools
+import os
+import time
+import xml.dom.minidom
+import xml.etree.ElementTree as ET
+import llnl.util.filesystem as fs
import llnl.util.tty as tty
-
import spack
import spack.cmd
+import spack.cmd.common.arguments as arguments
+from spack.build_environment import InstallError
+from spack.fetch_strategy import FetchError
+from spack.package import PackageBase
description = "Build and install packages"
def setup_parser(subparser):
subparser.add_argument(
- '-i', '--ignore-dependencies', action='store_true', dest='ignore_deps',
- help="Do not try to install dependencies of requested packages.")
+ '--only',
+ default='package,dependencies',
+ dest='things_to_install',
+ choices=['package', 'dependencies'],
+ help="""Select the mode of installation.
+The default is to install the package along with all its dependencies.
+Alternatively one can decide to install only the package or only
+the dependencies."""
+ )
subparser.add_argument(
'-j', '--jobs', action='store', type=int,
help="Explicitly set number of make jobs. Default is #cpus.")
@@ -54,19 +71,222 @@ def setup_parser(subparser):
subparser.add_argument(
'--fake', action='store_true', dest='fake',
help="Fake install. Just remove prefix and create a fake file.")
+
+ cd_group = subparser.add_mutually_exclusive_group()
+ arguments.add_common_arguments(cd_group, ['clean', 'dirty'])
+
subparser.add_argument(
- '--dirty', action='store_true', dest='dirty',
- help="Install a package *without* cleaning the environment.")
- subparser.add_argument(
- 'packages', nargs=argparse.REMAINDER,
- help="specs of packages to install")
+ 'package',
+ nargs=argparse.REMAINDER,
+ help="spec of the package to install"
+ )
subparser.add_argument(
'--run-tests', action='store_true', dest='run_tests',
- help="Run tests during installation of a package.")
+ help="Run package level tests during installation."
+ )
+ subparser.add_argument(
+ '--log-format',
+ default=None,
+ choices=['junit'],
+ help="Format to be used for log files."
+ )
+ subparser.add_argument(
+ '--log-file',
+ default=None,
+ help="Filename for the log file. If not passed a default will be used."
+ )
+
+
+# Needed for test cases
+class TestResult(object):
+ PASSED = 0
+ FAILED = 1
+ SKIPPED = 2
+ ERRORED = 3
+
+
+class TestSuite(object):
+ def __init__(self):
+ self.root = ET.Element('testsuite')
+ self.tests = []
+
+ def append(self, item):
+ if not isinstance(item, TestCase):
+ raise TypeError(
+ 'only TestCase instances may be appended to TestSuite'
+ )
+ self.tests.append(item) # Append the item to the list of tests
+
+ def dump(self, filename):
+ # Prepare the header for the entire test suite
+ number_of_errors = sum(
+ x.result_type == TestResult.ERRORED for x in self.tests
+ )
+ self.root.set('errors', str(number_of_errors))
+ number_of_failures = sum(
+ x.result_type == TestResult.FAILED for x in self.tests
+ )
+ self.root.set('failures', str(number_of_failures))
+ self.root.set('tests', str(len(self.tests)))
+
+ for item in self.tests:
+ self.root.append(item.element)
+
+ with codecs.open(filename, 'wb', 'utf-8') as file:
+ xml_string = ET.tostring(self.root)
+ xml_string = xml.dom.minidom.parseString(xml_string).toprettyxml()
+ file.write(xml_string)
+
+
+class TestCase(object):
+
+ results = {
+ TestResult.PASSED: None,
+ TestResult.SKIPPED: 'skipped',
+ TestResult.FAILED: 'failure',
+ TestResult.ERRORED: 'error',
+ }
+
+ def __init__(self, classname, name):
+ self.element = ET.Element('testcase')
+ self.element.set('classname', str(classname))
+ self.element.set('name', str(name))
+ self.result_type = None
+ def set_duration(self, duration):
+ self.element.set('time', str(duration))
-def install(parser, args):
- if not args.packages:
+ def set_result(self, result_type,
+ message=None, error_type=None, text=None):
+ self.result_type = result_type
+ result = TestCase.results[self.result_type]
+ if result is not None and result is not TestResult.PASSED:
+ subelement = ET.SubElement(self.element, result)
+ if error_type is not None:
+ subelement.set('type', error_type)
+ if message is not None:
+ subelement.set('message', str(message))
+ if text is not None:
+ subelement.text = text
+
+
+def fetch_text(path):
+ if not os.path.exists(path):
+ return ''
+
+ with codecs.open(path, 'rb', 'utf-8') as f:
+ return '\n'.join(
+ list(line.strip() for line in f.readlines())
+ )
+
+
+def junit_output(spec, test_suite):
+ # Cycle once and for all on the dependencies and skip
+ # the ones that are already installed. This ensures that
+ # for the same spec, the same number of entries will be
+ # displayed in the XML report
+ for x in spec.traverse(order='post'):
+ package = spack.repo.get(x)
+ if package.installed:
+ test_case = TestCase(package.name, x.short_spec)
+ test_case.set_duration(0.0)
+ test_case.set_result(
+ TestResult.SKIPPED,
+ message='Skipped [already installed]',
+ error_type='already_installed'
+ )
+ test_suite.append(test_case)
+
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(self, *args, ** kwargs):
+
+ # Check if the package has been installed already
+ if self.installed:
+ return
+
+ test_case = TestCase(self.name, self.spec.short_spec)
+ # Try to install the package
+ try:
+ # If already installed set the spec as skipped
+ start_time = time.time()
+ # PackageBase.do_install
+ func(self, *args, **kwargs)
+ duration = time.time() - start_time
+ test_case.set_duration(duration)
+ test_case.set_result(TestResult.PASSED)
+ except InstallError:
+ # Check if the package relies on dependencies that
+ # did not install
+ duration = time.time() - start_time
+ test_case.set_duration(duration)
+ if [x for x in self.spec.dependencies(('link', 'run')) if not spack.repo.get(x).installed]: # NOQA: ignore=E501
+ test_case.set_duration(0.0)
+ test_case.set_result(
+ TestResult.SKIPPED,
+ message='Skipped [failed dependencies]',
+ error_type='dep_failed'
+ )
+ else:
+ # An InstallError is considered a failure (the recipe
+ # didn't work correctly)
+ text = fetch_text(self.build_log_path)
+ test_case.set_result(
+ TestResult.FAILED,
+ message='Installation failure',
+ text=text
+ )
+ except FetchError:
+ # A FetchError is considered an error as
+ # we didn't even start building
+ duration = time.time() - start_time
+ test_case.set_duration(duration)
+ text = fetch_text(self.build_log_path)
+ test_case.set_result(
+ TestResult.ERRORED,
+ message='Unable to fetch package',
+ text=text
+ )
+ except Exception:
+ # Anything else is also an error
+ duration = time.time() - start_time
+ test_case.set_duration(duration)
+ text = fetch_text(self.build_log_path)
+ test_case.set_result(
+ TestResult.ERRORED,
+ message='Unexpected exception thrown during install',
+ text=text
+ )
+ except:
+ # Anything else is also an error
+ duration = time.time() - start_time
+ test_case.set_duration(duration)
+ text = fetch_text(self.build_log_path)
+ test_case.set_result(
+ TestResult.ERRORED,
+ message='Unknown error',
+ text=text
+ )
+
+ # Try to get the log
+ test_suite.append(test_case)
+ return wrapper
+ return decorator
+
+
+def default_log_file(spec):
+ """Computes the default filename for the log file and creates
+ the corresponding directory if not present
+ """
+ fmt = 'test-{x.name}-{x.version}-{hash}.xml'
+ basename = fmt.format(x=spec, hash=spec.dag_hash())
+ dirname = fs.join_path(spack.var_path, 'junit-report')
+ fs.mkdirp(dirname)
+ return fs.join_path(dirname, basename)
+
+
+def install(parser, args, **kwargs):
+ if not args.package:
tty.die("install requires at least one package argument")
if args.jobs is not None:
@@ -76,17 +296,51 @@ def install(parser, args):
if args.no_checksum:
spack.do_checksum = False # TODO: remove this global.
- specs = spack.cmd.parse_specs(args.packages, concretize=True)
- for spec in specs:
+ # Parse cli arguments and construct a dictionary
+ # that will be passed to Package.do_install API
+ kwargs.update({
+ 'keep_prefix': args.keep_prefix,
+ 'keep_stage': args.keep_stage,
+ 'install_deps': 'dependencies' in args.things_to_install,
+ 'make_jobs': args.jobs,
+ 'run_tests': args.run_tests,
+ 'verbose': args.verbose,
+ 'fake': args.fake,
+ 'dirty': args.dirty
+ })
+
+ # Spec from cli
+ specs = spack.cmd.parse_specs(args.package, concretize=True)
+ if len(specs) != 1:
+ tty.error('only one spec can be installed at a time.')
+ spec = specs.pop()
+
+ # Check if we were asked to produce some log for dashboards
+ if args.log_format is not None:
+ # Compute the filename for logging
+ log_filename = args.log_file
+ if not log_filename:
+ log_filename = default_log_file(spec)
+ # Create the test suite in which to log results
+ test_suite = TestSuite()
+ # Decorate PackageBase.do_install to get installation status
+ PackageBase.do_install = junit_output(
+ spec, test_suite
+ )(PackageBase.do_install)
+
+ # Do the actual installation
+ if args.things_to_install == 'dependencies':
+ # Install dependencies as-if they were installed
+ # for root (explicit=False in the DB)
+ kwargs['explicit'] = False
+ for s in spec.dependencies():
+ p = spack.repo.get(s)
+ p.do_install(**kwargs)
+ else:
package = spack.repo.get(spec)
- with spack.installed_db.write_transaction():
- package.do_install(
- keep_prefix=args.keep_prefix,
- keep_stage=args.keep_stage,
- ignore_deps=args.ignore_deps,
- make_jobs=args.jobs,
- run_tests=args.run_tests,
- verbose=args.verbose,
- fake=args.fake,
- dirty=args.dirty,
- explicit=True)
+ kwargs['explicit'] = True
+ package.do_install(**kwargs)
+
+ # Dump log file if asked to
+ if args.log_format is not None:
+ test_suite.dump(log_filename)
diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py
index c921efd1bd..e1389df69f 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -22,36 +22,51 @@
# 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 argparse
+import cgi
+import fnmatch
+import re
import sys
+from StringIO import StringIO
+
import llnl.util.tty as tty
-import argparse
+import spack
from llnl.util.tty.colify import colify
-import spack
-import fnmatch
-import re
+description = "Print available spack packages to stdout in different formats"
+
+formatters = {}
-description = "List available spack packages"
+
+def formatter(func):
+ """Decorator used to register formatters"""
+ formatters[func.__name__] = func
+ return func
def setup_parser(subparser):
subparser.add_argument(
'filter', nargs=argparse.REMAINDER,
- help='Optional glob patterns to filter results.')
- subparser.add_argument(
- '-s', '--sensitive', action='store_true', default=False,
- help='Use case-sensitive filtering. Default is case sensitive, '
- 'unless the query contains a capital letter.')
+ help='Optional case-insensitive glob patterns to filter results.')
subparser.add_argument(
'-d', '--search-description', action='store_true', default=False,
help='Filtering will also search the description for a match.')
+ subparser.add_argument(
+ '--format', default='name_only', choices=formatters,
+ help='Format to be used to print the output [default: name_only]')
-def list(parser, args):
- # Start with all package names.
- pkgs = set(spack.repo.all_package_names())
+def filter_by_name(pkgs, args):
+ """
+ Filters the sequence of packages according to user prescriptions
- # filter if a filter arg was provided
+ Args:
+ pkgs: sequence of packages
+ args: parsed command line arguments
+
+ Returns:
+ filtered and sorted list of packages
+ """
if args.filter:
res = []
for f in args.filter:
@@ -60,10 +75,7 @@ def list(parser, args):
else:
r = fnmatch.translate(f)
- re_flags = re.I
- if any(l.isupper for l in f) or args.sensitive:
- re_flags = 0
- rc = re.compile(r, flags=re_flags)
+ rc = re.compile(r, flags=re.IGNORECASE)
res.append(rc)
if args.search_description:
@@ -80,11 +92,91 @@ def list(parser, args):
return f.match(p)
pkgs = [p for p in pkgs if any(match(p, f) for f in res)]
- # sort before displaying.
- sorted_packages = sorted(pkgs, key=lambda s: s.lower())
+ return sorted(pkgs, key=lambda s: s.lower())
+
- # Print all the package names in columns
+@formatter
+def name_only(pkgs):
indent = 0
if sys.stdout.isatty():
- tty.msg("%d packages." % len(sorted_packages))
- colify(sorted_packages, indent=indent)
+ tty.msg("%d packages." % len(pkgs))
+ colify(pkgs, indent=indent)
+
+
+@formatter
+def rst(pkgs):
+ """Print out information on all packages in restructured text."""
+
+ def github_url(pkg):
+ """Link to a package file on github."""
+ url = 'https://github.com/LLNL/spack/blob/develop/var/spack/repos/builtin/packages/{0}/package.py'
+ return url.format(pkg.name)
+
+ def rst_table(elts):
+ """Print out a RST-style table."""
+ cols = StringIO()
+ ncol, widths = colify(elts, output=cols, tty=True)
+ header = ' '.join('=' * (w - 1) for w in widths)
+ return '%s\n%s%s' % (header, cols.getvalue(), header)
+
+ pkg_names = pkgs
+ pkgs = [spack.repo.get(name) for name in pkg_names]
+
+ print('.. _package-list:')
+ print('')
+ print('============')
+ print('Package List')
+ print('============')
+ print('')
+ print('This is a list of things you can install using Spack. It is')
+ print('automatically generated based on the packages in the latest Spack')
+ print('release.')
+ print('')
+ print('Spack currently has %d mainline packages:' % len(pkgs))
+ print('')
+ print(rst_table('`%s`_' % p for p in pkg_names))
+ print('')
+
+ # Output some text for each package.
+ for pkg in pkgs:
+ print('-----')
+ print('')
+ print('.. _%s:' % pkg.name)
+ print('')
+ # Must be at least 2 long, breaks for single letter packages like R.
+ print('-' * max(len(pkg.name), 2))
+ print(pkg.name)
+ print('-' * max(len(pkg.name), 2))
+ print('')
+ print('Homepage:')
+ print(' * `%s <%s>`__' % (cgi.escape(pkg.homepage), pkg.homepage))
+ print('')
+ print('Spack package:')
+ print(' * `%s/package.py <%s>`__' % (pkg.name, github_url(pkg)))
+ print('')
+ if pkg.versions:
+ print('Versions:')
+ print(' ' + ', '.join(str(v) for v in
+ reversed(sorted(pkg.versions))))
+ print('')
+
+ for deptype in spack.alldeps:
+ deps = pkg.dependencies_of_type(deptype)
+ if deps:
+ print('%s Dependencies' % deptype.capitalize())
+ print(' ' + ', '.join('%s_' % d if d in pkg_names
+ else d for d in deps))
+ print('')
+
+ print('Description:')
+ print(pkg.format_doc(indent=2))
+ print('')
+
+
+def list(parser, args):
+ # Retrieve the names of all the packages
+ pkgs = set(spack.repo.all_package_names())
+ # Filter the set appropriately
+ sorted_packages = filter_by_name(pkgs, args)
+ # Print to stdout
+ formatters[args.format](sorted_packages)
diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py
index b9c8b5c330..54f7185707 100644
--- a/lib/spack/spack/cmd/location.py
+++ b/lib/spack/spack/cmd/location.py
@@ -25,7 +25,6 @@
import argparse
import llnl.util.tty as tty
-from llnl.util.filesystem import join_path
import spack
import spack.cmd
@@ -77,7 +76,7 @@ def location(parser, args):
print spack.prefix
elif args.packages:
- print spack.repo.root
+ print spack.repo.first_repo().root
elif args.stages:
print spack.stage_path
@@ -99,7 +98,7 @@ def location(parser, args):
if args.package_dir:
# This one just needs the spec name.
- print join_path(spack.repo.root, spec.name)
+ print spack.repo.dirname_for_package_name(spec.name)
else:
# These versions need concretized specs.
diff --git a/lib/spack/spack/cmd/md5.py b/lib/spack/spack/cmd/md5.py
index 506cf0913f..2ae279a41e 100644
--- a/lib/spack/spack/cmd/md5.py
+++ b/lib/spack/spack/cmd/md5.py
@@ -25,6 +25,7 @@
import argparse
import hashlib
import os
+from urlparse import urlparse
import llnl.util.tty as tty
import spack.util.crypto
@@ -49,13 +50,23 @@ def compute_md5_checksum(url):
return value
+def normalized(files):
+ for p in files:
+ result = urlparse(p)
+ value = p
+ if not result.scheme:
+ value = os.path.abspath(p)
+ yield value
+
+
def md5(parser, args):
if not args.files:
setup_parser.parser.print_help()
return 1
+ urls = [x for x in normalized(args.files)]
results = []
- for url in args.files:
+ for url in urls:
try:
checksum = compute_md5_checksum(url)
results.append((checksum, url))
@@ -70,4 +81,4 @@ def md5(parser, args):
checksum = 'checksum' if len(results) == 1 else 'checksums'
tty.msg("%d MD5 %s:" % (len(results), checksum))
for checksum, url in results:
- print "%s %s" % (checksum, url)
+ print("{0} {1}".format(checksum, url))
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
index 2d0b83fe00..31460b3124 100644
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -29,10 +29,10 @@ import os
import shutil
import sys
+import llnl.util.filesystem as filesystem
import llnl.util.tty as tty
import spack.cmd
import spack.cmd.common.arguments as arguments
-import llnl.util.filesystem as filesystem
from spack.modules import module_types
description = "Manipulate module files"
@@ -210,7 +210,8 @@ def refresh(mtype, specs, args):
cls = module_types[mtype]
# Detect name clashes
- writers = [cls(spec) for spec in specs]
+ writers = [cls(spec) for spec in specs
+ if spack.repo.exists(spec.name)] # skip unknown packages.
file2writer = collections.defaultdict(list)
for item in writers:
file2writer[item.file_name].append(item)
@@ -243,17 +244,17 @@ def module(parser, args):
'known': True
},
}
- arguments.ConstraintAction.qualifiers.update(constraint_qualifiers)
-
+ query_args = constraint_qualifiers.get(args.subparser_name, {})
+ specs = args.specs(**query_args)
module_type = args.module_type
constraint = args.constraint
try:
- callbacks[args.subparser_name](module_type, args.specs, args)
+ callbacks[args.subparser_name](module_type, specs, args)
except MultipleMatches:
message = ('the constraint \'{query}\' matches multiple packages, '
'and this is not allowed in this context')
tty.error(message.format(query=constraint))
- for s in args.specs:
+ for s in specs:
sys.stderr.write(s.format(color=True) + '\n')
raise SystemExit(1)
except NoMatch:
diff --git a/lib/spack/spack/cmd/package-list.py b/lib/spack/spack/cmd/package-list.py
deleted file mode 100644
index 9ed42de823..0000000000
--- a/lib/spack/spack/cmd/package-list.py
+++ /dev/null
@@ -1,99 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created 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 Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, 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 Lesser 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 cgi
-from StringIO import StringIO
-from llnl.util.tty.colify import *
-import spack
-
-description = "Print a list of all packages in reStructuredText."
-
-
-def github_url(pkg):
- """Link to a package file on github."""
- url = "https://github.com/llnl/spack/blob/master/var/spack/packages/%s/package.py"
- return (url % pkg.name)
-
-
-def rst_table(elts):
- """Print out a RST-style table."""
- cols = StringIO()
- ncol, widths = colify(elts, output=cols, tty=True)
- header = " ".join("=" * (w - 1) for w in widths)
- return "%s\n%s%s" % (header, cols.getvalue(), header)
-
-
-def print_rst_package_list():
- """Print out information on all packages in restructured text."""
- pkgs = sorted(spack.repo.all_packages(), key=lambda s: s.name.lower())
- pkg_names = [p.name for p in pkgs]
-
- print ".. _package-list:"
- print
- print "Package List"
- print "=================="
-
- print "This is a list of things you can install using Spack. It is"
- print "automatically generated based on the packages in the latest Spack"
- print "release."
- print
-
- print "Spack currently has %d mainline packages:" % len(pkgs)
- print
- print rst_table("`%s`_" % p for p in pkg_names)
- print
- print "-----"
-
- # Output some text for each package.
- for pkg in pkgs:
- print
- print ".. _%s:" % pkg.name
- print
- print pkg.name
- print "-" * len(pkg.name)
- print "Links:"
- print " * `%s <%s>`__" % (cgi.escape(pkg.homepage), pkg.homepage)
- print " * `%s/package.py <%s>`__" % (pkg.name, github_url(pkg))
- print
- if pkg.versions:
- print "Versions:"
- print " " + ", ".join(str(v) for v in
- reversed(sorted(pkg.versions)))
-
- for deptype in spack.alldeps:
- deps = pkg.dependencies_of_type(deptype)
- if deps:
- print "%s Dependencies" % deptype.capitalize()
- print " " + ", ".join("%s_" % d if d in pkg_names
- else d for d in deps)
- print
-
- print "Description:"
- print pkg.format_doc(indent=2)
- print
- print "-----"
-
-
-def package_list(parser, args):
- print_rst_package_list()
diff --git a/lib/spack/spack/cmd/purge.py b/lib/spack/spack/cmd/purge.py
index 26d160635c..66cfc2af29 100644
--- a/lib/spack/spack/cmd/purge.py
+++ b/lib/spack/spack/cmd/purge.py
@@ -36,8 +36,8 @@ def setup_parser(subparser):
'-d', '--downloads', action='store_true',
help="Remove cached downloads.")
subparser.add_argument(
- '-u', '--user-cache', action='store_true',
- help="Remove caches in user home directory. Includes virtual indices.")
+ '-m', '--misc-cache', action='store_true',
+ help="Remove long-lived caches, like the virtual package index.")
subparser.add_argument(
'-a', '--all', action='store_true',
help="Remove all of the above.")
@@ -45,7 +45,7 @@ def setup_parser(subparser):
def purge(parser, args):
# Special case: no flags.
- if not any((args.stage, args.downloads, args.user_cache, args.all)):
+ if not any((args.stage, args.downloads, args.misc_cache, args.all)):
stage.purge()
return
@@ -54,5 +54,5 @@ def purge(parser, args):
stage.purge()
if args.downloads or args.all:
spack.fetch_cache.destroy()
- if args.user_cache or args.all:
- spack.user_cache.destroy()
+ if args.misc_cache or args.all:
+ spack.misc_cache.destroy()
diff --git a/lib/spack/spack/cmd/reindex.py b/lib/spack/spack/cmd/reindex.py
index e37eebbd92..7dddda2ffb 100644
--- a/lib/spack/spack/cmd/reindex.py
+++ b/lib/spack/spack/cmd/reindex.py
@@ -23,9 +23,9 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import spack
-
+import spack.store
description = "Rebuild Spack's package database."
def reindex(parser, args):
- spack.installed_db.reindex(spack.install_layout)
+ spack.store.db.reindex(spack.store.layout)
diff --git a/lib/spack/spack/cmd/setup.py b/lib/spack/spack/cmd/setup.py
index b55e102c0e..953906975e 100644
--- a/lib/spack/spack/cmd/setup.py
+++ b/lib/spack/spack/cmd/setup.py
@@ -22,14 +22,19 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-import sys
-import os
import argparse
+import copy
+import os
+import string
+import sys
import llnl.util.tty as tty
-
import spack
import spack.cmd
+import spack.cmd.install as install
+import spack.cmd.common.arguments as arguments
+from llnl.util.filesystem import set_executable
+from spack import which
from spack.cmd.edit import edit_package
from spack.stage import DIYStage
@@ -47,6 +52,74 @@ def setup_parser(subparser):
'spec', nargs=argparse.REMAINDER,
help="specs to use for install. Must contain package AND version.")
+ cd_group = subparser.add_mutually_exclusive_group()
+ arguments.add_common_arguments(cd_group, ['clean', 'dirty'])
+
+
+def spack_transitive_include_path():
+ return ';'.join(
+ os.path.join(dep, 'include')
+ for dep in os.environ['SPACK_DEPENDENCIES'].split(os.pathsep)
+ )
+
+
+def write_spconfig(package):
+ # Set-up the environment
+ spack.build_environment.setup_package(package)
+
+ cmd = [str(which('cmake'))] + package.std_cmake_args + package.cmake_args()
+
+ env = dict()
+
+ paths = os.environ['PATH'].split(':')
+ paths = [item for item in paths if 'spack/env' not in item]
+ env['PATH'] = ':'.join(paths)
+ env['SPACK_TRANSITIVE_INCLUDE_PATH'] = spack_transitive_include_path()
+ env['CMAKE_PREFIX_PATH'] = os.environ['CMAKE_PREFIX_PATH']
+ env['CC'] = os.environ['SPACK_CC']
+ env['CXX'] = os.environ['SPACK_CXX']
+ env['FC'] = os.environ['SPACK_FC']
+
+ setup_fname = 'spconfig.py'
+ with open(setup_fname, 'w') as fout:
+ fout.write(
+ r"""#!%s
+#
+
+import sys
+import os
+import subprocess
+
+def cmdlist(str):
+ return list(x.strip().replace("'",'') for x in str.split('\n') if x)
+env = dict(os.environ)
+""" % sys.executable)
+
+ env_vars = sorted(list(env.keys()))
+ for name in env_vars:
+ val = env[name]
+ if string.find(name, 'PATH') < 0:
+ fout.write('env[%s] = %s\n' % (repr(name), repr(val)))
+ else:
+ if name == 'SPACK_TRANSITIVE_INCLUDE_PATH':
+ sep = ';'
+ else:
+ sep = ':'
+
+ fout.write(
+ 'env[%s] = "%s".join(cmdlist("""\n' % (repr(name), sep))
+ for part in string.split(val, sep):
+ fout.write(' %s\n' % part)
+ fout.write('"""))\n')
+
+ fout.write('\ncmd = cmdlist("""\n')
+ fout.write('%s\n' % cmd[0])
+ for arg in cmd[1:]:
+ fout.write(' %s\n' % arg)
+ fout.write('""") + sys.argv[1:]\n')
+ fout.write('\nproc = subprocess.Popen(cmd, env=env)\nproc.wait()\n')
+ set_executable(setup_fname)
+
def setup(self, args):
if not args.spec:
@@ -77,6 +150,12 @@ def setup(self, args):
spec.concretize()
package = spack.repo.get(spec)
+ if not isinstance(package, spack.CMakePackage):
+ tty.die(
+ 'Support for {0} derived packages not yet implemented'.format(
+ package.build_system_class
+ )
+ )
# It's OK if the package is already installed.
@@ -86,9 +165,26 @@ def setup(self, args):
# TODO: make this an argument, not a global.
spack.do_checksum = False
- package.do_install(
- keep_prefix=True, # Don't remove install directory
- ignore_deps=args.ignore_deps,
- verbose=args.verbose,
- keep_stage=True, # don't remove source dir for SETUP.
- install_phases=set(['setup', 'provenance']))
+ # Install dependencies if requested to do so
+ if not args.ignore_deps:
+ parser = argparse.ArgumentParser()
+ install.setup_parser(parser)
+ inst_args = copy.deepcopy(args)
+ inst_args = parser.parse_args(
+ ['--only=dependencies'] + args.spec,
+ namespace=inst_args
+ )
+ install.install(parser, inst_args)
+ # Generate spconfig.py
+ tty.msg(
+ 'Generating spconfig.py [{0}]'.format(package.spec.cshort_spec)
+ )
+ write_spconfig(package)
+ # Install this package to register it in the DB and permit
+ # module file regeneration
+ inst_args = copy.deepcopy(args)
+ inst_args = parser.parse_args(
+ ['--only=package', '--fake'] + args.spec,
+ namespace=inst_args
+ )
+ install.install(parser, inst_args)
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index 6e6d1c1277..0a6fb330ac 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -23,36 +23,57 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import argparse
-import spack.cmd
import spack
+import spack.cmd
+import spack.cmd.common.arguments as arguments
description = "print out abstract and concrete versions of a spec."
def setup_parser(subparser):
- subparser.add_argument('-i', '--ids', action='store_true',
- help="show numerical ids for dependencies.")
+ arguments.add_common_arguments(subparser, ['long', 'very_long'])
+ subparser.add_argument(
+ '-y', '--yaml', action='store_true', default=False,
+ help='Print concrete spec as YAML.')
+ subparser.add_argument(
+ '-c', '--cover', action='store',
+ default='nodes', choices=['nodes', 'edges', 'paths'],
+ help='How extensively to traverse the DAG. (default: nodes).')
+ subparser.add_argument(
+ '-I', '--install-status', action='store_true', default=False,
+ help='Show install status of packages. Packages can be: '
+ 'installed [+], missing and needed by an installed package [-], '
+ 'or not installed (no annotation).')
subparser.add_argument(
'specs', nargs=argparse.REMAINDER, help="specs of packages")
def spec(parser, args):
- kwargs = {'ids': args.ids,
- 'indent': 2,
- 'color': True}
+ kwargs = {'color': True,
+ 'cover': args.cover,
+ 'install_status': args.install_status,
+ 'hashes': args.long or args.very_long,
+ 'hashlen': None if args.very_long else 7}
for spec in spack.cmd.parse_specs(args.specs):
+ # With -y, just print YAML to output.
+ if args.yaml:
+ spec.concretize()
+ print spec.to_yaml()
+ continue
+
+ # Print some diagnostic info by default.
print "Input spec"
- print "------------------------------"
+ print "--------------------------------"
print spec.tree(**kwargs)
print "Normalized"
- print "------------------------------"
+ print "--------------------------------"
spec.normalize()
print spec.tree(**kwargs)
print "Concretized"
- print "------------------------------"
+ print "--------------------------------"
spec.concretize()
print spec.tree(**kwargs)
diff --git a/lib/spack/spack/cmd/test-install.py b/lib/spack/spack/cmd/test-install.py
deleted file mode 100644
index 8e7173e9a2..0000000000
--- a/lib/spack/spack/cmd/test-install.py
+++ /dev/null
@@ -1,245 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created 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 Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, 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 Lesser 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 argparse
-import codecs
-import os
-import time
-import xml.dom.minidom
-import xml.etree.ElementTree as ET
-
-import llnl.util.tty as tty
-import spack
-import spack.cmd
-from llnl.util.filesystem import *
-from spack.build_environment import InstallError
-from spack.fetch_strategy import FetchError
-
-description = "Run package install as a unit test, output formatted results."
-
-
-def setup_parser(subparser):
- subparser.add_argument(
- '-j', '--jobs', action='store', type=int,
- help="Explicitly set number of make jobs. Default is #cpus.")
-
- subparser.add_argument(
- '-n', '--no-checksum', action='store_true', dest='no_checksum',
- help="Do not check packages against checksum")
-
- subparser.add_argument(
- '-o', '--output', action='store',
- help="test output goes in this file")
-
- subparser.add_argument(
- 'package', nargs=argparse.REMAINDER,
- help="spec of package to install")
-
-
-class TestResult(object):
- PASSED = 0
- FAILED = 1
- SKIPPED = 2
- ERRORED = 3
-
-
-class TestSuite(object):
-
- def __init__(self, filename):
- self.filename = filename
- self.root = ET.Element('testsuite')
- self.tests = []
-
- def __enter__(self):
- return self
-
- def append(self, item):
- if not isinstance(item, TestCase):
- raise TypeError(
- 'only TestCase instances may be appended to TestSuite')
- self.tests.append(item) # Append the item to the list of tests
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- # Prepare the header for the entire test suite
- number_of_errors = sum(
- x.result_type == TestResult.ERRORED for x in self.tests)
- self.root.set('errors', str(number_of_errors))
- number_of_failures = sum(
- x.result_type == TestResult.FAILED for x in self.tests)
- self.root.set('failures', str(number_of_failures))
- self.root.set('tests', str(len(self.tests)))
-
- for item in self.tests:
- self.root.append(item.element)
-
- with open(self.filename, 'wb') as file:
- xml_string = ET.tostring(self.root)
- xml_string = xml.dom.minidom.parseString(xml_string).toprettyxml()
- file.write(xml_string)
-
-
-class TestCase(object):
-
- results = {
- TestResult.PASSED: None,
- TestResult.SKIPPED: 'skipped',
- TestResult.FAILED: 'failure',
- TestResult.ERRORED: 'error',
- }
-
- def __init__(self, classname, name, time=None):
- self.element = ET.Element('testcase')
- self.element.set('classname', str(classname))
- self.element.set('name', str(name))
- if time is not None:
- self.element.set('time', str(time))
- self.result_type = None
-
- def set_result(self, result_type,
- message=None, error_type=None, text=None):
- self.result_type = result_type
- result = TestCase.results[self.result_type]
- if result is not None and result is not TestResult.PASSED:
- subelement = ET.SubElement(self.element, result)
- if error_type is not None:
- subelement.set('type', error_type)
- if message is not None:
- subelement.set('message', str(message))
- if text is not None:
- subelement.text = text
-
-
-def fetch_log(path):
- if not os.path.exists(path):
- return list()
- with codecs.open(path, 'rb', 'utf-8') as F:
- return list(line.strip() for line in F.readlines())
-
-
-def failed_dependencies(spec):
- def get_deps(deptype):
- return set(item for item in spec.dependencies(deptype)
- if not spack.repo.get(item).installed)
- link_deps = get_deps('link')
- run_deps = get_deps('run')
- return link_deps.union(run_deps)
-
-
-def get_top_spec_or_die(args):
- specs = spack.cmd.parse_specs(args.package, concretize=True)
- if len(specs) > 1:
- tty.die("Only 1 top-level package can be specified")
- top_spec = iter(specs).next()
- return top_spec
-
-
-def install_single_spec(spec, number_of_jobs):
- package = spack.repo.get(spec)
-
- # If it is already installed, skip the test
- if spack.repo.get(spec).installed:
- testcase = TestCase(package.name, package.spec.short_spec, time=0.0)
- testcase.set_result(
- TestResult.SKIPPED,
- message='Skipped [already installed]',
- error_type='already_installed')
- return testcase
-
- # If it relies on dependencies that did not install, skip
- if failed_dependencies(spec):
- testcase = TestCase(package.name, package.spec.short_spec, time=0.0)
- testcase.set_result(
- TestResult.SKIPPED,
- message='Skipped [failed dependencies]',
- error_type='dep_failed')
- return testcase
-
- # Otherwise try to install the spec
- try:
- start_time = time.time()
- package.do_install(keep_prefix=False,
- keep_stage=True,
- ignore_deps=False,
- make_jobs=number_of_jobs,
- verbose=True,
- fake=False)
- duration = time.time() - start_time
- testcase = TestCase(package.name, package.spec.short_spec, duration)
- testcase.set_result(TestResult.PASSED)
- except InstallError:
- # An InstallError is considered a failure (the recipe didn't work
- # correctly)
- duration = time.time() - start_time
- # Try to get the log
- lines = fetch_log(package.build_log_path)
- text = '\n'.join(lines)
- testcase = TestCase(package.name, package.spec.short_spec, duration)
- testcase.set_result(TestResult.FAILED,
- message='Installation failure', text=text)
-
- except FetchError:
- # A FetchError is considered an error (we didn't even start building)
- duration = time.time() - start_time
- testcase = TestCase(package.name, package.spec.short_spec, duration)
- testcase.set_result(TestResult.ERRORED,
- message='Unable to fetch package')
-
- return testcase
-
-
-def get_filename(args, top_spec):
- if not args.output:
- fname = 'test-{x.name}-{x.version}-{hash}.xml'.format(
- x=top_spec, hash=top_spec.dag_hash())
- output_directory = join_path(os.getcwd(), 'test-output')
- if not os.path.exists(output_directory):
- os.mkdir(output_directory)
- output_filename = join_path(output_directory, fname)
- else:
- output_filename = args.output
- return output_filename
-
-
-def test_install(parser, args):
- # Check the input
- if not args.package:
- tty.die("install requires a package argument")
-
- if args.jobs is not None:
- if args.jobs <= 0:
- tty.die("The -j option must be a positive integer!")
-
- if args.no_checksum:
- spack.do_checksum = False # TODO: remove this global.
-
- # Get the one and only top spec
- top_spec = get_top_spec_or_die(args)
- # Get the filename of the test
- output_filename = get_filename(args, top_spec)
- # TEST SUITE
- with TestSuite(output_filename) as test_suite:
- # Traverse in post order : each spec is a test case
- for spec in top_spec.traverse(order='post'):
- test_case = install_single_spec(spec, args.jobs)
- test_suite.append(test_case)
diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py
index bf7342f606..52c2a06778 100644
--- a/lib/spack/spack/cmd/test.py
+++ b/lib/spack/spack/cmd/test.py
@@ -56,7 +56,7 @@ class MockCache(object):
def store(self, copyCmd, relativeDst):
pass
- def fetcher(self, targetPath, digest):
+ def fetcher(self, targetPath, digest, **kwargs):
return MockCacheFetcher()
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index 8957d1c908..2f7e15ba55 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -29,6 +29,7 @@ import argparse
import llnl.util.tty as tty
import spack
import spack.cmd
+import spack.store
import spack.repository
description = "Remove an installed package"
@@ -54,9 +55,10 @@ def setup_parser(subparser):
subparser.add_argument(
'-a', '--all', action='store_true', dest='all',
help="USE CAREFULLY. Remove ALL installed packages that match each "
- "supplied spec. i.e., if you say uninstall libelf, ALL versions "
- "of libelf are uninstalled. This is both useful and dangerous, "
- "like rm -r.")
+ "supplied spec. i.e., if you say uninstall `libelf`,"
+ " ALL versions of `libelf` are uninstalled. If no spec is "
+ "supplied all installed software will be uninstalled. This "
+ "is both useful and dangerous, like rm -r.")
subparser.add_argument(
'-d', '--dependents', action='store_true', dest='dependents',
@@ -88,7 +90,7 @@ def concretize_specs(specs, allow_multiple_matches=False, force=False):
specs_from_cli = []
has_errors = False
for spec in specs:
- matching = spack.installed_db.query(spec)
+ matching = spack.store.db.query(spec)
# For each spec provided, make sure it refers to only one package.
# Fail and ask user to be unambiguous if it doesn't
if not allow_multiple_matches and len(matching) > 1:
@@ -99,7 +101,7 @@ def concretize_specs(specs, allow_multiple_matches=False, force=False):
has_errors = True
# No installed package matches the query
- if len(matching) == 0 and not force:
+ if len(matching) == 0 and spec is not any:
tty.error("%s does not match any installed packages." % spec)
has_errors = True
@@ -157,44 +159,53 @@ def do_uninstall(specs, force):
item.do_uninstall(force=force)
-def uninstall(parser, args):
- if not args.packages:
- tty.die("uninstall requires at least one package argument.")
-
- with spack.installed_db.write_transaction():
+def get_uninstall_list(args):
+ specs = [any]
+ if args.packages:
specs = spack.cmd.parse_specs(args.packages)
- # Gets the list of installed specs that match the ones give via cli
- # takes care of '-a' is given in the cli
- uninstall_list = concretize_specs(specs, args.all, args.force)
- dependent_list = installed_dependents(
- uninstall_list) # takes care of '-d'
-
- # Process dependent_list and update uninstall_list
- has_error = False
- if dependent_list and not args.dependents and not args.force:
- for spec, lst in dependent_list.items():
- tty.error("Will not uninstall %s" %
- spec.format("$_$@$%@$#", color=True))
- print('')
- print("The following packages depend on it:")
- spack.cmd.display_specs(lst, **display_args)
- print('')
- has_error = True
- elif args.dependents:
- for key, lst in dependent_list.items():
- uninstall_list.extend(lst)
- uninstall_list = list(set(uninstall_list))
-
- if has_error:
- tty.die('You can use spack uninstall --dependents '
- 'to uninstall these dependencies as well')
-
- if not args.yes_to_all:
- tty.msg("The following packages will be uninstalled : ")
+ # Gets the list of installed specs that match the ones give via cli
+ # takes care of '-a' is given in the cli
+ uninstall_list = concretize_specs(specs, args.all, args.force)
+ # Takes care of '-d'
+ dependent_list = installed_dependents(uninstall_list)
+ # Process dependent_list and update uninstall_list
+ has_error = False
+ if dependent_list and not args.dependents and not args.force:
+ for spec, lst in dependent_list.items():
+ tty.error("Will not uninstall %s" %
+ spec.format("$_$@$%@$#", color=True))
print('')
- spack.cmd.display_specs(uninstall_list, **display_args)
+ print("The following packages depend on it:")
+ spack.cmd.display_specs(lst, **display_args)
print('')
- spack.cmd.ask_for_confirmation('Do you want to proceed ? ')
+ has_error = True
+ elif args.dependents:
+ for key, lst in dependent_list.items():
+ uninstall_list.extend(lst)
+ uninstall_list = list(set(uninstall_list))
+ if has_error:
+ tty.die('You can use spack uninstall --dependents '
+ 'to uninstall these dependencies as well')
+
+ return uninstall_list
+
+
+def uninstall(parser, args):
+ if not args.packages and not args.all:
+ tty.die("uninstall requires at least one package argument.")
+
+ uninstall_list = get_uninstall_list(args)
+
+ if not uninstall_list:
+ tty.msg("There are no package to uninstall.")
+ return
+
+ if not args.yes_to_all:
+ tty.msg("The following packages will be uninstalled : ")
+ print('')
+ spack.cmd.display_specs(uninstall_list, **display_args)
+ print('')
+ spack.cmd.ask_for_confirmation('Do you want to proceed ? ')
- # Uninstall everything on the list
- do_uninstall(uninstall_list, args.force)
+ # Uninstall everything on the list
+ do_uninstall(uninstall_list, args.force)
diff --git a/lib/spack/spack/cmd/url-parse.py b/lib/spack/spack/cmd/url_parse.py
index b8c7c95040..b8c7c95040 100644
--- a/lib/spack/spack/cmd/url-parse.py
+++ b/lib/spack/spack/cmd/url_parse.py
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
index a77991e4dc..5170872cf7 100644
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -113,10 +113,17 @@ class Compiler(object):
PrgEnv_compiler = None
def __init__(self, cspec, operating_system,
- paths, modules=[], alias=None, **kwargs):
+ paths, modules=[], alias=None, environment=None,
+ extra_rpaths=None, **kwargs):
+ self.operating_system = operating_system
+ self.spec = cspec
+ self.modules = modules
+ self.alias = alias
+
def check(exe):
if exe is None:
return None
+ exe = self._find_full_path(exe)
_verify_executables(exe)
return exe
@@ -129,6 +136,9 @@ class Compiler(object):
else:
self.fc = check(paths[3])
+ self.environment = environment
+ self.extra_rpaths = extra_rpaths or []
+
# Unfortunately have to make sure these params are accepted
# in the same order they are returned by sorted(flags)
# in compilers/__init__.py
@@ -138,11 +148,6 @@ class Compiler(object):
if value is not None:
self.flags[flag] = value.split()
- self.operating_system = operating_system
- self.spec = cspec
- self.modules = modules
- self.alias = alias
-
@property
def version(self):
return self.spec.version
@@ -269,6 +274,21 @@ class Compiler(object):
successful.reverse()
return dict(((v, p, s), path) for v, p, s, path in successful)
+ def _find_full_path(self, path):
+ """Return the actual path for a tool.
+
+ Some toolchains use forwarding executables (particularly Xcode-based
+ toolchains) which can be manipulated by external environment variables.
+ This method should be used to extract the actual path used for a tool
+ by finding out the end executable the forwarding executables end up
+ running.
+ """
+ return path
+
+ def setup_custom_environment(self, env):
+ """Set any environment variables necessary to use the compiler."""
+ pass
+
def __repr__(self):
"""Return a string representation of the compiler toolchain."""
return self.__str__()
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
index eb866c8bbb..5b049367cc 100644
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -26,7 +26,6 @@
system and configuring Spack to use multiple compilers.
"""
import imp
-import platform
from llnl.util.lang import list_modules
from llnl.util.filesystem import join_path
@@ -41,15 +40,10 @@ from spack.util.naming import mod_to_class
_imported_compilers_module = 'spack.compilers'
_path_instance_vars = ['cc', 'cxx', 'f77', 'fc']
-_other_instance_vars = ['modules', 'operating_system']
+_other_instance_vars = ['modules', 'operating_system', 'environment',
+ 'extra_rpaths']
_cache_config_file = []
-# TODO: customize order in config file
-if platform.system() == 'Darwin':
- _default_order = ['clang', 'gcc', 'intel']
-else:
- _default_order = ['gcc', 'intel', 'pgi', 'clang', 'xlc', 'nag']
-
def _auto_compiler_spec(function):
def converter(cspec_like, *args, **kwargs):
@@ -65,8 +59,11 @@ def _to_dict(compiler):
d['spec'] = str(compiler.spec)
d['paths'] = dict((attr, getattr(compiler, attr, None))
for attr in _path_instance_vars)
+ d['flags'] = dict((fname, fvals) for fname, fvals in compiler.flags)
d['operating_system'] = str(compiler.operating_system)
d['modules'] = compiler.modules if compiler.modules else []
+ d['environment'] = compiler.environment if compiler.environment else {}
+ d['extra_rpaths'] = compiler.extra_rpaths if compiler.extra_rpaths else []
if compiler.alias:
d['alias'] = compiler.alias
@@ -168,18 +165,6 @@ def all_compilers(scope=None, init_config=True):
for s in all_compilers_config(scope, init_config)]
-def default_compiler():
- versions = []
- for name in _default_order:
- versions = find(name)
- if versions:
- break
- else:
- raise NoCompilersError()
-
- return sorted(versions)[-1]
-
-
def find_compilers(*paths):
"""Return a list of compilers found in the suppied paths.
This invokes the find_compilers() method for each operating
@@ -212,7 +197,7 @@ def supported(compiler_spec):
@_auto_compiler_spec
def find(compiler_spec, scope=None):
"""Return specs of available compilers that match the supplied
- compiler spec. Return an list if nothing found."""
+ compiler spec. Return an empty list if nothing found."""
return [c for c in all_compilers(scope) if c.satisfies(compiler_spec)]
@@ -221,7 +206,7 @@ def compilers_for_spec(compiler_spec, scope=None, **kwargs):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""
- platform = kwargs.get("platform", None)
+ platform = kwargs.get('platform', None)
config = all_compilers_config(scope)
def get_compilers(cspec):
@@ -241,7 +226,7 @@ def compilers_for_spec(compiler_spec, scope=None, **kwargs):
compiler_paths = []
for c in _path_instance_vars:
compiler_path = items['paths'][c]
- if compiler_path != "None":
+ if compiler_path != 'None':
compiler_paths.append(compiler_path)
else:
compiler_paths.append(None)
@@ -250,21 +235,19 @@ def compilers_for_spec(compiler_spec, scope=None, **kwargs):
if mods == 'None':
mods = []
+ os = None
if 'operating_system' in items:
os = spack.architecture._operating_system_from_dict(
items['operating_system'], platform)
- else:
- os = None
-
- alias = items['alias'] if 'alias' in items else None
- flags = {}
- for f in spack.spec.FlagMap.valid_compiler_flags():
- if f in items:
- flags[f] = items[f]
+ alias = items.get('alias', None)
+ compiler_flags = items.get('flags', {})
+ environment = items.get('environment', {})
+ extra_rpaths = items.get('extra_rpaths', [])
compilers.append(
- cls(cspec, os, compiler_paths, mods, alias, **flags))
+ cls(cspec, os, compiler_paths, mods, alias, environment,
+ extra_rpaths, **compiler_flags))
return compilers
diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py
index 4cf65222ae..da18adcecd 100644
--- a/lib/spack/spack/compilers/clang.py
+++ b/lib/spack/spack/compilers/clang.py
@@ -23,11 +23,15 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import re
+import os
+import sys
+import spack
import spack.compiler as cpr
from spack.compiler import *
from spack.util.executable import *
import llnl.util.tty as tty
from spack.version import ver
+from shutil import copytree, ignore_patterns
class Clang(Compiler):
@@ -38,18 +42,18 @@ class Clang(Compiler):
cxx_names = ['clang++']
# Subclasses use possible names of Fortran 77 compiler
- f77_names = []
+ f77_names = ['gfortran']
# Subclasses use possible names of Fortran 90 compiler
- fc_names = []
+ fc_names = ['gfortran']
# Named wrapper links within spack.build_env_path
link_paths = {'cc': 'clang/clang',
'cxx': 'clang/clang++',
# Use default wrappers for fortran, in case provided in
# compilers.yaml
- 'f77': 'f77',
- 'fc': 'f90'}
+ 'f77': 'clang/gfortran',
+ 'fc': 'clang/gfortran'}
@property
def is_apple(self):
@@ -77,18 +81,17 @@ class Clang(Compiler):
@classmethod
def default_version(cls, comp):
"""The '--version' option works for clang compilers.
- On most platforms, output looks like this::
+ On most platforms, output looks like this::
- clang version 3.1 (trunk 149096)
- Target: x86_64-unknown-linux-gnu
- Thread model: posix
+ clang version 3.1 (trunk 149096)
+ Target: x86_64-unknown-linux-gnu
+ Thread model: posix
- On Mac OS X, it looks like this:
-
- Apple LLVM version 7.0.2 (clang-700.1.81)
- Target: x86_64-apple-darwin15.2.0
- Thread model: posix
+ On Mac OS X, it looks like this::
+ Apple LLVM version 7.0.2 (clang-700.1.81)
+ Target: x86_64-apple-darwin15.2.0
+ Thread model: posix
"""
if comp not in cpr._version_cache:
compiler = Executable(comp)
@@ -101,10 +104,108 @@ class Clang(Compiler):
ver = match.group(1) + '-apple'
else:
# Normal clang compiler versions are left as-is
- match = re.search(r'^clang version ([^ )]+)', output)
+ match = re.search(r'clang version ([^ )]+)', output)
if match:
ver = match.group(1)
cpr._version_cache[comp] = ver
return cpr._version_cache[comp]
+
+ def _find_full_path(self, path):
+ basename = os.path.basename(path)
+
+ if not self.is_apple or basename not in ('clang', 'clang++'):
+ return super(Clang, self)._find_full_path(path)
+
+ xcrun = Executable('xcrun')
+ full_path = xcrun('-f', basename, output=str)
+ return full_path.strip()
+
+ @classmethod
+ def fc_version(cls, fc):
+ version = get_compiler_version(
+ fc, '-dumpversion',
+ # older gfortran versions don't have simple dumpversion output.
+ r'(?:GNU Fortran \(GCC\))?(\d+\.\d+(?:\.\d+)?)')
+ # This is horribly ad hoc, we need to map from gcc/gfortran version
+ # to clang version, but there could be multiple clang
+ # versions that work for a single gcc/gfortran version
+ if sys.platform == 'darwin':
+ clangversionfromgcc = {'6.2.0': '8.0.0-apple'}
+ else:
+ clangversionfromgcc = {}
+ if version in clangversionfromgcc:
+ return clangversionfromgcc[version]
+ else:
+ return 'unknown'
+
+ @classmethod
+ def f77_version(cls, f77):
+ return cls.fc_version(f77)
+
+ def setup_custom_environment(self, env):
+ """Set the DEVELOPER_DIR environment for the Xcode toolchain.
+
+ On macOS, not all buildsystems support querying CC and CXX for the
+ compilers to use and instead query the Xcode toolchain for what
+ compiler to run. This side-steps the spack wrappers. In order to inject
+ spack into this setup, we need to copy (a subset of) Xcode.app and
+ replace the compiler executables with symlinks to the spack wrapper.
+ Currently, the stage is used to store the Xcode.app copies. We then set
+ the 'DEVELOPER_DIR' environment variables to cause the xcrun and
+ related tools to use this Xcode.app.
+ """
+ super(Clang, self).setup_custom_environment(env)
+
+ if not self.is_apple:
+ return
+
+ xcode_select = Executable('xcode-select')
+ real_root = xcode_select('--print-path', output=str).strip()
+ real_root = os.path.dirname(os.path.dirname(real_root))
+ developer_root = os.path.join(spack.stage_path,
+ 'xcode-select',
+ self.name,
+ str(self.version))
+ xcode_link = os.path.join(developer_root, 'Xcode.app')
+
+ if not os.path.exists(developer_root):
+ tty.warn('Copying Xcode from %s to %s in order to add spack '
+ 'wrappers to it. Please do not interrupt.'
+ % (real_root, developer_root))
+
+ # We need to make a new Xcode.app instance, but with symlinks to
+ # the spack wrappers for the compilers it ships. This is necessary
+ # because some projects insist on just asking xcrun and related
+ # tools where the compiler runs. These tools are very hard to trick
+ # as they do realpath and end up ignoring the symlinks in a
+ # "softer" tree of nothing but symlinks in the right places.
+ copytree(real_root, developer_root, symlinks=True,
+ ignore=ignore_patterns('AppleTV*.platform',
+ 'Watch*.platform',
+ 'iPhone*.platform',
+ 'Documentation',
+ 'swift*'))
+
+ real_dirs = [
+ 'Toolchains/XcodeDefault.xctoolchain/usr/bin',
+ 'usr/bin',
+ ]
+
+ bins = ['c++', 'c89', 'c99', 'cc', 'clang', 'clang++', 'cpp']
+
+ for real_dir in real_dirs:
+ dev_dir = os.path.join(developer_root,
+ 'Contents',
+ 'Developer',
+ real_dir)
+ for fname in os.listdir(dev_dir):
+ if fname in bins:
+ os.unlink(os.path.join(dev_dir, fname))
+ os.symlink(os.path.join(spack.build_env_path, 'cc'),
+ os.path.join(dev_dir, fname))
+
+ os.symlink(developer_root, xcode_link)
+
+ env.set('DEVELOPER_DIR', xcode_link)
diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py
index a556f346d7..557b1c13a9 100644
--- a/lib/spack/spack/compilers/gcc.py
+++ b/lib/spack/spack/compilers/gcc.py
@@ -41,8 +41,9 @@ class Gcc(Compiler):
fc_names = ['gfortran']
# MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
- # Homebrew and Linuxes may build gcc with -X, -X.Y suffixes
- suffixes = [r'-mp-\d\.\d', r'-\d\.\d', r'-\d']
+ # Homebrew and Linuxbrew may build gcc with -X, -X.Y suffixes.
+ # Old compatibility versions may contain XY suffixes.
+ suffixes = [r'-mp-\d\.\d', r'-\d\.\d', r'-\d', r'\d\d']
# Named wrapper links within spack.build_env_path
link_paths = {'cc': 'gcc/gcc',
@@ -83,3 +84,7 @@ class Gcc(Compiler):
@classmethod
def f77_version(cls, f77):
return cls.fc_version(f77)
+
+ @property
+ def stdcxx_libs(self):
+ return ('-lstdc++', )
diff --git a/lib/spack/spack/compilers/intel.py b/lib/spack/spack/compilers/intel.py
index 8531ecd19a..14f4d2dc91 100644
--- a/lib/spack/spack/compilers/intel.py
+++ b/lib/spack/spack/compilers/intel.py
@@ -68,15 +68,19 @@ class Intel(Compiler):
@classmethod
def default_version(cls, comp):
"""The '--version' option seems to be the most consistent one
- for intel compilers. Output looks like this::
+ for intel compilers. Output looks like this::
- icpc (ICC) 12.1.5 20120612
- Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
+ icpc (ICC) 12.1.5 20120612
+ Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
- or::
+ or::
- ifort (IFORT) 12.1.5 20120612
- Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
+ ifort (IFORT) 12.1.5 20120612
+ Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
"""
return get_compiler_version(
comp, '--version', r'\((?:IFORT|ICC)\) ([^ ]+)')
+
+ @property
+ def stdcxx_libs(self):
+ return ('-cxxlib', )
diff --git a/lib/spack/spack/compilers/nag.py b/lib/spack/spack/compilers/nag.py
index fdfc078b5e..5ba235adbb 100644
--- a/lib/spack/spack/compilers/nag.py
+++ b/lib/spack/spack/compilers/nag.py
@@ -61,19 +61,19 @@ class Nag(Compiler):
# options with '-Wl,-Wl,,'
@property
def f77_rpath_arg(self):
- return '-Wl,-Wl,,-rpath,'
+ return '-Wl,-Wl,,-rpath,,'
@property
def fc_rpath_arg(self):
- return '-Wl,-Wl,,-rpath,'
+ return '-Wl,-Wl,,-rpath,,'
@classmethod
def default_version(self, comp):
"""The '-V' option works for nag compilers.
- Output looks like this::
+ Output looks like this::
- NAG Fortran Compiler Release 6.0(Hibiya) Build 1037
- Product NPL6A60NA for x86-64 Linux
+ NAG Fortran Compiler Release 6.0(Hibiya) Build 1037
+ Product NPL6A60NA for x86-64 Linux
"""
return get_compiler_version(
comp, '-V', r'NAG Fortran Compiler Release ([0-9.]+)')
diff --git a/lib/spack/spack/compilers/pgi.py b/lib/spack/spack/compilers/pgi.py
index 0e4be6e9ba..37ffe44028 100644
--- a/lib/spack/spack/compilers/pgi.py
+++ b/lib/spack/spack/compilers/pgi.py
@@ -58,11 +58,11 @@ class Pgi(Compiler):
@classmethod
def default_version(cls, comp):
"""The '-V' option works for all the PGI compilers.
- Output looks like this::
+ Output looks like this::
- pgcc 15.10-0 64-bit target on x86-64 Linux -tp sandybridge
- The Portland Group - PGI Compilers and Tools
- Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
+ pgcc 15.10-0 64-bit target on x86-64 Linux -tp sandybridge
+ The Portland Group - PGI Compilers and Tools
+ Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
"""
return get_compiler_version(
comp, '-V', r'pg[^ ]* ([^ ]+) \d\d\d?-bit target')
diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py
index 726dee62e3..dcea147814 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -33,6 +33,7 @@ or user preferences.
TODO: make this customizable and allow users to configure
concretization policies.
"""
+from __future__ import print_function
import spack
import spack.spec
import spack.compilers
@@ -42,6 +43,7 @@ from spack.version import *
from functools import partial
from itertools import chain
from spack.config import *
+import spack.preferred_packages
class DefaultConcretizer(object):
@@ -93,7 +95,11 @@ class DefaultConcretizer(object):
not b.external and b.external_module):
# We're choosing between different providers, so
# maintain order from provider sort
- return candidates.index(a) - candidates.index(b)
+ index_of_a = next(i for i in range(0, len(candidates))
+ if a.satisfies(candidates[i]))
+ index_of_b = next(i for i in range(0, len(candidates))
+ if b.satisfies(candidates[i]))
+ return index_of_a - index_of_b
result = cmp_specs(a, b)
if result != 0:
@@ -160,23 +166,59 @@ class DefaultConcretizer(object):
# If there are known available versions, return the most recent
# version that satisfies the spec
pkg = spec.package
- 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)],
- cmp=cmp_versions)
- def prefer_key(v):
- return pkg.versions.get(Version(v)).get('preferred', False)
- valid_versions.sort(key=prefer_key, reverse=True)
+ # ---------- Produce prioritized list of versions
+ # Get list of preferences from packages.yaml
+ preferred = spack.pkgsort
+ # NOTE: spack.pkgsort == spack.preferred_packages.PreferredPackages()
+
+ yaml_specs = [
+ x[0] for x in
+ preferred._spec_for_pkgname(spec.name, 'version', None)]
+ n = len(yaml_specs)
+ yaml_index = dict(
+ [(spc, n - index) for index, spc in enumerate(yaml_specs)])
+
+ # List of versions we could consider, in sorted order
+ unsorted_versions = [
+ v for v in pkg.versions
+ if any(v.satisfies(sv) for sv in spec.versions)]
+
+ # The keys below show the order of precedence of factors used
+ # to select a version when concretizing. The item with
+ # the "largest" key will be selected.
+ #
+ # NOTE: When COMPARING VERSIONS, the '@develop' version is always
+ # larger than other versions. BUT when CONCRETIZING,
+ # the largest NON-develop version is selected by
+ # default.
+ keys = [(
+ # ------- Special direction from the user
+ # Respect order listed in packages.yaml
+ yaml_index.get(v, -1),
+
+ # The preferred=True flag (packages or packages.yaml or both?)
+ pkg.versions.get(Version(v)).get('preferred', False),
+
+ # ------- Regular case: use latest non-develop version by default.
+ # Avoid @develop version, which would otherwise be the "largest"
+ # in straight version comparisons
+ not v.isdevelop(),
+
+ # Compare the version itself
+ # This includes the logic:
+ # a) develop > everything (disabled by "not v.isdevelop() above)
+ # b) numeric > non-numeric
+ # c) Numeric or string comparison
+ v) for v in unsorted_versions]
+ keys.sort(reverse=True)
+
+ # List of versions in complete sorted order
+ valid_versions = [x[-1] for x in keys]
+ # --------------------------
if valid_versions:
- # Disregard @develop and take the next valid version
- if ver(valid_versions[0]) == ver('develop') and \
- len(valid_versions) > 1:
- spec.versions = ver([valid_versions[1]])
- else:
- spec.versions = ver([valid_versions[0]])
+ 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
@@ -255,7 +297,7 @@ class DefaultConcretizer(object):
spec.architecture = spack.architecture.Arch()
return True
- # Concretize the operating_system and target based of the spec
+ # Concretize the operating_system and target based of the spec
ret = any((self._concretize_platform(spec),
self._concretize_operating_system(spec),
self._concretize_target(spec)))
@@ -343,14 +385,15 @@ class DefaultConcretizer(object):
arch.platform_os)
# copy concrete version into other_compiler
- index = 0
- while not _proper_compiler_style(matches[index], spec.architecture):
- index += 1
- if index == len(matches) - 1:
- arch = spec.architecture
- raise UnavailableCompilerVersionError(spec.compiler,
- arch.platform_os)
- spec.compiler = matches[index].copy()
+ try:
+ spec.compiler = next(
+ c for c in matches
+ if _proper_compiler_style(c, spec.architecture)).copy()
+ except StopIteration:
+ raise UnavailableCompilerVersionError(
+ spec.compiler, spec.architecture.platform_os
+ )
+
assert(spec.compiler.concrete)
return True # things changed.
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index c90eff4229..989b3da169 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -24,99 +24,30 @@
##############################################################################
"""This module implements Spack's configuration file handling.
-Configuration file scopes
-===============================
-
-When Spack runs, it pulls configuration data from several config
-directories, each of which contains configuration files. In Spack,
-there are two configuration scopes:
-
- 1. ``site``: Spack loads site-wide configuration options from
- ``$(prefix)/etc/spack/``.
-
- 2. ``user``: Spack next loads per-user configuration options from
- ~/.spack/.
-
-Spack may read configuration files from both of these locations. When
-configurations conflict, the user config options take precedence over
-the site configurations. Each configuration directory may contain
-several configuration files, such as compilers.yaml or mirrors.yaml.
-
-Configuration file format
-===============================
-
-Configuration files are formatted using YAML syntax. This format is
-implemented by libyaml (included with Spack as an external module),
-and it's easy to read and versatile.
-
-Config files are structured as trees, like this ``compiler`` section::
-
- compilers:
- chaos_5_x86_64_ib:
- gcc@4.4.7:
- cc: /usr/bin/gcc
- cxx: /usr/bin/g++
- f77: /usr/bin/gfortran
- fc: /usr/bin/gfortran
- bgqos_0:
- xlc@12.1:
- cc: /usr/local/bin/mpixlc
- ...
-
-In this example, entries like ''compilers'' and ''xlc@12.1'' are used to
-categorize entries beneath them in the tree. At the root of the tree,
-entries like ''cc'' and ''cxx'' are specified as name/value pairs.
-
-``config.get_config()`` returns these trees as nested dicts, but it
-strips the first level off. So, ``config.get_config('compilers')``
-would return something like this for the above example:
-
- { 'chaos_5_x86_64_ib' :
- { 'gcc@4.4.7' :
- { 'cc' : '/usr/bin/gcc',
- 'cxx' : '/usr/bin/g++'
- 'f77' : '/usr/bin/gfortran'
- 'fc' : '/usr/bin/gfortran' }
- }
- { 'bgqos_0' :
- { 'cc' : '/usr/local/bin/mpixlc' } }
-
-Likewise, the ``mirrors.yaml`` file's first line must be ``mirrors:``,
-but ``get_config()`` strips that off too.
-
-Precedence
-===============================
-
-``config.py`` routines attempt to recursively merge configuration
-across scopes. So if there are ``compilers.py`` files in both the
-site scope and the user scope, ``get_config('compilers')`` will return
-merged dictionaries of *all* the compilers available. If a user
-compiler conflicts with a site compiler, Spack will overwrite the site
-configuration wtih the user configuration. If both the user and site
-``mirrors.yaml`` files contain lists of mirrors, then ``get_config()``
-will return a concatenated list of mirrors, with the user config items
-first.
-
-Sometimes, it is useful to *completely* override a site setting with a
-user one. To accomplish this, you can use *two* colons at the end of
-a key in a configuration file. For example, this:
-
- compilers::
- chaos_5_x86_64_ib:
- gcc@4.4.7:
- cc: /usr/bin/gcc
- cxx: /usr/bin/g++
- f77: /usr/bin/gfortran
- fc: /usr/bin/gfortran
- bgqos_0:
- xlc@12.1:
- cc: /usr/local/bin/mpixlc
- ...
-
-Will make Spack take compilers *only* from the user configuration, and
-the site configuration will be ignored.
+This implements Spack's configuration system, which handles merging
+multiple scopes with different levels of precedence. See the
+documentation on :ref:`configuration-scopes` for details on how Spack's
+configuration system behaves. The scopes are:
+
+ #. ``default``
+ #. ``site``
+ #. ``user``
+
+And corresponding :ref:`per-platform scopes <platform-scopes>`. Important
+functions in this module are:
+
+* :py:func:`get_config`
+* :py:func:`update_config`
+
+``get_config`` reads in YAML data for a particular scope and returns
+it. Callers can then modify the data and write it back with
+``update_config``.
+
+When read in, Spack validates configurations with jsonschemas. The
+schemas are in submodules of :py:mod:`spack.schema`.
"""
+
import copy
import os
import re
@@ -132,12 +63,13 @@ import llnl.util.tty as tty
from llnl.util.filesystem import mkdirp
import spack
+import spack.architecture
from spack.error import SpackError
import spack.schema
# Hacked yaml for configuration files preserves line numbers.
import spack.util.spack_yaml as syaml
-from spack.build_environment import get_path_from_module
+
"""Dict from section names -> schema for that section."""
section_schemas = {
@@ -145,8 +77,8 @@ section_schemas = {
'mirrors': spack.schema.mirrors.schema,
'repos': spack.schema.repos.schema,
'packages': spack.schema.packages.schema,
- 'targets': spack.schema.targets.schema,
'modules': spack.schema.modules.schema,
+ 'config': spack.schema.config.schema,
}
"""OrderedDict of config scopes keyed by name.
@@ -264,16 +196,33 @@ class ConfigScope(object):
"""Empty cached config information."""
self.sections = {}
+ def __repr__(self):
+ return '<ConfigScope: %s: %s>' % (self.name, self.path)
+
+#
+# Below are configuration scopes.
+#
+# Each scope can have per-platfom overrides in subdirectories of the
+# configuration directory.
+#
+_platform = spack.architecture.platform().name
+
"""Default configuration scope is the lowest-level scope. These are
versioned with Spack and can be overridden by sites or users."""
-ConfigScope('defaults', os.path.join(spack.etc_path, 'spack', 'defaults'))
+_defaults_path = os.path.join(spack.etc_path, 'spack', 'defaults')
+ConfigScope('defaults', _defaults_path)
+ConfigScope('defaults/%s' % _platform, os.path.join(_defaults_path, _platform))
"""Site configuration is per spack instance, for sites or projects.
No site-level configs should be checked into spack by default."""
-ConfigScope('site', os.path.join(spack.etc_path, 'spack'))
+_site_path = os.path.join(spack.etc_path, 'spack')
+ConfigScope('site', _site_path)
+ConfigScope('site/%s' % _platform, os.path.join(_site_path, _platform))
"""User configuration can override both spack defaults and site config."""
-ConfigScope('user', spack.user_config_path)
+_user_path = spack.user_config_path
+ConfigScope('user', _user_path)
+ConfigScope('user/%s' % _platform, os.path.join(_user_path, _platform))
def highest_precedence_scope():
@@ -317,7 +266,7 @@ def _read_config_file(filename, schema):
try:
tty.debug("Reading config file %s" % filename)
with open(filename) as f:
- data = syaml.load(f)
+ data = _mark_overrides(syaml.load(f))
if data:
validate_section(data, schema)
@@ -339,6 +288,34 @@ def clear_config_caches():
scope.clear()
+def override(string):
+ """Test if a spack YAML string is an override.
+
+ See ``spack_yaml`` for details. Keys in Spack YAML can end in `::`,
+ and if they do, their values completely replace lower-precedence
+ configs instead of merging into them.
+
+ """
+ return hasattr(string, 'override') and string.override
+
+
+def _mark_overrides(data):
+ if isinstance(data, list):
+ return [_mark_overrides(elt) for elt in data]
+
+ elif isinstance(data, dict):
+ marked = {}
+ for key, val in data.iteritems():
+ if isinstance(key, basestring) and key.endswith(':'):
+ key = syaml.syaml_str(key[:-1])
+ key.override = True
+ marked[key] = _mark_overrides(val)
+ return marked
+
+ else:
+ return data
+
+
def _merge_yaml(dest, source):
"""Merges source into dest; entries in source take precedence over dest.
@@ -371,9 +348,11 @@ def _merge_yaml(dest, source):
# Source dict is merged into dest.
elif they_are(dict):
for sk, sv in source.iteritems():
- if sk not in dest:
+ if override(sk) or sk not in dest:
+ # if sk ended with ::, or if it's new, completely override
dest[sk] = copy.copy(sv)
else:
+ # otherwise, merge the YAML
dest[sk] = _merge_yaml(dest[sk], source[sk])
return dest
@@ -385,7 +364,26 @@ def _merge_yaml(dest, source):
def get_config(section, scope=None):
"""Get configuration settings for a section.
- Strips off the top-level section name from the YAML dict.
+ If ``scope`` is ``None`` or not provided, return the merged contents
+ of all of Spack's configuration scopes. If ``scope`` is provided,
+ return only the confiugration as specified in that scope.
+
+ This off the top-level name from the YAML section. That is, for a
+ YAML config file that looks like this::
+
+ config:
+ install_tree: $spack/opt/spack
+ module_roots:
+ lmod: $spack/share/spack/lmod
+
+ ``get_config('config')`` will return::
+
+ { 'install_tree': '$spack/opt/spack',
+ 'module_roots: {
+ 'lmod': '$spack/share/spack/lmod'
+ }
+ }
+
"""
validate_section_name(section)
merged_section = syaml.syaml_dict()
@@ -403,18 +401,18 @@ def get_config(section, scope=None):
if not data or not isinstance(data, dict):
continue
- # Allow complete override of site config with '<section>::'
- override_key = section + ':'
- if not (section in data or override_key in data):
+ if section not in data:
tty.warn("Skipping bad configuration file: '%s'" % scope.path)
continue
- if override_key in data:
- merged_section = data[override_key]
- else:
- merged_section = _merge_yaml(merged_section, data[section])
+ merged_section = _merge_yaml(merged_section, data)
+
+ # no config files -- empty config.
+ if section not in merged_section:
+ return {}
- return merged_section
+ # take the top key off before returning.
+ return merged_section[section]
def get_config_filename(scope, section):
@@ -463,6 +461,9 @@ def print_section(section):
def spec_externals(spec):
"""Return a list of external specs (with external directory path filled in),
one for each known external installation."""
+ # break circular import.
+ from spack.build_environment import get_path_from_module
+
allpkgs = get_config('packages')
name = spec.name
diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py
index f3dcdef0a9..e8902ec024 100644
--- a/lib/spack/spack/database.py
+++ b/lib/spack/spack/database.py
@@ -33,7 +33,7 @@ The database serves two purposes:
2. It will allow us to track external installations as well as lost
packages and their dependencies.
-Prior ot the implementation of this store, a direcotry layout served
+Prior to the implementation of this store, a directory layout served
as the authoritative database of packages in Spack. This module
provides a cache and a sanity checking mechanism for what is in the
filesystem.
@@ -42,18 +42,19 @@ filesystem.
import os
import socket
-import yaml
from yaml.error import MarkedYAMLError, YAMLError
import llnl.util.tty as tty
from llnl.util.filesystem import *
from llnl.util.lock import *
-import spack.spec
+import spack.store
+import spack.repository
+from spack.directory_layout import DirectoryLayoutError
from spack.version import Version
-from spack.spec import Spec
+import spack.spec
from spack.error import SpackError
-from spack.repository import UnknownPackageError
+import spack.util.spack_yaml as syaml
# DB goes in this directory underneath the root
@@ -65,6 +66,9 @@ _db_version = Version('0.9.2')
# Default timeout for spack database locks is 5 min.
_db_lock_timeout = 60
+# Types of dependencies tracked by the database
+_tracked_deps = 'nobuild'
+
def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
@@ -153,17 +157,20 @@ class Database(object):
self._index_path = join_path(self._db_dir, 'index.yaml')
self._lock_path = join_path(self._db_dir, 'lock')
+ # This is for other classes to use to lock prefix directories.
+ self.prefix_lock_path = join_path(self._db_dir, 'prefix_lock')
+
# Create needed directories and files
if not os.path.exists(self._db_dir):
mkdirp(self._db_dir)
- if not os.path.exists(self._lock_path):
- touch(self._lock_path)
-
# initialize rest of state.
self.lock = Lock(self._lock_path)
self._data = {}
+ # whether there was an error at the start of a read transaction
+ self._error = None
+
def write_transaction(self, timeout=_db_lock_timeout):
"""Get a write lock context manager for use in a `with` block."""
return WriteTransaction(self.lock, self._read, self._write, timeout)
@@ -194,11 +201,12 @@ class Database(object):
}
try:
- return yaml.dump(database, stream=stream, default_flow_style=False)
+ return syaml.dump(
+ database, stream=stream, default_flow_style=False)
except YAMLError as e:
raise SpackYAMLError("error writing YAML database:", str(e))
- def _read_spec_from_yaml(self, hash_key, installs, parent_key=None):
+ def _read_spec_from_yaml(self, hash_key, installs):
"""Recursively construct a spec from a hash in a YAML database.
Does not do any locking.
@@ -211,20 +219,27 @@ class Database(object):
spec_dict[name]['hash'] = hash_key
# Build spec from dict first.
- spec = Spec.from_node_dict(spec_dict)
+ spec = spack.spec.Spec.from_node_dict(spec_dict)
+ return spec
+ def _assign_dependencies(self, hash_key, installs, data):
# Add dependencies from other records in the install DB to
# form a full spec.
+ spec = data[hash_key].spec
+ spec_dict = installs[hash_key]['spec']
+
if 'dependencies' in spec_dict[spec.name]:
yaml_deps = spec_dict[spec.name]['dependencies']
- for dname, dhash, dtypes in Spec.read_yaml_dep_specs(yaml_deps):
- child = self._read_spec_from_yaml(dhash, installs, hash_key)
- spec._add_dependency(child, dtypes)
+ for dname, dhash, dtypes in spack.spec.Spec.read_yaml_dep_specs(
+ yaml_deps):
+ if dhash not in data:
+ tty.warn("Missing dependency not in database: ",
+ "%s needs %s-%s" % (
+ spec.format('$_$#'), dname, dhash[:7]))
+ continue
- # Specs from the database need to be marked concrete because
- # they represent actual installations.
- spec._mark_concrete()
- return spec
+ child = data[dhash].spec
+ spec._add_dependency(child, dtypes)
def _read_from_yaml(self, stream):
"""
@@ -236,9 +251,9 @@ class Database(object):
try:
if isinstance(stream, basestring):
with open(stream, 'r') as f:
- yfile = yaml.load(f)
+ yfile = syaml.load(f)
else:
- yfile = yaml.load(stream)
+ yfile = syaml.load(stream)
except MarkedYAMLError as e:
raise SpackYAMLError("error parsing YAML database:", str(e))
@@ -248,7 +263,8 @@ class Database(object):
def check(cond, msg):
if not cond:
- raise CorruptDatabaseError(self._index_path, msg)
+ raise CorruptDatabaseError(
+ "Spack database is corrupt: %s" % msg, self._index_path)
check('database' in yfile, "No 'database' attribute in YAML.")
@@ -264,25 +280,31 @@ class Database(object):
if version > _db_version:
raise InvalidDatabaseVersionError(_db_version, version)
elif version < _db_version:
- self.reindex(spack.install_layout)
+ self.reindex(spack.store.layout)
installs = dict((k, v.to_dict()) for k, v in self._data.items())
- # Iterate through database and check each record.
+ def invalid_record(hash_key, error):
+ msg = ("Invalid record in Spack database: "
+ "hash: %s, cause: %s: %s")
+ msg %= (hash_key, type(e).__name__, str(e))
+ raise CorruptDatabaseError(msg, self._index_path)
+
+ # Build up the database in three passes:
+ #
+ # 1. Read in all specs without dependencies.
+ # 2. Hook dependencies up among specs.
+ # 3. Mark all specs concrete.
+ #
+ # The database is built up so that ALL specs in it share nodes
+ # (i.e., its specs are a true Merkle DAG, unlike most specs.)
+
+ # Pass 1: Iterate through database and build specs w/o dependencies
data = {}
for hash_key, rec in installs.items():
try:
# This constructs a spec DAG from the list of all installs
spec = self._read_spec_from_yaml(hash_key, installs)
- # Validate the spec by ensuring the stored and actual
- # hashes are the same.
- spec_hash = spec.dag_hash()
- if not spec_hash == hash_key:
- tty.warn(
- "Hash mismatch in database: %s -> spec with hash %s" %
- (hash_key, spec_hash))
- continue # TODO: is skipping the right thing to do?
-
# Insert the brand new spec in the database. Each
# spec has its own copies of its dependency specs.
# TODO: would a more immmutable spec implementation simplify
@@ -290,34 +312,66 @@ class Database(object):
data[hash_key] = InstallRecord.from_dict(spec, rec)
except Exception as e:
- tty.warn("Invalid database reecord:",
- "file: %s" % self._index_path,
- "hash: %s" % hash_key,
- "cause: %s: %s" % (type(e).__name__, str(e)))
- raise
+ invalid_record(hash_key, e)
+
+ # Pass 2: Assign dependencies once all specs are created.
+ for hash_key in data:
+ try:
+ self._assign_dependencies(hash_key, installs, data)
+ except Exception as e:
+ invalid_record(hash_key, e)
+
+ # Pass 3: Mark all specs concrete. Specs representing real
+ # installations must be explicitly marked.
+ # We do this *after* all dependencies are connected because if we
+ # do it *while* we're constructing specs,it causes hashes to be
+ # cached prematurely.
+ for hash_key, rec in data.items():
+ rec.spec._mark_concrete()
self._data = data
def reindex(self, directory_layout):
- """Build database index from scratch based from a directory layout.
+ """Build database index from scratch based on a directory layout.
Locks the DB if it isn't locked already.
"""
- with self.write_transaction():
+ # Special transaction to avoid recursive reindex calls and to
+ # ignore errors if we need to rebuild a corrupt database.
+ def _read_suppress_error():
+ try:
+ if os.path.isfile(self._index_path):
+ self._read_from_yaml(self._index_path)
+ except CorruptDatabaseError as e:
+ self._error = e
+ self._data = {}
+
+ transaction = WriteTransaction(
+ self.lock, _read_suppress_error, self._write, _db_lock_timeout)
+
+ with transaction:
+ if self._error:
+ tty.warn(
+ "Spack database was corrupt. Will rebuild. Error was:",
+ str(self._error))
+ self._error = None
+
old_data = self._data
try:
self._data = {}
# Ask the directory layout to traverse the filesystem.
for spec in directory_layout.all_specs():
- # Create a spec for each known package and add it.
- path = directory_layout.path_for_spec(spec)
- old_info = old_data.get(spec.dag_hash())
+ # Try to recover explicit value from old DB, but
+ # default it to False if DB was corrupt.
explicit = False
- if old_info is not None:
- explicit = old_info.explicit
- self._add(spec, path, directory_layout, explicit=explicit)
+ if old_data is not None:
+ old_info = old_data.get(spec.dag_hash())
+ if old_info is not None:
+ explicit = old_info.explicit
+
+ self._add(spec, directory_layout, explicit=explicit)
self._check_ref_counts()
@@ -336,10 +390,7 @@ class Database(object):
counts = {}
for key, rec in self._data.items():
counts.setdefault(key, 0)
- # XXX(deptype): This checks all dependencies, but build
- # dependencies might be able to be dropped in the
- # future.
- for dep in rec.spec.dependencies():
+ for dep in rec.spec.dependencies(_tracked_deps):
dep_key = dep.dag_hash()
counts.setdefault(dep_key, 0)
counts[dep_key] += 1
@@ -395,54 +446,64 @@ class Database(object):
else:
# The file doesn't exist, try to traverse the directory.
# reindex() takes its own write lock, so no lock here.
- self.reindex(spack.install_layout)
+ self.reindex(spack.store.layout)
- def _add(self, spec, path, directory_layout=None, explicit=False):
- """Add an install record for spec at path to the database.
+ def _add(self, spec, directory_layout=None, explicit=False):
+ """Add an install record for this spec to the database.
- This assumes that the spec is not already installed. It
- updates the ref counts on dependencies of the spec in the DB.
+ Assumes spec is installed in ``layout.path_for_spec(spec)``.
- This operation is in-memory, and does not lock the DB.
+ Also ensures dependencies are present and updated in the DB as
+ either intsalled or missing.
"""
- key = spec.dag_hash()
- if key in self._data:
- rec = self._data[key]
- rec.installed = True
-
- # TODO: this overwrites a previous install path (when path !=
- # self._data[key].path), and the old path still has a
- # dependent in the DB. We could consider re-RPATH-ing the
- # dependents. This case is probably infrequent and may not be
- # worth fixing, but this is where we can discover it.
- rec.path = path
+ if not spec.concrete:
+ raise NonConcreteSpecAddError(
+ "Specs added to DB must be concrete.")
- else:
- self._data[key] = InstallRecord(spec, path, True,
- explicit=explicit)
- for dep in spec.dependencies(('link', 'run')):
- self._increment_ref_count(dep, directory_layout)
+ for dep in spec.dependencies(_tracked_deps):
+ dkey = dep.dag_hash()
+ if dkey not in self._data:
+ self._add(dep, directory_layout, explicit=False)
- def _increment_ref_count(self, spec, directory_layout=None):
- """Recursively examine dependencies and update their DB entries."""
key = spec.dag_hash()
if key not in self._data:
installed = False
path = None
- if directory_layout:
+ if not spec.external and directory_layout:
path = directory_layout.path_for_spec(spec)
- installed = os.path.isdir(path)
+ try:
+ directory_layout.check_installed(spec)
+ installed = True
+ except DirectoryLayoutError as e:
+ tty.warn(
+ 'Dependency missing due to corrupt install directory:',
+ path, str(e))
- self._data[key] = InstallRecord(spec.copy(), path, installed)
+ # Create a new install record with no deps initially.
+ new_spec = spec.copy(deps=False)
+ self._data[key] = InstallRecord(
+ new_spec, path, installed, ref_count=0, explicit=explicit)
- for dep in spec.dependencies('link'):
- self._increment_ref_count(dep)
+ # Connect dependencies from the DB to the new copy.
+ for name, dep in spec.dependencies_dict(_tracked_deps).iteritems():
+ dkey = dep.spec.dag_hash()
+ new_spec._add_dependency(self._data[dkey].spec, dep.deptypes)
+ self._data[dkey].ref_count += 1
- self._data[key].ref_count += 1
+ # Mark concrete once everything is built, and preserve
+ # the original hash of concrete specs.
+ new_spec._mark_concrete()
+ new_spec._hash = key
+
+ else:
+ # If it is already there, mark it as installed.
+ self._data[key].installed = True
+
+ self._data[key].explicit = explicit
@_autospec
- def add(self, spec, path, explicit=False):
+ def add(self, spec, directory_layout, explicit=False):
"""Add spec at path to database, locking and reading DB to sync.
``add()`` will lock and read from the DB on disk.
@@ -451,7 +512,7 @@ class Database(object):
# TODO: ensure that spec is concrete?
# Entire add is transactional.
with self.write_transaction():
- self._add(spec, path, explicit=explicit)
+ self._add(spec, directory_layout, explicit=explicit)
def _get_matching_spec_key(self, spec, **kwargs):
"""Get the exact spec OR get a single spec that matches."""
@@ -481,7 +542,7 @@ class Database(object):
if rec.ref_count == 0 and not rec.installed:
del self._data[key]
- for dep in spec.dependencies('link'):
+ for dep in spec.dependencies(_tracked_deps):
self._decrement_ref_count(dep)
def _remove(self, spec):
@@ -495,7 +556,7 @@ class Database(object):
return rec.spec
del self._data[key]
- for dep in rec.spec.dependencies('link'):
+ for dep in rec.spec.dependencies(_tracked_deps):
self._decrement_ref_count(dep)
# Returns the concrete spec so we know it in the case where a
@@ -528,7 +589,7 @@ class Database(object):
try:
if s.package.extends(extendee_spec):
yield s.package
- except UnknownPackageError:
+ except spack.repository.UnknownPackageError:
continue
# skips unknown packages
# TODO: conditional way to do this instead of catching exceptions
@@ -601,11 +662,11 @@ class Database(object):
class CorruptDatabaseError(SpackError):
+ """Raised when errors are found while reading the database."""
+
- def __init__(self, path, msg=''):
- super(CorruptDatabaseError, self).__init__(
- "Spack database is corrupt: %s. %s." % (path, msg),
- "Try running `spack reindex` to fix.")
+class NonConcreteSpecAddError(SpackError):
+ """Raised when attemptint to add non-concrete spec to DB."""
class InvalidDatabaseVersionError(SpackError):
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 313bf48f0d..9cf00334a8 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -212,7 +212,10 @@ def _depends_on(pkg, spec, when=None, type=None):
@directive(('dependencies', '_deptypes'))
def depends_on(pkg, spec, when=None, type=None):
- """Creates a dict of deps with specs defining when they apply."""
+ """Creates a dict of deps with specs defining when they apply.
+ This directive is to be used inside a Package definition to declare
+ that the package requires other packages to be built first.
+ @see The section "Dependency specs" in the Spack Packaging Guide."""
_depends_on(pkg, spec, when=when, type=type)
@@ -256,7 +259,7 @@ def provides(pkg, *specs, **kwargs):
@directive('patches')
-def patch(pkg, url_or_filename, level=1, when=None):
+def patch(pkg, url_or_filename, level=1, when=None, **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
@@ -268,7 +271,7 @@ def patch(pkg, url_or_filename, level=1, when=None):
cur_patches = pkg.patches.setdefault(when_spec, [])
# if this spec is identical to some other, then append this
# patch to the existing list.
- cur_patches.append(Patch(pkg, url_or_filename, level))
+ cur_patches.append(Patch.create(pkg, url_or_filename, level, **kwargs))
@directive('variants')
@@ -288,21 +291,20 @@ def variant(pkg, name, default=False, description=""):
@directive('resources')
def resource(pkg, **kwargs):
- """
- Define an external resource to be fetched and staged when building the
+ """Define an external resource to be fetched and staged when building the
package. Based on the keywords present in the dictionary the appropriate
FetchStrategy will be used for the resource. Resources are fetched and
- staged in their own folder inside spack stage area, and then linked into
+ staged in their own folder inside spack stage area, and then moved into
the stage area of the package that needs them.
List of recognized keywords:
* 'when' : (optional) represents the condition upon which the resource is
- needed
- * 'destination' : (optional) path where to link the resource. This path
- must be relative to the main package stage area.
+ needed
+ * 'destination' : (optional) path where to move the resource. This path
+ must be relative to the main package stage area.
* 'placement' : (optional) gives the possibility to fine tune how the
- resource is linked into the main package stage area.
+ resource is moved into the main package stage area.
"""
when = kwargs.get('when', pkg.name)
destination = kwargs.get('destination', "")
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 0ae6f765f4..28e6584fb2 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -32,7 +32,7 @@ import yaml
from llnl.util.filesystem import join_path, mkdirp
import spack
-from spack.spec import Spec
+import spack.spec
from spack.error import SpackError
@@ -210,7 +210,7 @@ class YamlDirectoryLayout(DirectoryLayout):
"""Read the contents of a file and parse them as a spec"""
try:
with open(path) as f:
- spec = Spec.from_yaml(f)
+ spec = spack.spec.Spec.from_yaml(f)
except Exception as e:
if spack.debug:
raise
@@ -268,6 +268,13 @@ class YamlDirectoryLayout(DirectoryLayout):
if installed_spec == spec:
return path
+ # DAG hashes currently do not include build dependencies.
+ #
+ # TODO: remove this when we do better concretization and don't
+ # ignore build-only deps in hashes.
+ elif installed_spec == spec.copy(deps=('link', 'run')):
+ return path
+
if spec.dag_hash() == installed_spec.dag_hash():
raise SpecHashCollisionError(spec, installed_spec)
else:
@@ -416,7 +423,7 @@ class RemoveFailedError(DirectoryLayoutError):
def __init__(self, installed_spec, prefix, error):
super(RemoveFailedError, self).__init__(
'Could not remove prefix %s for %s : %s'
- % prefix, installed_spec.short_spec, error)
+ % (prefix, installed_spec.short_spec, error))
self.cause = error
diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py
index 613ece2f45..de30a9c7be 100644
--- a/lib/spack/spack/environment.py
+++ b/lib/spack/spack/environment.py
@@ -261,21 +261,22 @@ class EnvironmentModifications(object):
@staticmethod
def from_sourcing_files(*args, **kwargs):
- """
- Creates an instance of EnvironmentModifications that, if executed,
+ """Creates an instance of EnvironmentModifications that, if executed,
has the same effect on the environment as sourcing the files passed as
parameters
- Args:
- *args: list of files to be sourced
-
- Returns:
- instance of EnvironmentModifications
+ :param \*args: list of files to be sourced
+ :rtype: instance of EnvironmentModifications
"""
+
env = EnvironmentModifications()
# Check if the files are actually there
- if not all(os.path.isfile(file) for file in args):
- raise RuntimeError('trying to source non-existing files')
+ files = [line.split(' ')[0] for line in args]
+ non_existing = [file for file in files if not os.path.isfile(file)]
+ if non_existing:
+ message = 'trying to source non-existing files\n'
+ message += '\n'.join(non_existing)
+ raise RuntimeError(message)
# Relevant kwd parameters and formats
info = dict(kwargs)
info.setdefault('shell', '/bin/bash')
diff --git a/lib/spack/spack/error.py b/lib/spack/spack/error.py
index c94875e91a..b6261a05f4 100644
--- a/lib/spack/spack/error.py
+++ b/lib/spack/spack/error.py
@@ -26,6 +26,7 @@ import os
import sys
import llnl.util.tty as tty
import spack
+import inspect
class SpackError(Exception):
@@ -38,19 +39,31 @@ class SpackError(Exception):
self.message = message
self._long_message = long_message
+ # for exceptions raised from child build processes, we save the
+ # traceback as a string and print it in the parent.
+ self.traceback = None
+
@property
def long_message(self):
return self._long_message
def die(self):
+ # basic debug message
+ tty.error(self.message)
+ if self.long_message:
+ print(self.long_message)
+
+ # stack trace, etc. in debug mode.
if spack.debug:
- sys.excepthook(*sys.exc_info())
- os._exit(1)
- else:
- tty.error(self.message)
- if self.long_message:
- print self.long_message
- os._exit(1)
+ if self.traceback:
+ # exception came from a build child, already got
+ # traceback in child, so print it.
+ sys.stderr.write(self.traceback)
+ else:
+ # run parent exception hook.
+ sys.excepthook(*sys.exc_info())
+
+ os._exit(1)
def __str__(self):
msg = self.message
@@ -58,6 +71,16 @@ class SpackError(Exception):
msg += "\n %s" % self._long_message
return msg
+ def __repr__(self):
+ args = [repr(self.message), repr(self.long_message)]
+ args = ','.join(args)
+ qualified_name = inspect.getmodule(
+ self).__name__ + '.' + type(self).__name__
+ return qualified_name + '(' + args + ')'
+
+ def __reduce__(self):
+ return type(self), (self.message, self.long_message)
+
class UnsupportedPlatformError(SpackError):
"""Raised by packages when a platform is not supported"""
diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py
index c69a23033c..5084a68e08 100644
--- a/lib/spack/spack/fetch_strategy.py
+++ b/lib/spack/spack/fetch_strategy.py
@@ -116,6 +116,14 @@ class FetchStrategy(object):
def archive(self, destination):
pass # Used to create tarball for mirror.
+ @property
+ def cachable(self):
+ """Return whether the fetcher is capable of caching the
+ resource it retrieves. This generally is determined by
+ whether the resource is identifiably associated with a
+ specific package version."""
+ pass
+
def __str__(self): # Should be human readable URL.
return "FetchStrategy.__str___"
@@ -158,10 +166,20 @@ class URLFetchStrategy(FetchStrategy):
self.digest = digest
self.expand_archive = kwargs.get('expand', True)
+ self.extra_curl_options = kwargs.get('curl_options', [])
+ self._curl = None
+
+ self.extension = kwargs.get('extension', None)
if not self.url:
raise ValueError("URLFetchStrategy requires a url for fetching.")
+ @property
+ def curl(self):
+ if not self._curl:
+ self._curl = which('curl', required=True)
+ return self._curl
+
@_needs_stage
def fetch(self):
self.stage.chdir()
@@ -170,14 +188,13 @@ class URLFetchStrategy(FetchStrategy):
tty.msg("Already downloaded %s" % self.archive_file)
return
- possible_files = self.stage.expected_archive_files
save_file = None
partial_file = None
- if possible_files:
- save_file = self.stage.expected_archive_files[0]
- partial_file = self.stage.expected_archive_files[0] + '.part'
+ if self.stage.save_filename:
+ save_file = self.stage.save_filename
+ partial_file = self.stage.save_filename + '.part'
- tty.msg("Trying to fetch from %s" % self.url)
+ tty.msg("Fetching %s" % self.url)
if partial_file:
save_args = ['-C',
@@ -195,15 +212,21 @@ class URLFetchStrategy(FetchStrategy):
self.url,
]
+ if spack.insecure:
+ curl_args.append('-k')
+
if sys.stdout.isatty():
curl_args.append('-#') # status bar when using a tty
else:
curl_args.append('-sS') # just errors when not.
+ curl_args += self.extra_curl_options
+
# Run curl but grab the mime type from the http headers
- headers = spack.curl(*curl_args, output=str, fail_on_error=False)
+ curl = self.curl
+ headers = curl(*curl_args, output=str, fail_on_error=False)
- if spack.curl.returncode != 0:
+ if curl.returncode != 0:
# clean up archive on failure.
if self.archive_file:
os.remove(self.archive_file)
@@ -211,12 +234,12 @@ class URLFetchStrategy(FetchStrategy):
if partial_file and os.path.exists(partial_file):
os.remove(partial_file)
- if spack.curl.returncode == 22:
+ if curl.returncode == 22:
# This is a 404. Curl will print the error.
raise FailedDownloadError(
self.url, "URL %s was not found!" % self.url)
- elif spack.curl.returncode == 60:
+ elif curl.returncode == 60:
# This is a certificate error. Suggest spack -k
raise FailedDownloadError(
self.url,
@@ -232,7 +255,7 @@ class URLFetchStrategy(FetchStrategy):
# error, but print a spack message too
raise FailedDownloadError(
self.url,
- "Curl failed with error %d" % spack.curl.returncode)
+ "Curl failed with error %d" % curl.returncode)
# Check if we somehow got an HTML file rather than the archive we
# asked for. We only look at the last content type, to handle
@@ -257,6 +280,10 @@ class URLFetchStrategy(FetchStrategy):
"""Path to the source archive within this stage directory."""
return self.stage.archive_file
+ @property
+ def cachable(self):
+ return bool(self.digest)
+
@_needs_stage
def expand(self):
if not self.expand_archive:
@@ -268,10 +295,12 @@ class URLFetchStrategy(FetchStrategy):
self.stage.chdir()
if not self.archive_file:
raise NoArchiveFileError(
- "URLFetchStrategy couldn't find archive file",
+ "Couldn't find archive file",
"Failed on expand() for URL %s" % self.url)
- decompress = decompressor_for(self.archive_file)
+ if not self.extension:
+ self.extension = extension(self.archive_file)
+ decompress = decompressor_for(self.archive_file, self.extension)
# Expand all tarballs in their own directory to contain
# exploding tarballs.
@@ -298,7 +327,8 @@ class URLFetchStrategy(FetchStrategy):
shutil.move(os.path.join(tarball_container, f),
os.path.join(self.stage.path, f))
os.rmdir(tarball_container)
-
+ if not files:
+ os.rmdir(tarball_container)
# Set the wd back to the stage when done.
self.stage.chdir()
@@ -307,7 +337,7 @@ class URLFetchStrategy(FetchStrategy):
if not self.archive_file:
raise NoArchiveFileError("Cannot call archive() before fetching.")
- shutil.copy(self.archive_file, destination)
+ shutil.copyfile(self.archive_file, destination)
@_needs_stage
def check(self):
@@ -362,16 +392,34 @@ class CacheURLFetchStrategy(URLFetchStrategy):
@_needs_stage
def fetch(self):
- super(CacheURLFetchStrategy, self).fetch()
+ path = re.sub('^file://', '', self.url)
+
+ # check whether the cache file exists.
+ if not os.path.isfile(path):
+ raise NoCacheError('No cache of %s' % path)
+
+ self.stage.chdir()
+
+ # remove old symlink if one is there.
+ filename = self.stage.save_filename
+ if os.path.exists(filename):
+ os.remove(filename)
+
+ # Symlink to local cached archive.
+ os.symlink(path, filename)
+
+ # Remove link if checksum fails, or subsequent fetchers
+ # will assume they don't need to download.
if self.digest:
try:
self.check()
except ChecksumError:
- # Future fetchers will assume they don't need to
- # download if the file remains
os.remove(self.archive_file)
raise
+ # Notify the user how we fetched.
+ tty.msg('Using cached archive: %s' % path)
+
class VCSFetchStrategy(FetchStrategy):
@@ -529,8 +577,18 @@ class GitFetchStrategy(VCSFetchStrategy):
def git(self):
if not self._git:
self._git = which('git', required=True)
+
+ # If the user asked for insecure fetching, make that work
+ # with git as well.
+ if spack.insecure:
+ self._git.add_default_env('GIT_SSL_NO_VERIFY', 'true')
+
return self._git
+ @property
+ def cachable(self):
+ return bool(self.commit or self.tag)
+
@_needs_stage
def fetch(self):
self.stage.chdir()
@@ -647,6 +705,10 @@ class SvnFetchStrategy(VCSFetchStrategy):
self._svn = which('svn', required=True)
return self._svn
+ @property
+ def cachable(self):
+ return bool(self.revision)
+
@_needs_stage
def fetch(self):
self.stage.chdir()
@@ -730,6 +792,10 @@ class HgFetchStrategy(VCSFetchStrategy):
self._hg = which('hg', required=True)
return self._hg
+ @property
+ def cachable(self):
+ return bool(self.revision)
+
@_needs_stage
def fetch(self):
self.stage.chdir()
@@ -836,46 +902,59 @@ def for_package_version(pkg, version):
raise InvalidArgsError(pkg, version)
+def from_list_url(pkg):
+ """If a package provides a URL which lists URLs for resources by
+ version, this can can create a fetcher for a URL discovered for
+ the specified package's version."""
+ if pkg.list_url:
+ try:
+ versions = pkg.fetch_remote_versions()
+ try:
+ url_from_list = versions[pkg.version]
+ return URLFetchStrategy(url=url_from_list, digest=None)
+ except KeyError:
+ tty.msg("Can not find version %s in url_list" %
+ self.version)
+ except:
+ tty.msg("Could not determine url from list_url.")
+
+
class FsCache(object):
def __init__(self, root):
self.root = os.path.abspath(root)
def store(self, fetcher, relativeDst):
- unique = False
- uidGroups = [['tag', 'commit'], ['digest'], ['revision']]
- for grp in uidGroups:
- try:
- unique |= any(getattr(fetcher, x) for x in grp)
- except AttributeError:
- pass
- if unique:
- break
- if not unique:
+ # skip fetchers that aren't cachable
+ if not fetcher.cachable:
+ return
+
+ # Don't store things that are already cached.
+ if isinstance(fetcher, CacheURLFetchStrategy):
return
dst = join_path(self.root, relativeDst)
mkdirp(os.path.dirname(dst))
fetcher.archive(dst)
- def fetcher(self, targetPath, digest):
- url = "file://" + join_path(self.root, targetPath)
- return CacheURLFetchStrategy(url, digest)
+ def fetcher(self, targetPath, digest, **kwargs):
+ path = join_path(self.root, targetPath)
+ return CacheURLFetchStrategy(path, digest, **kwargs)
def destroy(self):
shutil.rmtree(self.root, ignore_errors=True)
class FetchError(spack.error.SpackError):
+ """Superclass fo fetcher errors."""
- def __init__(self, msg, long_msg=None):
- super(FetchError, self).__init__(msg, long_msg)
+class NoCacheError(FetchError):
+ """Raised when there is no cached archive for a package."""
-class FailedDownloadError(FetchError):
+class FailedDownloadError(FetchError):
"""Raised wen a download fails."""
-
def __init__(self, url, msg=""):
super(FailedDownloadError, self).__init__(
"Failed to fetch file from URL: %s" % url, msg)
@@ -883,19 +962,14 @@ class FailedDownloadError(FetchError):
class NoArchiveFileError(FetchError):
-
- def __init__(self, msg, long_msg):
- super(NoArchiveFileError, self).__init__(msg, long_msg)
+ """"Raised when an archive file is expected but none exists."""
class NoDigestError(FetchError):
-
- def __init__(self, msg, long_msg=None):
- super(NoDigestError, self).__init__(msg, long_msg)
+ """Raised after attempt to checksum when URL has no digest."""
class InvalidArgsError(FetchError):
-
def __init__(self, pkg, version):
msg = ("Could not construct a fetch strategy for package %s at "
"version %s")
@@ -904,17 +978,11 @@ class InvalidArgsError(FetchError):
class ChecksumError(FetchError):
-
"""Raised when archive fails to checksum."""
- def __init__(self, message, long_msg=None):
- super(ChecksumError, self).__init__(message, long_msg)
-
class NoStageError(FetchError):
-
"""Raised when fetch operations are called before set_stage()."""
-
def __init__(self, method):
super(NoStageError, self).__init__(
"Must call FetchStrategy.set_stage() before calling %s" %
diff --git a/lib/spack/spack/file_cache.py b/lib/spack/spack/file_cache.py
index 0a66166fd8..e37f77d68d 100644
--- a/lib/spack/spack/file_cache.py
+++ b/lib/spack/spack/file_cache.py
@@ -77,10 +77,7 @@ class FileCache(object):
def _get_lock(self, key):
"""Create a lock for a key, if necessary, and return a lock object."""
if key not in self._locks:
- lock_file = self._lock_path(key)
- if not os.path.exists(lock_file):
- touch(lock_file)
- self._locks[key] = Lock(lock_file)
+ self._locks[key] = Lock(self._lock_path(key))
return self._locks[key]
def init_entry(self, key):
@@ -116,7 +113,7 @@ class FileCache(object):
Returns a ReadTransaction context manager and opens the cache file for
reading. You can use it like this:
- with spack.user_cache.read_transaction(key) as cache_file:
+ with file_cache_object.read_transaction(key) as cache_file:
cache_file.read()
"""
diff --git a/lib/spack/spack/graph.py b/lib/spack/spack/graph.py
index b875e9da99..9753741c61 100644
--- a/lib/spack/spack/graph.py
+++ b/lib/spack/spack/graph.py
@@ -67,22 +67,20 @@ from heapq import *
from llnl.util.lang import *
from llnl.util.tty.color import *
-import spack
-from spack.spec import Spec
+from spack.spec import *
__all__ = ['topological_sort', 'graph_ascii', 'AsciiGraph', 'graph_dot']
-def topological_sort(spec, **kwargs):
+def topological_sort(spec, reverse=False, deptype=None):
"""Topological sort for specs.
Return a list of dependency specs sorted topologically. The spec
argument is not modified in the process.
"""
- reverse = kwargs.get('reverse', False)
- # XXX(deptype): iterate over a certain kind of dependency. Maybe color
- # edges based on the type of dependency?
+ deptype = canonical_deptype(deptype)
+
if not reverse:
parents = lambda s: s.dependents()
children = lambda s: s.dependencies()
@@ -91,7 +89,7 @@ def topological_sort(spec, **kwargs):
children = lambda s: s.dependents()
# Work on a copy so this is nondestructive.
- spec = spec.copy()
+ spec = spec.copy(deps=deptype)
nodes = spec.index()
topo_order = []
@@ -129,7 +127,7 @@ def find(seq, predicate):
return -1
-# Names of different graph line states. We Record previous line
+# Names of different graph line states. We record previous line
# states so that we can easily determine what to do when connecting.
states = ('node', 'collapse', 'merge-right', 'expand-right', 'back-edge')
NODE, COLLAPSE, MERGE_RIGHT, EXPAND_RIGHT, BACK_EDGE = states
@@ -143,6 +141,7 @@ class AsciiGraph(object):
self.node_character = '*'
self.debug = False
self.indent = 0
+ self.deptype = alldeps
# These are colors in the order they'll be used for edges.
# See llnl.util.tty.color for details on color characters.
@@ -162,6 +161,9 @@ class AsciiGraph(object):
def _write_edge(self, string, index, sub=0):
"""Write a colored edge to the output stream."""
+ # Ignore empty frontier entries (they're just collapsed)
+ if not self._frontier[index]:
+ return
name = self._frontier[index][sub]
edge = "@%s{%s}" % (self._name_to_color[name], string)
self._out.write(edge)
@@ -386,7 +388,7 @@ class AsciiGraph(object):
self._out = ColorStream(sys.stdout, color=color)
# We'll traverse the spec in topo order as we graph it.
- topo_order = topological_sort(spec, reverse=True)
+ topo_order = topological_sort(spec, reverse=True, deptype=self.deptype)
# Work on a copy to be nondestructive
spec = spec.copy()
@@ -420,20 +422,26 @@ class AsciiGraph(object):
if back:
back.sort()
prev_ends = []
+ collapse_l1 = False
for j, (b, d) in enumerate(back):
self._frontier[i].remove(d)
if i - b > 1:
- self._back_edge_line(prev_ends, b, i, False,
- 'left-1')
+ collapse_l1 = any(not e for e in self._frontier)
+ self._back_edge_line(
+ prev_ends, b, i, collapse_l1, 'left-1')
del prev_ends[:]
prev_ends.append(b)
# Check whether we did ALL the deps as back edges,
# in which case we're done.
- collapse = not self._frontier[i]
- if collapse:
+ pop = not self._frontier[i]
+ collapse_l2 = pop
+ if collapse_l1:
+ collapse_l2 = False
+ if pop:
self._frontier.pop(i)
- self._back_edge_line(prev_ends, -1, -1, collapse, 'left-2')
+ self._back_edge_line(
+ prev_ends, -1, -1, collapse_l2, 'left-2')
elif len(self._frontier[i]) > 1:
# Expand forward after doing all back connections
@@ -476,32 +484,28 @@ class AsciiGraph(object):
# Replace node with its dependencies
self._frontier.pop(i)
- if node.dependencies():
- deps = sorted((d.name for d in node.dependencies()),
- reverse=True)
+ deps = node.dependencies(self.deptype)
+ if deps:
+ deps = sorted((d.name for d in deps), reverse=True)
self._connect_deps(i, deps, "new-deps") # anywhere.
elif self._frontier:
self._collapse_line(i)
-def graph_ascii(spec, **kwargs):
- node_character = kwargs.get('node', 'o')
- out = kwargs.pop('out', None)
- debug = kwargs.pop('debug', False)
- indent = kwargs.pop('indent', 0)
- color = kwargs.pop('color', None)
- check_kwargs(kwargs, graph_ascii)
-
+def graph_ascii(spec, node='o', out=None, debug=False,
+ indent=0, color=None, deptype=None):
graph = AsciiGraph()
graph.debug = debug
graph.indent = indent
- graph.node_character = node_character
+ graph.node_character = node
+ if deptype:
+ graph.deptype = canonical_deptype(deptype)
graph.write(spec, color=color, out=out)
-def graph_dot(*specs, **kwargs):
+def graph_dot(specs, deptype=None, static=False, out=None):
"""Generate a graph in dot format of all provided specs.
Print out a dot formatted graph of all the dependencies between
@@ -510,42 +514,73 @@ def graph_dot(*specs, **kwargs):
spack graph --dot qt | dot -Tpdf > spack-graph.pdf
"""
- out = kwargs.pop('out', sys.stdout)
- check_kwargs(kwargs, graph_dot)
+ if out is None:
+ out = sys.stdout
+
+ if deptype is None:
+ deptype = alldeps
out.write('digraph G {\n')
- out.write(' label = "Spack Dependencies"\n')
out.write(' labelloc = "b"\n')
- out.write(' rankdir = "LR"\n')
+ out.write(' rankdir = "TB"\n')
out.write(' ranksep = "5"\n')
+ out.write('node[\n')
+ out.write(' fontname=Monaco,\n')
+ out.write(' penwidth=2,\n')
+ out.write(' fontsize=12,\n')
+ out.write(' margin=.1,\n')
+ out.write(' shape=box,\n')
+ out.write(' fillcolor=lightblue,\n')
+ out.write(' style="rounded,filled"]\n')
+
out.write('\n')
- def quote(string):
+ def q(string):
return '"%s"' % string
if not specs:
- specs = [p.name for p in spack.repo.all_packages()]
- else:
- roots = specs
- specs = set()
- for spec in roots:
- specs.update(Spec(s.name) for s in spec.normalized().traverse())
+ raise ValueError("Must provide specs ot graph_dot")
+
+ # Static graph includes anything a package COULD depend on.
+ if static:
+ names = set.union(*[s.package.possible_dependencies() for s in specs])
+ specs = [Spec(name) for name in names]
- deps = []
+ labeled = set()
+
+ def label(key, label):
+ if key not in labeled:
+ out.write(' "%s" [label="%s"]\n' % (key, label))
+ labeled.add(key)
+
+ deps = set()
for spec in specs:
- out.write(' %-30s [label="%s"]\n' % (quote(spec.name), spec.name))
+ if static:
+ out.write(' "%s" [label="%s"]\n' % (spec.name, spec.name))
+
+ # Skip virtual specs (we'll find out about them from concrete ones.
+ if spec.virtual:
+ continue
- # Skip virtual specs (we'll find out about them from concrete ones.
- if spec.virtual:
- continue
+ # Add edges for each depends_on in the package.
+ for dep_name, dep in spec.package.dependencies.iteritems():
+ deps.add((spec.name, dep_name))
+
+ # If the package provides something, add an edge for that.
+ for provider in set(s.name for s in spec.package.provided):
+ deps.add((provider, spec.name))
+
+ else:
+ def key_label(s):
+ return s.dag_hash(), "%s-%s" % (s.name, s.dag_hash(7))
- # Add edges for each depends_on in the package.
- for dep_name, dep in spec.package.dependencies.iteritems():
- deps.append((spec.name, dep_name))
+ for s in spec.traverse(deptype=deptype):
+ skey, slabel = key_label(s)
+ out.write(' "%s" [label="%s"]\n' % (skey, slabel))
- # If the package provides something, add an edge for that.
- for provider in set(s.name for s in spec.package.provided):
- deps.append((provider, spec.name))
+ for d in s.dependencies(deptype=deptype):
+ dkey, _ = key_label(d)
+ deps.add((skey, dkey))
out.write('\n')
diff --git a/lib/spack/spack/hooks/__init__.py b/lib/spack/spack/hooks/__init__.py
index c7c84defa0..ff4ebc2e57 100644
--- a/lib/spack/spack/hooks/__init__.py
+++ b/lib/spack/spack/hooks/__init__.py
@@ -24,7 +24,7 @@
##############################################################################
"""This package contains modules with hooks for various stages in the
Spack install process. You can add modules here and they'll be
- executaed by package at various times during the package lifecycle.
+ executed by package at various times during the package lifecycle.
Each hook is just a function that takes a package as a parameter.
Hooks are not executed in any particular order.
@@ -41,9 +41,10 @@
features.
"""
import imp
-from llnl.util.lang import memoized, list_modules
-from llnl.util.filesystem import join_path
+
import spack
+from llnl.util.filesystem import join_path
+from llnl.util.lang import memoized, list_modules
@memoized
@@ -70,12 +71,11 @@ class HookRunner(object):
if hasattr(hook, '__call__'):
hook(pkg)
-
#
# Define some functions that can be called to fire off hooks.
#
-pre_install = HookRunner('pre_install')
-post_install = HookRunner('post_install')
+pre_install = HookRunner('pre_install')
+post_install = HookRunner('post_install')
-pre_uninstall = HookRunner('pre_uninstall')
+pre_uninstall = HookRunner('pre_uninstall')
post_uninstall = HookRunner('post_uninstall')
diff --git a/lib/spack/spack/hooks/licensing.py b/lib/spack/spack/hooks/licensing.py
index 9010b84154..a99099749c 100644
--- a/lib/spack/spack/hooks/licensing.py
+++ b/lib/spack/spack/hooks/licensing.py
@@ -157,6 +157,11 @@ def symlink_license(pkg):
license_dir = os.path.dirname(link_name)
if not os.path.exists(license_dir):
mkdirp(license_dir)
+
+ # If example file already exists, overwrite it with a symlink
+ if os.path.exists(link_name):
+ os.remove(link_name)
+
if os.path.exists(target):
os.symlink(target, link_name)
tty.msg("Added local symlink %s to global license file" %
diff --git a/lib/spack/spack/hooks/dotkit.py b/lib/spack/spack/hooks/module_file_generation.py
index a140646e04..445cea4e91 100644
--- a/lib/spack/spack/hooks/dotkit.py
+++ b/lib/spack/spack/hooks/module_file_generation.py
@@ -26,10 +26,12 @@ import spack.modules
def post_install(pkg):
- dk = spack.modules.Dotkit(pkg.spec)
- dk.write()
+ for item, cls in spack.modules.module_types.iteritems():
+ generator = cls(pkg.spec)
+ generator.write()
def post_uninstall(pkg):
- dk = spack.modules.Dotkit(pkg.spec)
- dk.remove()
+ for item, cls in spack.modules.module_types.iteritems():
+ generator = cls(pkg.spec)
+ generator.remove()
diff --git a/lib/spack/spack/hooks/sbang.py b/lib/spack/spack/hooks/sbang.py
index 02c1ce3816..6f9736a018 100644
--- a/lib/spack/spack/hooks/sbang.py
+++ b/lib/spack/spack/hooks/sbang.py
@@ -81,8 +81,10 @@ def filter_shebang(path):
tty.warn("Patched overlong shebang in %s" % path)
-def filter_shebangs_in_directory(directory):
- for file in os.listdir(directory):
+def filter_shebangs_in_directory(directory, filenames=None):
+ if filenames is None:
+ filenames = os.listdir(directory)
+ for file in filenames:
path = os.path.join(directory, file)
# only handle files
@@ -104,6 +106,6 @@ 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
- filter_shebangs_in_directory(pkg.prefix.bin)
+
+ for directory, _, filenames in os.walk(pkg.prefix):
+ filter_shebangs_in_directory(directory, filenames)
diff --git a/lib/spack/spack/mirror.py b/lib/spack/spack/mirror.py
index f053e4405f..97aeef5434 100644
--- a/lib/spack/spack/mirror.py
+++ b/lib/spack/spack/mirror.py
@@ -44,7 +44,7 @@ from spack.version import *
from spack.util.compression import allowed_archive
-def mirror_archive_filename(spec, fetcher):
+def mirror_archive_filename(spec, fetcher, resourceId=None):
"""Get the name of the spec's archive in the mirror."""
if not spec.version.concrete:
raise ValueError("mirror.path requires spec with concrete version.")
@@ -52,7 +52,14 @@ def mirror_archive_filename(spec, fetcher):
if isinstance(fetcher, fs.URLFetchStrategy):
if fetcher.expand_archive:
# If we fetch with a URLFetchStrategy, use URL's archive type
- ext = url.downloaded_file_extension(fetcher.url)
+ ext = url.determine_url_file_extension(fetcher.url)
+ ext = ext or spec.package.versions[spec.package.version].get(
+ 'extension', None)
+ ext = ext.lstrip('.')
+ if not ext:
+ raise MirrorError(
+ "%s version does not specify an extension" % spec.name +
+ " and could not parse extension from %s" % fetcher.url)
else:
# If the archive shouldn't be expanded, don't check extension.
ext = None
@@ -60,15 +67,18 @@ def mirror_archive_filename(spec, fetcher):
# Otherwise we'll make a .tar.gz ourselves
ext = 'tar.gz'
- filename = "%s-%s" % (spec.package.name, spec.version)
- if ext:
- filename += ".%s" % ext
+ if resourceId:
+ filename = "%s-%s" % (resourceId, spec.version) + ".%s" % ext
+ else:
+ filename = "%s-%s" % (spec.package.name, spec.version) + ".%s" % ext
+
return filename
-def mirror_archive_path(spec, fetcher):
+def mirror_archive_path(spec, fetcher, resourceId=None):
"""Get the relative path to the spec's archive within a mirror."""
- return join_path(spec.name, mirror_archive_filename(spec, fetcher))
+ return join_path(
+ spec.name, mirror_archive_filename(spec, fetcher, resourceId))
def get_matching_versions(specs, **kwargs):
@@ -120,27 +130,28 @@ def suggest_archive_basename(resource):
def create(path, specs, **kwargs):
"""Create a directory to be used as a spack mirror, and fill it with
- package archives.
-
- Arguments:
- path Path to create a mirror directory hierarchy in.
- specs Any package versions matching these specs will be added
- to the mirror.
-
- Keyword args:
- no_checksum: If True, do not checkpoint when fetching (default False)
- num_versions: Max number of versions to fetch per spec,
- if spec is ambiguous (default is 0 for all of them)
-
- Return Value:
- Returns a tuple of lists: (present, mirrored, error)
- * present: Package specs that were already present.
- * mirrored: Package specs that were successfully mirrored.
- * error: Package specs that failed to mirror due to some error.
-
- This routine iterates through all known package versions, and
- it creates specs for those versions. If the version satisfies any spec
- in the specs list, it is downloaded and added to the mirror.
+ package archives.
+
+ Arguments:
+ path: Path to create a mirror directory hierarchy in.
+ specs: Any package versions matching these specs will be added \
+ to the mirror.
+
+ Keyword args:
+ no_checksum: If True, do not checkpoint when fetching (default False)
+ num_versions: Max number of versions to fetch per spec, \
+ if spec is ambiguous (default is 0 for all of them)
+
+ Return Value:
+ Returns a tuple of lists: (present, mirrored, error)
+
+ * present: Package specs that were already present.
+ * mirrored: Package specs that were successfully mirrored.
+ * error: Package specs that failed to mirror due to some error.
+
+ This routine iterates through all known package versions, and
+ it creates specs for those versions. If the version satisfies any spec
+ in the specs list, it is downloaded and added to the mirror.
"""
# Make sure nothing is in the way.
if os.path.isfile(path):
@@ -196,8 +207,9 @@ def add_single_spec(spec, mirror_root, categories, **kwargs):
name = spec.format("$_$@")
else:
resource = stage.resource
- archive_path = join_path(
- subdir, suggest_archive_basename(resource))
+ archive_path = os.path.abspath(join_path(
+ mirror_root,
+ mirror_archive_path(spec, fetcher, resource.name)))
name = "{resource} ({pkg}).".format(
resource=resource.name, pkg=spec.format("$_$@"))
subdir = os.path.dirname(archive_path)
diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py
index debc6752b4..5e2a840e14 100644
--- a/lib/spack/spack/modules.py
+++ b/lib/spack/spack/modules.py
@@ -40,6 +40,7 @@ module file.
"""
import copy
import datetime
+import itertools
import os
import os.path
import re
@@ -47,19 +48,27 @@ import string
import textwrap
import llnl.util.tty as tty
+from llnl.util.filesystem import join_path, mkdirp
+
import spack
+import spack.compilers # Needed by LmodModules
import spack.config
-from llnl.util.filesystem import join_path, mkdirp
+from spack.util.path import canonicalize_path
from spack.build_environment import parent_class_modules
from spack.build_environment import set_module_variables_for_package
from spack.environment import *
__all__ = ['EnvModule', 'Dotkit', 'TclModule']
-# Registry of all types of modules. Entries created by EnvModule's metaclass
+"""Registry of all types of modules. Entries created by EnvModule's
+ metaclass."""
module_types = {}
-CONFIGURATION = spack.config.get_config('modules')
+"""Module install roots are in config.yaml."""
+_roots = spack.config.get_config('config').get('module_roots', {})
+
+"""Specifics about modules are in modules.yaml"""
+_module_config = spack.config.get_config('modules')
def print_help():
@@ -89,7 +98,7 @@ def inspect_path(prefix):
"""
env = EnvironmentModifications()
# Inspect the prefix to check for the existence of common directories
- prefix_inspections = CONFIGURATION.get('prefix_inspections', {})
+ prefix_inspections = _module_config.get('prefix_inspections', {})
for relative_path, variables in prefix_inspections.items():
expected = join_path(prefix, relative_path)
if os.path.isdir(expected):
@@ -120,7 +129,7 @@ def dependencies(spec, request='all'):
return []
if request == 'direct':
- return spec.dependencies()
+ return spec.dependencies(deptype=('link', 'run'))
# FIXME : during module file creation nodes seem to be visited multiple
# FIXME : times even if cover='nodes' is given. This work around permits
@@ -128,14 +137,13 @@ def dependencies(spec, request='all'):
# FIXME : step among nodes that refer to the same package?
seen = set()
seen_add = seen.add
- l = [xx
- for xx in sorted(
- spec.traverse(order='post',
- depth=True,
- cover='nodes',
- root=False),
- reverse=True)]
- return [xx for ii, xx in l if not (xx in seen or seen_add(xx))]
+ l = sorted(
+ spec.traverse(order='post',
+ cover='nodes',
+ deptype=('link', 'run'),
+ root=False),
+ reverse=True)
+ return [x for x in l if not (x in seen or seen_add(x))]
def update_dictionary_extending_lists(target, update):
@@ -166,7 +174,7 @@ def parse_config_options(module_generator):
module file
"""
# Get the configuration for this kind of generator
- module_configuration = copy.deepcopy(CONFIGURATION.get(
+ module_configuration = copy.deepcopy(_module_config.get(
module_generator.name, {}))
#####
@@ -234,6 +242,10 @@ def filter_blacklisted(specs, module_name):
yield x
+def format_env_var_name(name):
+ return name.replace('-', '_').upper()
+
+
class EnvModule(object):
name = 'env_module'
formats = {}
@@ -242,7 +254,7 @@ class EnvModule(object):
def __init__(cls, name, bases, dict):
type.__init__(cls, name, bases, dict)
- if cls.name != 'env_module' and cls.name in CONFIGURATION[
+ if cls.name != 'env_module' and cls.name in _module_config[
'enable']:
module_types[cls.name] = cls
@@ -265,56 +277,35 @@ class EnvModule(object):
@property
def naming_scheme(self):
try:
- naming_scheme = CONFIGURATION[self.name]['naming_scheme']
+ naming_scheme = _module_config[self.name]['naming_scheme']
except KeyError:
naming_scheme = self.default_naming_format
return naming_scheme
@property
- def tokens(self):
- """Tokens that can be substituted in environment variable values
- and naming schemes
- """
- tokens = {
- 'name': self.spec.name,
- 'version': self.spec.version,
- 'compiler': self.spec.compiler,
- 'prefix': self.spec.package.prefix
- }
- return tokens
-
- @property
- def upper_tokens(self):
- """Tokens that can be substituted in environment variable names"""
- upper_tokens = {
- 'name': self.spec.name.replace('-', '_').upper()
- }
- return upper_tokens
-
- @property
def use_name(self):
"""
Subclasses should implement this to return the name the module command
uses to refer to the package.
"""
- naming_tokens = self.tokens
- naming_scheme = self.naming_scheme
- name = naming_scheme.format(**naming_tokens)
+ name = self.spec.format(self.naming_scheme)
# Not everybody is working on linux...
parts = name.split('/')
name = join_path(*parts)
# Add optional suffixes based on constraints
+ path_elements = [name] + self._get_suffixes()
+ return '-'.join(path_elements)
+
+ def _get_suffixes(self):
configuration, _ = parse_config_options(self)
- suffixes = [name]
+ suffixes = []
for constraint, suffix in configuration.get('suffixes', {}).items():
if constraint in self.spec:
suffixes.append(suffix)
- # Always append the hash to make the module file unique
- hash_length = configuration.pop('hash_length', 7)
+ hash_length = configuration.get('hash_length', 7)
if hash_length != 0:
suffixes.append(self.spec.dag_hash(length=hash_length))
- name = '-'.join(suffixes)
- return name
+ return suffixes
@property
def category(self):
@@ -329,7 +320,7 @@ class EnvModule(object):
@property
def blacklisted(self):
- configuration = CONFIGURATION.get(self.name, {})
+ configuration = _module_config.get(self.name, {})
whitelist_matches = [x
for x in configuration.get('whitelist', [])
if self.spec.satisfies(x)]
@@ -388,6 +379,7 @@ class EnvModule(object):
for mod in modules:
set_module_variables_for_package(package, mod)
set_module_variables_for_package(package, package.module)
+ package.setup_environment(spack_env, env)
package.setup_dependent_package(self.pkg.module, self.spec)
package.setup_dependent_environment(spack_env, env, self.spec)
@@ -451,8 +443,9 @@ class EnvModule(object):
def process_environment_command(self, env):
for command in env:
# Token expansion from configuration file
- name = command.args.get('name', '').format(**self.upper_tokens)
- value = str(command.args.get('value', '')).format(**self.tokens)
+ name = format_env_var_name(
+ self.spec.format(command.args.get('name', '')))
+ value = self.spec.format(str(command.args.get('value', '')))
command.update_args(name=name, value=value)
# Format the line int the module file
try:
@@ -486,7 +479,9 @@ class EnvModule(object):
class Dotkit(EnvModule):
name = 'dotkit'
- path = join_path(spack.share_path, 'dotkit')
+ path = canonicalize_path(
+ _roots.get(name, join_path(spack.share_path, name)))
+
environment_modifications_formats = {
PrependPath: 'dk_alter {name} {value}\n',
RemovePath: 'dk_unalter {name} {value}\n',
@@ -496,7 +491,7 @@ class Dotkit(EnvModule):
autoload_format = 'dk_op {module_file}\n'
default_naming_format = \
- '{name}-{version}-{compiler.name}-{compiler.version}'
+ '${PACKAGE}-${VERSION}-${COMPILERNAME}-${COMPILERVER}'
@property
def file_name(self):
@@ -529,14 +524,8 @@ class Dotkit(EnvModule):
class TclModule(EnvModule):
name = 'tcl'
- path = join_path(spack.share_path, "modules")
- environment_modifications_formats = {
- PrependPath: 'prepend-path --delim "{separator}" {name} \"{value}\"\n',
- AppendPath: 'append-path --delim "{separator}" {name} \"{value}\"\n',
- RemovePath: 'remove-path --delim "{separator}" {name} \"{value}\"\n',
- SetEnv: 'setenv {name} \"{value}\"\n',
- UnsetEnv: 'unsetenv {name}\n'
- }
+ path = canonicalize_path(
+ _roots.get(name, join_path(spack.share_path, 'modules')))
autoload_format = ('if ![ is-loaded {module_file} ] {{\n'
' puts stderr "Autoloading {module_file}"\n'
@@ -546,7 +535,7 @@ class TclModule(EnvModule):
prerequisite_format = 'prereq {module_file}\n'
default_naming_format = \
- '{name}-{version}-{compiler.name}-{compiler.version}'
+ '${PACKAGE}-${VERSION}-${COMPILERNAME}-${COMPILERVER}'
@property
def file_name(self):
@@ -556,11 +545,13 @@ class TclModule(EnvModule):
def header(self):
timestamp = datetime.datetime.now()
# TCL Modulefile header
- header = '#%Module1.0\n'
- header += '## Module file created by spack (https://github.com/LLNL/spack) on %s\n' % timestamp
- header += '##\n'
- header += '## %s\n' % self.spec.short_spec
- header += '##\n'
+ header = """\
+#%%Module1.0
+## Module file created by spack (https://github.com/LLNL/spack) on %s
+##
+## %s
+##
+""" % (timestamp, self.spec.short_spec)
# TODO : category ?
# Short description
@@ -575,8 +566,46 @@ class TclModule(EnvModule):
header += '}\n\n'
return header
+ def process_environment_command(self, env):
+ environment_modifications_formats_colon = {
+ PrependPath: 'prepend-path {name} \"{value}\"\n',
+ AppendPath: 'append-path {name} \"{value}\"\n',
+ RemovePath: 'remove-path {name} \"{value}\"\n',
+ SetEnv: 'setenv {name} \"{value}\"\n',
+ UnsetEnv: 'unsetenv {name}\n'
+ }
+ environment_modifications_formats_general = {
+ PrependPath:
+ 'prepend-path --delim "{separator}" {name} \"{value}\"\n',
+ AppendPath:
+ 'append-path --delim "{separator}" {name} \"{value}\"\n',
+ RemovePath:
+ 'remove-path --delim "{separator}" {name} \"{value}\"\n',
+ SetEnv: 'setenv {name} \"{value}\"\n',
+ UnsetEnv: 'unsetenv {name}\n'
+ }
+ for command in env:
+ # Token expansion from configuration file
+ name = format_env_var_name(
+ self.spec.format(command.args.get('name', '')))
+ value = self.spec.format(str(command.args.get('value', '')))
+ command.update_args(name=name, value=value)
+ # Format the line int the module file
+ try:
+ if command.args.get('separator', ':') == ':':
+ yield environment_modifications_formats_colon[type(
+ command)].format(**command.args)
+ else:
+ yield environment_modifications_formats_general[type(
+ command)].format(**command.args)
+ except KeyError:
+ message = ('Cannot handle command of type {command}: '
+ 'skipping request')
+ details = '{context} at {filename}:{lineno}'
+ tty.warn(message.format(command=type(command)))
+ tty.warn(details.format(**command.args))
+
def module_specific_content(self, configuration):
- naming_tokens = self.tokens
# Conflict
conflict_format = configuration.get('conflict', [])
f = string.Formatter()
@@ -597,5 +626,249 @@ class TclModule(EnvModule):
nformat=self.naming_scheme,
cformat=item))
raise SystemExit('Module generation aborted.')
- line = line.format(**naming_tokens)
+ line = self.spec.format(line)
yield line
+
+# To construct an arbitrary hierarchy of module files:
+# 1. Parse the configuration file and check that all the items in
+# hierarchical_scheme are indeed virtual packages
+# This needs to be done only once at start-up
+# 2. Order the stack as `hierarchical_scheme + ['mpi, 'compiler']
+# 3. Check which of the services are provided by the package
+# -> may be more than one
+# 4. Check which of the services are needed by the package
+# -> this determines where to write the module file
+# 5. For each combination of services in which we have at least one provider
+# here add the appropriate conditional MODULEPATH modifications
+
+
+class LmodModule(EnvModule):
+ name = 'lmod'
+ path = canonicalize_path(
+ _roots.get(name, join_path(spack.share_path, name)))
+
+ environment_modifications_formats = {
+ PrependPath: 'prepend_path("{name}", "{value}")\n',
+ AppendPath: 'append_path("{name}", "{value}")\n',
+ RemovePath: 'remove_path("{name}", "{value}")\n',
+ SetEnv: 'setenv("{name}", "{value}")\n',
+ UnsetEnv: 'unsetenv("{name}")\n'
+ }
+
+ autoload_format = ('if not isloaded("{module_file}") then\n'
+ ' LmodMessage("Autoloading {module_file}")\n'
+ ' load("{module_file}")\n'
+ 'end\n\n')
+
+ prerequisite_format = 'prereq("{module_file}")\n'
+
+ family_format = 'family("{family}")\n'
+
+ path_part_without_hash = join_path('{token.name}', '{token.version}')
+
+ # TODO : Check that extra tokens specified in configuration file
+ # TODO : are actually virtual dependencies
+ configuration = _module_config.get('lmod', {})
+ hierarchy_tokens = configuration.get('hierarchical_scheme', [])
+ hierarchy_tokens = hierarchy_tokens + ['mpi', 'compiler']
+
+ def __init__(self, spec=None):
+ super(LmodModule, self).__init__(spec)
+
+ self.configuration = _module_config.get('lmod', {})
+ hierarchy_tokens = self.configuration.get('hierarchical_scheme', [])
+ # TODO : Check that the extra hierarchy tokens specified in the
+ # TODO : configuration file are actually virtual dependencies
+ self.hierarchy_tokens = hierarchy_tokens + ['mpi', 'compiler']
+
+ # Sets the root directory for this architecture
+ self.modules_root = join_path(LmodModule.path, self.spec.architecture)
+
+ # Retrieve core compilers
+ self.core_compilers = self.configuration.get('core_compilers', [])
+ # Keep track of the requirements that this package has in terms
+ # of virtual packages that participate in the hierarchical structure
+ self.requires = {'compiler': self.spec.compiler}
+ # For each virtual dependency in the hierarchy
+ for x in self.hierarchy_tokens:
+ if x in self.spec and not self.spec.package.provides(
+ x): # if I depend on it
+ self.requires[x] = self.spec[x] # record the actual provider
+ # Check what are the services I need (this will determine where the
+ # module file will be written)
+ self.substitutions = {}
+ self.substitutions.update(self.requires)
+ # TODO : complete substitutions
+ # Check what service I provide to others
+ self.provides = {}
+ # If it is in the list of supported compilers family -> compiler
+ if self.spec.name in spack.compilers.supported_compilers():
+ self.provides['compiler'] = spack.spec.CompilerSpec(str(self.spec))
+ # Special case for llvm
+ if self.spec.name == 'llvm':
+ self.provides['compiler'] = spack.spec.CompilerSpec(str(self.spec))
+ self.provides['compiler'].name = 'clang'
+
+ for x in self.hierarchy_tokens:
+ if self.spec.package.provides(x):
+ self.provides[x] = self.spec[x]
+
+ def _hierarchy_token_combinations(self):
+ """
+ Yields all the relevant combinations that could appear in the hierarchy
+ """
+ for ii in range(len(self.hierarchy_tokens) + 1):
+ for item in itertools.combinations(self.hierarchy_tokens, ii):
+ if 'compiler' in item:
+ yield item
+
+ def _hierarchy_to_be_provided(self):
+ """
+ Filters a list of hierarchy tokens and yields only the one that we
+ need to provide
+ """
+ for item in self._hierarchy_token_combinations():
+ if any(x in self.provides for x in item):
+ yield item
+
+ def token_to_path(self, name, value):
+ # If we are dealing with a core compiler, return 'Core'
+ if name == 'compiler' and str(value) in self.core_compilers:
+ return 'Core'
+ # CompilerSpec does not have an hash
+ if name == 'compiler':
+ return self.path_part_without_hash.format(token=value)
+ # In this case the hierarchy token refers to a virtual provider
+ path = self.path_part_without_hash.format(token=value)
+ path = '-'.join([path, value.dag_hash(length=7)])
+ return path
+
+ @property
+ def file_name(self):
+ parts = [self.token_to_path(x, self.requires[x])
+ for x in self.hierarchy_tokens if x in self.requires]
+ hierarchy_name = join_path(*parts)
+ fullname = join_path(self.modules_root, hierarchy_name,
+ self.use_name + '.lua')
+ return fullname
+
+ @property
+ def use_name(self):
+ path_elements = [self.spec.format("${PACKAGE}/${VERSION}")]
+ # The remaining elements are filename suffixes
+ path_elements.extend(self._get_suffixes())
+ return '-'.join(path_elements)
+
+ def modulepath_modifications(self):
+ # What is available is what we require plus what we provide
+ entry = ''
+ available = {}
+ available.update(self.requires)
+ available.update(self.provides)
+ available_parts = [self.token_to_path(x, available[x])
+ for x in self.hierarchy_tokens if x in available]
+ # Missing parts
+ missing = [x for x in self.hierarchy_tokens if x not in available]
+ # Direct path we provide on top of compilers
+ modulepath = join_path(self.modules_root, *available_parts)
+ env = EnvironmentModifications()
+ env.prepend_path('MODULEPATH', modulepath)
+ for line in self.process_environment_command(env):
+ entry += line
+
+ def local_variable(x):
+ lower, upper = x.lower(), x.upper()
+ fmt = 'local {lower}_name = os.getenv("LMOD_{upper}_NAME")\n'
+ fmt += 'local {lower}_version = os.getenv("LMOD_{upper}_VERSION")\n' # NOQA: ignore=501
+ return fmt.format(lower=lower, upper=upper)
+
+ def set_variables_for_service(env, x):
+ upper = x.upper()
+ s = self.provides[x]
+ name, version = os.path.split(self.token_to_path(x, s))
+
+ env.set('LMOD_{upper}_NAME'.format(upper=upper), name)
+ env.set('LMOD_{upper}_VERSION'.format(upper=upper), version)
+
+ def conditional_modulepath_modifications(item):
+ entry = 'if '
+ needed = []
+ for x in self.hierarchy_tokens:
+ if x in missing:
+ needed.append('{x}_name '.format(x=x))
+ entry += 'and '.join(needed) + 'then\n'
+ entry += ' local t = pathJoin("{root}"'.format(
+ root=self.modules_root)
+ for x in item:
+ if x in missing:
+ entry += ', {lower}_name, {lower}_version'.format(
+ lower=x.lower())
+ else:
+ entry += ', "{x}"'.format(
+ x=self.token_to_path(x, available[x]))
+ entry += ')\n'
+ entry += ' prepend_path("MODULEPATH", t)\n'
+ entry += 'end\n\n'
+ return entry
+
+ if 'compiler' not in self.provides:
+ # Retrieve variables
+ entry += '\n'
+ for x in missing:
+ entry += local_variable(x)
+ entry += '\n'
+ # Conditional modifications
+ conditionals = [x
+ for x in self._hierarchy_to_be_provided()
+ if any(t in missing for t in x)]
+ for item in conditionals:
+ entry += conditional_modulepath_modifications(item)
+
+ # Set environment variables for the services we provide
+ env = EnvironmentModifications()
+ for x in self.provides:
+ set_variables_for_service(env, x)
+ for line in self.process_environment_command(env):
+ entry += line
+
+ return entry
+
+ @property
+ def header(self):
+ timestamp = datetime.datetime.now()
+ # Header as in
+ # https://www.tacc.utexas.edu/research-development/tacc-projects/lmod/advanced-user-guide/more-about-writing-module-files
+ header = "-- -*- lua -*-\n"
+ header += '-- Module file created by spack (https://github.com/LLNL/spack) on %s\n' % timestamp # NOQA: ignore=E501
+ header += '--\n'
+ header += '-- %s\n' % self.spec.short_spec
+ header += '--\n'
+
+ # Short description -> whatis()
+ if self.short_description:
+ header += "whatis([[Name : {name}]])\n".format(name=self.spec.name)
+ header += "whatis([[Version : {version}]])\n".format(
+ version=self.spec.version)
+
+ # Long description -> help()
+ if self.long_description:
+ doc = re.sub(r'"', '\"', self.long_description)
+ header += "help([[{documentation}]])\n".format(documentation=doc)
+
+ # Certain things need to be done only if we provide a service
+ if self.provides:
+ # Add family directives
+ header += '\n'
+ for x in self.provides:
+ header += self.family_format.format(family=x)
+ header += '\n'
+ header += '-- MODULEPATH modifications\n'
+ header += '\n'
+ # Modify MODULEPATH
+ header += self.modulepath_modifications()
+ # Set environment variables for services we provide
+ header += '\n'
+ header += '-- END MODULEPATH modifications\n'
+ header += '\n'
+
+ return header
diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py
index d1d1f32445..4e2fb3bdaa 100644
--- a/lib/spack/spack/multimethod.py
+++ b/lib/spack/spack/multimethod.py
@@ -105,7 +105,17 @@ class SpecMultiMethod(object):
def __get__(self, obj, objtype):
"""This makes __call__ support instance methods."""
- return functools.partial(self.__call__, obj)
+ # Method_list is a list of tuples (constraint, method)
+ # Here we are going to assume that we have at least one
+ # element in the list. The first registered function
+ # will be the one 'wrapped'.
+ wrapped_method = self.method_list[0][1]
+ # Call functools.wraps manually to get all the attributes
+ # we need to be disguised as the wrapped_method
+ func = functools.wraps(wrapped_method)(
+ functools.partial(self.__call__, obj)
+ )
+ return func
def __call__(self, package_self, *args, **kwargs):
"""Find the first method with a spec that matches the
diff --git a/lib/spack/spack/operating_systems/linux_distro.py b/lib/spack/spack/operating_systems/linux_distro.py
index 6d70ae80b6..b9a39361da 100644
--- a/lib/spack/spack/operating_systems/linux_distro.py
+++ b/lib/spack/spack/operating_systems/linux_distro.py
@@ -1,5 +1,4 @@
import re
-import platform as py_platform
from spack.architecture import OperatingSystem
@@ -12,8 +11,14 @@ class LinuxDistro(OperatingSystem):
"""
def __init__(self):
- distname, version, _ = py_platform.linux_distribution(
- full_distribution_name=False)
+ try:
+ # This will throw an error if imported on a non-Linux platform.
+ from external.distro import linux_distribution
+ distname, version, _ = linux_distribution(
+ full_distribution_name=False)
+ distname, version = str(distname), str(version)
+ except ImportError as e:
+ distname, version = 'unknown', ''
# Grabs major version from tuple on redhat; on other platforms
# grab the first legal identifier in the version field. On
diff --git a/lib/spack/spack/operating_systems/mac_os.py b/lib/spack/spack/operating_systems/mac_os.py
index 3e5ab9b2e9..dafb5c1d41 100644
--- a/lib/spack/spack/operating_systems/mac_os.py
+++ b/lib/spack/spack/operating_systems/mac_os.py
@@ -22,7 +22,7 @@ class MacOs(OperatingSystem):
"10.11": "elcapitan",
"10.12": "sierra"}
- mac_ver = py_platform.mac_ver()[0][:-2]
+ mac_ver = '.'.join(py_platform.mac_ver()[0].split('.')[:2])
name = mac_releases.get(mac_ver, "macos")
super(MacOs, self).__init__(name, mac_ver)
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index ff8c8e96bc..7462a73533 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -33,16 +33,21 @@ Homebrew makes it very easy to create packages. For a complete
rundown on spack and how it differs from homebrew, look at the
README.
"""
+import contextlib
+import copy
+import functools
+import inspect
import os
-import sys
import re
+import sys
import textwrap
import time
-import string
+from StringIO import StringIO
+import llnl.util.lock
import llnl.util.tty as tty
import spack
-import spack.build_environment
+import spack.store
import spack.compilers
import spack.directives
import spack.error
@@ -52,26 +57,187 @@ import spack.mirror
import spack.repository
import spack.url
import spack.util.web
-
-from urlparse import urlparse
-from StringIO import StringIO
from llnl.util.filesystem import *
from llnl.util.lang import *
from llnl.util.link_tree import LinkTree
from llnl.util.tty.log import log_output
+from spack import directory_layout
from spack.stage import Stage, ResourceStage, StageComposite
-from spack.util.compression import allowed_archive
+from spack.util.crypto import bit_length
from spack.util.environment import dump_environment
-from spack.util.executable import ProcessError, which
from spack.version import *
-from spack import directory_layout
-
"""Allowed URL schemes for spack packages."""
_ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
-class Package(object):
+class InstallPhase(object):
+ """Manages a single phase of the installation
+
+ This descriptor stores at creation time the name of the method it should
+ search for execution. The method is retrieved at __get__ time, so that
+ it can be overridden by subclasses of whatever class declared the phases.
+
+ It also provides hooks to execute prerequisite and sanity checks.
+ """
+
+ def __init__(self, name):
+ self.name = name
+ self.preconditions = []
+ self.sanity_checks = []
+
+ def __get__(self, instance, owner):
+ # The caller is a class that is trying to customize
+ # my behavior adding something
+ if instance is None:
+ return self
+ # If instance is there the caller wants to execute the
+ # install phase, thus return a properly set wrapper
+ phase = getattr(instance, self.name)
+
+ @functools.wraps(phase)
+ def phase_wrapper(spec, prefix):
+ # Check instance attributes at the beginning of a phase
+ self._on_phase_start(instance)
+ # Execute phase pre-conditions,
+ # and give them the chance to fail
+ for check in self.preconditions:
+ # Do something sensible at some point
+ check(instance)
+ phase(spec, prefix)
+ # Execute phase sanity_checks,
+ # and give them the chance to fail
+ for check in self.sanity_checks:
+ check(instance)
+ # Check instance attributes at the end of a phase
+ self._on_phase_exit(instance)
+ return phase_wrapper
+
+ def _on_phase_start(self, instance):
+ pass
+
+ def _on_phase_exit(self, instance):
+ # If a phase has a matching last_phase attribute,
+ # stop the installation process raising a StopIteration
+ if getattr(instance, 'last_phase', None) == self.name:
+ raise StopIteration('Stopping at \'{0}\' phase'.format(self.name))
+
+ def copy(self):
+ try:
+ return copy.deepcopy(self)
+ except TypeError:
+ # This bug-fix was not back-ported in Python 2.6
+ # http://bugs.python.org/issue1515
+ other = InstallPhase(self.name)
+ other.preconditions.extend(self.preconditions)
+ other.sanity_checks.extend(self.sanity_checks)
+ return other
+
+
+class PackageMeta(type):
+ """Conveniently transforms attributes to permit extensible phases
+
+ Iterates over the attribute 'phases' and creates / updates private
+ InstallPhase attributes in the class that is being initialized
+ """
+ phase_fmt = '_InstallPhase_{0}'
+
+ _InstallPhase_sanity_checks = {}
+ _InstallPhase_preconditions = {}
+
+ def __new__(meta, name, bases, attr_dict):
+ # Check if phases is in attr dict, then set
+ # install phases wrappers
+ if 'phases' in attr_dict:
+ _InstallPhase_phases = [PackageMeta.phase_fmt.format(x) for x in attr_dict['phases']] # NOQA: ignore=E501
+ for phase_name, callback_name in zip(_InstallPhase_phases, attr_dict['phases']): # NOQA: ignore=E501
+ attr_dict[phase_name] = InstallPhase(callback_name)
+ attr_dict['_InstallPhase_phases'] = _InstallPhase_phases
+
+ def _append_checks(check_name):
+ # Name of the attribute I am going to check it exists
+ attr_name = PackageMeta.phase_fmt.format(check_name)
+ checks = getattr(meta, attr_name)
+ if checks:
+ for phase_name, funcs in checks.items():
+ try:
+ # Search for the phase in the attribute dictionary
+ phase = attr_dict[
+ PackageMeta.phase_fmt.format(phase_name)]
+ except KeyError:
+ # If it is not there it's in the bases
+ # and we added a check. We need to copy
+ # and extend
+ for base in bases:
+ phase = getattr(
+ base,
+ PackageMeta.phase_fmt.format(phase_name),
+ None
+ )
+ attr_dict[PackageMeta.phase_fmt.format(
+ phase_name)] = phase.copy()
+ phase = attr_dict[
+ PackageMeta.phase_fmt.format(phase_name)]
+ getattr(phase, check_name).extend(funcs)
+ # Clear the attribute for the next class
+ setattr(meta, attr_name, {})
+
+ @classmethod
+ def _register_checks(cls, check_type, *args):
+ def _register_sanity_checks(func):
+ attr_name = PackageMeta.phase_fmt.format(check_type)
+ check_list = getattr(meta, attr_name)
+ for item in args:
+ checks = check_list.setdefault(item, [])
+ checks.append(func)
+ setattr(meta, attr_name, check_list)
+ return func
+ return _register_sanity_checks
+
+ @staticmethod
+ def on_package_attributes(**attrs):
+ def _execute_under_condition(func):
+ @functools.wraps(func)
+ def _wrapper(instance):
+ # If all the attributes have the value we require, then
+ # execute
+ if all([getattr(instance, key, None) == value for key, value in attrs.items()]): # NOQA: ignore=E501
+ func(instance)
+ return _wrapper
+ return _execute_under_condition
+
+ @classmethod
+ def precondition(cls, *args):
+ return cls._register_checks('preconditions', *args)
+
+ @classmethod
+ def sanity_check(cls, *args):
+ return cls._register_checks('sanity_checks', *args)
+
+ if all([not hasattr(x, '_register_checks') for x in bases]):
+ attr_dict['_register_checks'] = _register_checks
+
+ if all([not hasattr(x, 'sanity_check') for x in bases]):
+ attr_dict['sanity_check'] = sanity_check
+
+ if all([not hasattr(x, 'precondition') for x in bases]):
+ attr_dict['precondition'] = precondition
+
+ if all([not hasattr(x, 'on_package_attributes') for x in bases]):
+ attr_dict['on_package_attributes'] = on_package_attributes
+
+ # Preconditions
+ _append_checks('preconditions')
+ # Sanity checks
+ _append_checks('sanity_checks')
+ return super(PackageMeta, meta).__new__(meta, name, bases, attr_dict)
+
+ def __init__(cls, name, bases, dict):
+ type.__init__(cls, name, bases, dict)
+ spack.directives.ensure_dicts(cls)
+
+
+class PackageBase(object):
"""This is the superclass for all spack packages.
***The Package class***
@@ -84,9 +250,9 @@ class Package(object):
with the package itself. Packages are written in pure python.
Packages are all submodules of spack.packages. If spack is installed
- in $prefix, all of its python files are in $prefix/lib/spack. Most
- of them are in the spack module, so all the packages live in
- $prefix/lib/spack/spack/packages.
+ in ``$prefix``, all of its python files are in ``$prefix/lib/spack``.
+ Most of them are in the spack module, so all the packages live in
+ ``$prefix/lib/spack/spack/packages``.
All you have to do to create a package is make a new subclass of Package
in this directory. Spack automatically scans the python files there
@@ -95,7 +261,7 @@ class Package(object):
**An example package**
Let's look at the cmake package to start with. This package lives in
- $prefix/lib/spack/spack/packages/cmake.py:
+ ``$prefix/var/spack/repos/builtin/packages/cmake/package.py``:
.. code-block:: python
@@ -118,19 +284,21 @@ class Package(object):
1. The module name, ``cmake``.
* User will refers to this name, e.g. 'spack install cmake'.
- * Corresponds to the name of the file, 'cmake.py', and it can
- include ``_``, ``-``, and numbers (it can even start with a
+ * It can include ``_``, ``-``, and numbers (it can even start with a
number).
2. The class name, "Cmake". This is formed by converting `-` or
``_`` in the module name to camel case. If the name starts with
a number, we prefix the class name with ``_``. Examples:
- Module Name Class Name
- foo_bar FooBar
- docbook-xml DocbookXml
- FooBar Foobar
- 3proxy _3proxy
+ =========== ==========
+ Module Name Class Name
+ =========== ==========
+ foo_bar FooBar
+ docbook-xml DocbookXml
+ FooBar Foobar
+ 3proxy _3proxy
+ =========== ==========
The class name is what spack looks for when it loads a package module.
@@ -139,28 +307,30 @@ class Package(object):
Aside from proper naming, here is the bare minimum set of things you
need when you make a package:
- homepage
- informational URL, so that users know what they're
- installing.
+ homepage:
+ informational URL, so that users know what they're
+ installing.
- url
- URL of the source archive that spack will fetch.
+ url or url_for_version(self, version):
+ If url, then the URL of the source archive that spack will fetch.
+ If url_for_version(), then a method returning the URL required
+ to fetch a particular version.
- install()
- This function tells spack how to build and install the
- software it downloaded.
+ install():
+ This function tells spack how to build and install the
+ software it downloaded.
**Optional Attributes**
You can also optionally add these attributes, if needed:
- list_url
+ list_url:
Webpage to scrape for available version strings. Default is the
directory containing the tarball; use this if the default isn't
correct so that invoking 'spack versions' will work for this
package.
- url_version(self, version)
+ url_version(self, version):
When spack downloads packages at particular versions, it just
converts version to string with str(version). Override this if
your package needs special version formatting in its URL. boost
@@ -177,12 +347,15 @@ class Package(object):
**spack create**
- Most software comes in nicely packaged tarballs, like this one:
- http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz
+ Most software comes in nicely packaged tarballs, like this one
+
+ http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz
+
Taking a page from homebrew, spack deduces pretty much everything it
- needs to know from the URL above. If you simply type this:
+ needs to know from the URL above. If you simply type this::
spack create http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz
+
Spack will download the tarball, generate an md5 hash, figure out the
version and the name of the package from the URL, and create a new
package file for you with all the names and attributes set correctly.
@@ -214,13 +387,13 @@ class Package(object):
you can just run configure or cmake without any additional arguments and
it will find the dependencies automatically.
-
**The Install Function**
The install function is designed so that someone not too terribly familiar
with Python could write a package installer. For example, we put a number
of commands in install scope that you can use almost like shell commands.
- These include make, configure, cmake, rm, rmtree, mkdir, mkdirp, and others.
+ These include make, configure, cmake, rm, rmtree, mkdir, mkdirp, and
+ others.
You can see above in the cmake script that these commands are used to run
configure and make almost like they're used on the command line. The
@@ -235,17 +408,16 @@ class Package(object):
pollute other namespaces, and it allows you to more easily implement an
install function.
- For a full list of commands and variables available in module scope, see the
- add_commands_to_module() function in this class. This is where most of
- them are created and set on the module.
-
+ For a full list of commands and variables available in module scope, see
+ the add_commands_to_module() function in this class. This is where most
+ of them are created and set on the module.
**Parallel Builds**
By default, Spack will run make in parallel when you run make() in your
install function. Spack figures out how many cores are available on
- your system and runs make with -j<cores>. If you do not want this behavior,
- you can explicitly mark a package not to use parallel make:
+ your system and runs make with -j<cores>. If you do not want this
+ behavior, you can explicitly mark a package not to use parallel make:
.. code-block:: python
@@ -254,15 +426,16 @@ class Package(object):
parallel = False
...
- This changes thd default behavior so that make is sequential. If you still
- want to build some parts in parallel, you can do this in your install function:
+ This changes the default behavior so that make is sequential. If you still
+ want to build some parts in parallel, you can do this in your install
+ function:
.. code-block:: python
make(parallel=True)
- Likewise, if you do not supply parallel = True in your Package, you can keep
- the default parallel behavior and run make like this when you want a
+ Likewise, if you do not supply parallel = True in your Package, you can
+ keep the default parallel behavior and run make like this when you want a
sequential build:
.. code-block:: python
@@ -293,41 +466,56 @@ class Package(object):
p.do_restage() # removes the build directory and
# re-expands the archive.
- The convention used here is that a do_* function is intended to be called
- internally by Spack commands (in spack.cmd). These aren't for package
- writers to override, and doing so may break the functionality of the Package
- class.
+ The convention used here is that a ``do_*`` function is intended to be
+ called internally by Spack commands (in spack.cmd). These aren't for
+ package writers to override, and doing so may break the functionality
+ of the Package class.
Package creators override functions like install() (all of them do this),
clean() (some of them do this), and others to provide custom behavior.
-
"""
+ __metaclass__ = PackageMeta
#
# These are default values for instance variables.
#
"""By default we build in parallel. Subclasses can override this."""
parallel = True
+
"""# jobs to use for parallel make. If set, overrides default of ncpus."""
make_jobs = None
+
"""By default do not run tests within package's install()"""
run_tests = False
+
"""Most packages are NOT extendable. Set to True if you want extensions."""
extendable = False
+
+ """When True, add RPATHs for the entire DAG. When False, add RPATHs only
+ for immediate dependencies."""
+ transitive_rpaths = True
+
"""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 = []
+ """Per-process lock objects for each install prefix."""
+ prefix_locks = {}
+
def __init__(self, spec):
# this determines how the package should be built.
self.spec = spec
+ # Lock on the prefix shared resource. Will be set in prefix property
+ self._prefix_lock = None
+
# Name of package is the name of its module, without the
# containing module names.
self.name = self.module.__name__
@@ -337,9 +525,6 @@ class Package(object):
# Allow custom staging paths for packages
self.path = None
- # Sanity check attributes required by Spack directives.
- spack.directives.ensure_dicts(type(self))
-
# Check versions in the versions dict.
for v in self.versions:
assert (isinstance(v, Version))
@@ -403,6 +588,22 @@ class Package(object):
if self.is_extension:
spack.repo.get(self.extendee_spec)._check_extendable()
+ self.extra_args = {}
+
+ def possible_dependencies(self, visited=None):
+ """Return set of possible transitive dependencies of this package."""
+ if visited is None:
+ visited = set()
+
+ visited.add(self.name)
+ for name in self.dependencies:
+ if name not in visited and not spack.spec.Spec(name).virtual:
+ pkg = spack.repo.get(name)
+ for name in pkg.possible_dependencies(visited):
+ visited.add(name)
+
+ return visited
+
@property
def package_dir(self):
"""Return the directory where the package.py file lives."""
@@ -460,8 +661,13 @@ class Package(object):
# TODO: move this out of here and into some URL extrapolation module?
def url_for_version(self, version):
- """
- Returns a URL that you can download a new version of this package from.
+ """Returns a URL from which the specified version of this package
+ may be downloaded.
+
+ version: class Version
+ The version for which a URL is sought.
+
+ See Class Version (version.py)
"""
if not isinstance(version, Version):
version = Version(version)
@@ -481,7 +687,8 @@ class Package(object):
def _make_resource_stage(self, root_stage, fetcher, resource):
resource_stage_folder = self._resource_stage(resource)
- resource_mirror = join_path(self.name, os.path.basename(fetcher.url))
+ resource_mirror = spack.mirror.mirror_archive_path(
+ self.spec, fetcher, resource.name)
stage = ResourceStage(resource.fetcher,
root=root_stage,
resource=resource,
@@ -496,7 +703,6 @@ class Package(object):
# Construct a path where the stage should build..
s = self.spec
stage_name = "%s-%s-%s" % (s.name, s.version, s.dag_hash())
- # Build the composite stage
stage = Stage(fetcher, mirror_path=mp, name=stage_name, path=self.path)
return stage
@@ -626,7 +832,7 @@ class Package(object):
if not self.is_extension:
raise ValueError(
"is_extension called on package that is not an extension.")
- exts = spack.install_layout.extension_map(self.extendee_spec)
+ exts = spack.store.layout.extension_map(self.extendee_spec)
return (self.name in exts) and (exts[self.name] == self.spec)
def provides(self, vpkg_name):
@@ -647,7 +853,7 @@ class Package(object):
TODO: move this method to database.py?
"""
dependents = []
- for spec in spack.installed_db.query():
+ for spec in spack.store.db.query():
if self.name == spec.name:
continue
# XXX(deptype): Should build dependencies not count here?
@@ -658,6 +864,29 @@ class Package(object):
return dependents
@property
+ def prefix_lock(self):
+ """Prefix lock is a byte range lock on the nth byte of a file.
+
+ The lock file is ``spack.store.db.prefix_lock`` -- the DB
+ tells us what to call it and it lives alongside the install DB.
+
+ n is the sys.maxsize-bit prefix of the DAG hash. This makes
+ likelihood of collision is very low AND it gives us
+ readers-writer lock semantics with just a single lockfile, so no
+ cleanup required.
+ """
+ if self._prefix_lock is None:
+ prefix = self.spec.prefix
+ if prefix not in Package.prefix_locks:
+ Package.prefix_locks[prefix] = llnl.util.lock.Lock(
+ spack.store.db.prefix_lock_path,
+ self.spec.dag_hash_bit_prefix(bit_length(sys.maxsize)), 1)
+
+ self._prefix_lock = Package.prefix_locks[prefix]
+
+ return self._prefix_lock
+
+ @property
def prefix(self):
"""Get the prefix into which this package should be installed."""
return self.spec.prefix
@@ -687,7 +916,7 @@ class Package(object):
Removes the prefix for a package along with any empty parent
directories
"""
- spack.install_layout.remove_install_directory(self.spec)
+ spack.store.layout.remove_install_directory(self.spec)
def do_fetch(self, mirror_only=False):
"""
@@ -714,7 +943,7 @@ class Package(object):
if not ignore_checksum:
raise FetchError("Will not fetch %s" %
- self.spec.format('$_$@'), checksum_msg)
+ self.spec.format('$_$@'), ck_msg)
self.stage.fetch(mirror_only)
@@ -818,12 +1047,35 @@ class Package(object):
return namespace
def do_fake_install(self):
- """Make a fake install directory contaiing a 'fake' file in bin."""
+ """Make a fake install directory containing a 'fake' file in bin."""
+ # FIXME : Make this part of the 'install' behavior ?
mkdirp(self.prefix.bin)
touch(join_path(self.prefix.bin, 'fake'))
+ mkdirp(self.prefix.include)
mkdirp(self.prefix.lib)
mkdirp(self.prefix.man1)
+ def _if_make_target_execute(self, target):
+ try:
+ # Check if we have a makefile
+ file = [x for x in ('Makefile', 'makefile') if os.path.exists(x)]
+ file = file.pop()
+ except IndexError:
+ tty.msg('No Makefile found in the build directory')
+ return
+
+ # Check if 'target' is in the makefile
+ regex = re.compile('^' + target + ':')
+ with open(file, 'r') as f:
+ matches = [line for line in f.readlines() if regex.match(line)]
+
+ if not matches:
+ tty.msg('Target \'' + target + ':\' not found in Makefile')
+ return
+
+ # Execute target
+ inspect.getmodule(self).make(target)
+
def _get_needed_resources(self):
resources = []
# Select the resources that are needed for this build
@@ -841,38 +1093,55 @@ class Package(object):
resource_stage_folder = '-'.join(pieces)
return resource_stage_folder
- install_phases = set(['configure', 'build', 'install', 'provenance'])
+ @contextlib.contextmanager
+ def _prefix_read_lock(self):
+ try:
+ self.prefix_lock.acquire_read(60)
+ yield self
+ finally:
+ self.prefix_lock.release_read()
+
+ @contextlib.contextmanager
+ def _prefix_write_lock(self):
+ try:
+ self.prefix_lock.acquire_write(60)
+ yield self
+ finally:
+ self.prefix_lock.release_write()
def do_install(self,
keep_prefix=False,
keep_stage=False,
- ignore_deps=False,
+ install_deps=True,
skip_patch=False,
verbose=False,
make_jobs=None,
run_tests=False,
fake=False,
explicit=False,
- dirty=False,
- install_phases=install_phases):
+ dirty=None,
+ **kwargs):
"""Called by commands to install a package and its dependencies.
Package implementations should override install() to describe
their build process.
- Args:
- keep_prefix -- Keep install prefix on failure. By default, destroys it.
- 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 -- Don't 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.
- verbose -- Display verbose build output (by default, suppresses it)
- dirty -- Don't clean the build environment before installing.
- make_jobs -- Number of make jobs to use for install. Default is ncpus
- run_tests -- Runn tests within the package's install()
+ :param keep_prefix: Keep install prefix on failure. By default, \
+ destroys it.
+ :param 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.
+ :param install_deps: Install dependencies before installing this \
+ package
+ :param fake: Don't really build; install fake stub files instead.
+ :param skip_patch: Skip patch stage of build if True.
+ :param verbose: Display verbose build output (by default, suppresses \
+ it)
+ :param dirty: Don't clean the build environment before installing.
+ :param make_jobs: Number of make jobs to use for install. Default is \
+ ncpus
+ :param force: Install again, even if already installed.
+ :param run_tests: Run tests within the package's install()
"""
if not self.spec.concrete:
raise ValueError("Can only install concrete packages: %s."
@@ -885,28 +1154,41 @@ class Package(object):
return
# Ensure package is not already installed
- layout = spack.install_layout
- if 'install' in install_phases and layout.check_installed(self.spec):
- tty.msg("%s is already installed in %s" % (self.name, self.prefix))
- rec = spack.installed_db.get_record(self.spec)
- if (not rec.explicit) and explicit:
- with spack.installed_db.write_transaction():
- rec = spack.installed_db.get_record(self.spec)
- rec.explicit = True
- return
+ layout = spack.store.layout
+ with self._prefix_read_lock():
+ if layout.check_installed(self.spec):
+ tty.msg(
+ "%s is already installed in %s" % (self.name, self.prefix))
+ rec = spack.store.db.get_record(self.spec)
+ if (not rec.explicit) and explicit:
+ with spack.store.db.write_transaction():
+ rec = spack.store.db.get_record(self.spec)
+ rec.explicit = True
+ return
+
+ # Dirty argument takes precedence over dirty config setting.
+ if dirty is None:
+ dirty = spack.dirty
+
+ self._do_install_pop_kwargs(kwargs)
tty.msg("Installing %s" % self.name)
# First, install dependencies recursively.
- if not ignore_deps:
- self.do_install_dependencies(keep_prefix=keep_prefix,
- keep_stage=keep_stage,
- ignore_deps=ignore_deps,
- fake=fake,
- skip_patch=skip_patch,
- verbose=verbose,
- make_jobs=make_jobs,
- run_tests=run_tests)
+ if install_deps:
+ for dep in self.spec.dependencies():
+ dep.package.do_install(
+ keep_prefix=keep_prefix,
+ keep_stage=keep_stage,
+ install_deps=install_deps,
+ fake=fake,
+ skip_patch=skip_patch,
+ verbose=verbose,
+ make_jobs=make_jobs,
+ run_tests=run_tests,
+ dirty=dirty,
+ **kwargs
+ )
# Set run_tests flag before starting build.
self.run_tests = run_tests
@@ -915,7 +1197,7 @@ class Package(object):
self.make_jobs = make_jobs
# Then install the package itself.
- def build_process():
+ def build_process(input_stream):
"""Forked for each build. Has its own process and python
module space set up by build_environment.fork()."""
@@ -926,18 +1208,16 @@ class Package(object):
else:
self.do_stage()
- tty.msg("Building %s" % self.name)
+ tty.msg(
+ 'Building {0} [{1}]'.format(self.name, self.build_system_class)
+ )
self.stage.keep = keep_stage
- self.install_phases = install_phases
- self.build_directory = join_path(self.stage.path, 'spack-build')
- self.source_directory = self.stage.source_path
- with self.stage:
+ with contextlib.nested(self.stage, self._prefix_write_lock()):
# Run the pre-install hook in the child process after
# the directory is created.
spack.hooks.pre_install(self)
-
if fake:
self.do_fake_install()
else:
@@ -947,44 +1227,34 @@ class Package(object):
# Save the build environment in a file before building.
env_path = join_path(os.getcwd(), 'spack-build.env')
- try:
- # Redirect I/O to a build log (and optionally to
- # the terminal)
- log_path = join_path(os.getcwd(), 'spack-build.out')
- log_file = open(log_path, 'w')
- with log_output(log_file, verbose, sys.stdout.isatty(),
- True):
- dump_environment(env_path)
- self.install(self.spec, self.prefix)
-
- except ProcessError as e:
- # Annotate ProcessErrors with the location of
- # the build log
- e.build_log = log_path
- raise e
-
- # Ensure that something was actually installed.
- if 'install' in self.install_phases:
- self.sanity_check_prefix()
-
- # Copy provenance into the install directory on success
- if 'provenance' in self.install_phases:
- log_install_path = layout.build_log_path(self.spec)
- env_install_path = layout.build_env_path(self.spec)
- packages_dir = layout.build_packages_path(self.spec)
-
- # Remove first if we're overwriting another build
- # (can happen with spack setup)
- try:
- # log_install_path and env_install_path are here
- shutil.rmtree(packages_dir)
- except:
- pass
-
- install(log_path, log_install_path)
- install(env_path, env_install_path)
- dump_packages(self.spec, packages_dir)
-
+ # Redirect I/O to a build log (and optionally to
+ # the terminal)
+ log_path = join_path(os.getcwd(), 'spack-build.out')
+
+ # FIXME : refactor this assignment
+ self.log_path = log_path
+ self.env_path = env_path
+ dump_environment(env_path)
+
+ # Spawn a daemon that reads from a pipe and redirects
+ # everything to log_path
+ redirection_context = log_output(
+ log_path,
+ echo=verbose,
+ force_color=sys.stdout.isatty(),
+ debug=True,
+ input_stream=input_stream
+ )
+ with redirection_context as log_redirection:
+ for phase_name, phase in zip(self.phases, self._InstallPhase_phases): # NOQA: ignore=E501
+ tty.msg(
+ 'Executing phase : \'{0}\''.format(phase_name) # NOQA: ignore=E501
+ )
+ # Redirect stdout and stderr to daemon pipe
+ with log_redirection:
+ getattr(self, phase)(
+ self.spec, self.prefix)
+ self.log()
# Run post install hooks before build stage is removed.
spack.hooks.post_install(self)
@@ -1000,35 +1270,67 @@ class Package(object):
try:
# Create the install prefix and fork the build process.
- spack.install_layout.create_install_directory(self.spec)
- except directory_layout.InstallDirectoryAlreadyExistsError:
- if 'install' in install_phases:
- # Abort install if install directory exists.
- # But do NOT remove it (you'd be overwriting someone's data)
- tty.warn("Keeping existing install prefix in place.")
- raise
- else:
- # We're not installing anyway, so don't worry if someone
- # else has already written in the install directory
- pass
-
- try:
+ spack.store.layout.create_install_directory(self.spec)
+ # Fork a child to do the actual installation
spack.build_environment.fork(self, build_process, dirty=dirty)
- except:
- # remove the install prefix if anything went wrong during install.
+ # If we installed then we should keep the prefix
+ keep_prefix = True if self.last_phase is None else keep_prefix
+ # note: PARENT of the build process adds the new package to
+ # the database, so that we don't need to re-read from file.
+ spack.store.db.add(
+ self.spec, spack.store.layout, explicit=explicit
+ )
+ except directory_layout.InstallDirectoryAlreadyExistsError:
+ # Abort install if install directory exists.
+ # But do NOT remove it (you'd be overwriting someone else's stuff)
+ tty.warn("Keeping existing install prefix in place.")
+ raise
+ except StopIteration as e:
+ # A StopIteration exception means that do_install
+ # was asked to stop early from clients
+ tty.msg(e.message)
+ tty.msg(
+ 'Package stage directory : {0}'.format(self.stage.source_path)
+ )
+ finally:
+ # Remove the install prefix if anything went wrong during install.
if not keep_prefix:
self.remove_prefix()
- else:
- tty.warn("Keeping install prefix in place despite error.",
- "Spack will think this package is installed. " +
- "Manually remove this directory to fix:",
- self.prefix,
- wrap=False)
- raise
- # note: PARENT of the build process adds the new package to
- # the database, so that we don't need to re-read from file.
- spack.installed_db.add(self.spec, self.prefix, explicit=explicit)
+ def _do_install_pop_kwargs(self, kwargs):
+ """Pops kwargs from do_install before starting the installation
+
+ Args:
+ kwargs:
+ 'stop_at': last installation phase to be executed (or None)
+
+ """
+ self.last_phase = kwargs.pop('stop_at', None)
+ if self.last_phase is not None and self.last_phase not in self.phases:
+ tty.die('\'{0}\' is not an allowed phase for package {1}'
+ .format(self.last_phase, self.name))
+
+ def log(self):
+ # Copy provenance into the install directory on success
+ log_install_path = spack.store.layout.build_log_path(
+ self.spec)
+ env_install_path = spack.store.layout.build_env_path(
+ self.spec)
+ packages_dir = spack.store.layout.build_packages_path(
+ self.spec)
+
+ # Remove first if we're overwriting another build
+ # (can happen with spack setup)
+ try:
+ # log_install_path and env_install_path are inside this
+ shutil.rmtree(packages_dir)
+ except Exception:
+ # FIXME : this potentially catches too many things...
+ pass
+
+ install(self.log_path, log_install_path)
+ install(self.env_path, env_install_path)
+ dump_packages(self.spec, packages_dir)
def sanity_check_prefix(self):
"""This function checks whether install succeeded."""
@@ -1048,20 +1350,16 @@ class Package(object):
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)
+ installed.difference_update(
+ spack.store.layout.hidden_file_paths)
if not installed:
raise InstallError(
"Install failed for %s. Nothing was installed!" % self.name)
- def do_install_dependencies(self, **kwargs):
- # Pass along paths of dependencies here
- for dep in self.spec.dependencies():
- dep.package.do_install(**kwargs)
-
@property
def build_log_path(self):
if self.installed:
- return spack.install_layout.build_log_path(self.spec)
+ return spack.store.layout.build_log_path(self.spec)
else:
return join_path(self.stage.source_path, 'spack-build.out')
@@ -1108,20 +1406,20 @@ class Package(object):
def setup_dependent_environment(self, spack_env, run_env, dependent_spec):
"""Set up the environment of packages that depend on this one.
- This is similar to `setup_environment`, but it is used to
+ This is similar to ``setup_environment``, but it is used to
modify the compile and runtime environments of packages that
*depend* on this one. This gives packages like Python and
others that follow the extension model a way to implement
common environment or compile-time settings for dependencies.
- By default, this delegates to self.setup_environment()
+ By default, this delegates to ``self.setup_environment()``
- Example :
+ Example:
1. Installing python modules generally requires
- `PYTHONPATH` to point to the lib/pythonX.Y/site-packages
- directory in the module's install prefix. This could
- set that variable.
+ `PYTHONPATH` to point to the lib/pythonX.Y/site-packages
+ directory in the module's install prefix. This could
+ set that variable.
Args:
@@ -1140,7 +1438,6 @@ class Package(object):
This is useful if there are some common steps to installing
all extensions for a certain package.
-
"""
self.setup_environment(spack_env, run_env)
@@ -1188,20 +1485,13 @@ class Package(object):
"""
pass
- def install(self, spec, prefix):
- """
- Package implementations override this with their own configuration
- """
- raise InstallError("Package %s provides no install method!" %
- self.name)
-
def do_uninstall(self, force=False):
if not self.installed:
# prefix may not exist, but DB may be inconsistent. Try to fix by
# removing, but omit hooks.
- specs = spack.installed_db.query(self.spec, installed=True)
+ specs = spack.store.db.query(self.spec, installed=True)
if specs:
- spack.installed_db.remove(specs[0])
+ spack.store.db.remove(specs[0])
tty.msg("Removed stale DB entry for %s" % self.spec.short_spec)
return
else:
@@ -1213,11 +1503,12 @@ class Package(object):
raise PackageStillNeededError(self.spec, dependents)
# Pre-uninstall hook runs first.
- spack.hooks.pre_uninstall(self)
-
- # Uninstalling in Spack only requires removing the prefix.
- self.remove_prefix()
- spack.installed_db.remove(self.spec)
+ with self._prefix_write_lock():
+ spack.hooks.pre_uninstall(self)
+ # Uninstalling in Spack only requires removing the prefix.
+ self.remove_prefix()
+ #
+ spack.store.db.remove(self.spec)
tty.msg("Successfully uninstalled %s" % self.spec.short_spec)
# Once everything else is done, run post install hooks
@@ -1251,8 +1542,8 @@ class Package(object):
"""
self._sanity_check_extension()
- spack.install_layout.check_extension_conflict(self.extendee_spec,
- self.spec)
+ spack.store.layout.check_extension_conflict(
+ self.extendee_spec, self.spec)
# Activate any package dependencies that are also extensions.
if not force:
@@ -1263,7 +1554,7 @@ class Package(object):
self.extendee_spec.package.activate(self, **self.extendee_args)
- spack.install_layout.add_extension(self.extendee_spec, self.spec)
+ spack.store.layout.add_extension(self.extendee_spec, self.spec)
tty.msg("Activated extension %s for %s" %
(self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@")))
@@ -1278,7 +1569,7 @@ class Package(object):
"""
def ignore(filename):
- return (filename in spack.install_layout.hidden_file_paths or
+ return (filename in spack.store.layout.hidden_file_paths or
kwargs.get('ignore', lambda f: False)(filename))
tree = LinkTree(extension.prefix)
@@ -1296,9 +1587,11 @@ class Package(object):
# Allow a force deactivate to happen. This can unlink
# spurious files if something was corrupted.
if not force:
- spack.install_layout.check_activated(self.extendee_spec, self.spec)
+ spack.store.layout.check_activated(
+ self.extendee_spec, self.spec)
- activated = spack.install_layout.extension_map(self.extendee_spec)
+ activated = spack.store.layout.extension_map(
+ self.extendee_spec)
for name, aspec in activated.items():
if aspec == self.spec:
continue
@@ -1314,8 +1607,8 @@ class Package(object):
# redundant activation check -- makes SURE the spec is not
# still activated even if something was wrong above.
if self.activated:
- spack.install_layout.remove_extension(self.extendee_spec,
- self.spec)
+ spack.store.layout.remove_extension(
+ self.extendee_spec, self.spec)
tty.msg("Deactivated extension %s for %s" %
(self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@")))
@@ -1331,7 +1624,7 @@ class Package(object):
"""
def ignore(filename):
- return (filename in spack.install_layout.hidden_file_paths or
+ return (filename in spack.store.layout.hidden_file_paths or
kwargs.get('ignore', lambda f: False)(filename))
tree = LinkTree(extension.prefix)
@@ -1404,6 +1697,16 @@ class Package(object):
return " ".join("-Wl,-rpath,%s" % p for p in self.rpath)
+class Package(PackageBase):
+ phases = ['install']
+ # To be used in UI queries that require to know which
+ # build-system class we are using
+ build_system_class = 'Package'
+ # This will be used as a registration decorator in user
+ # packages, if need be
+ PackageBase.sanity_check('install')(PackageBase.sanity_check_prefix)
+
+
def install_dependency_symlinks(pkg, spec, prefix):
"""Execute a dummy install and flatten dependencies"""
flatten_dependencies(spec, prefix)
@@ -1422,7 +1725,7 @@ def flatten_dependencies(spec, flat_dir):
for dep in spec.traverse(root=False):
name = dep.name
- dep_path = spack.install_layout.path_for_spec(dep)
+ dep_path = spack.store.layout.path_for_spec(dep)
dep_files = LinkTree(dep_path)
os.mkdir(flat_dir + '/' + name)
@@ -1434,16 +1737,6 @@ def flatten_dependencies(spec, flat_dir):
dep_files.merge(flat_dir + '/' + name)
-def validate_package_url(url_string):
- """Determine whether spack can handle a particular URL or not."""
- url = urlparse(url_string)
- if url.scheme not in _ALLOWED_URL_SCHEMES:
- tty.die("Invalid protocol in URL: '%s'" % url_string)
-
- if not allowed_archive(url_string):
- tty.die("Invalid file type in URL: '%s'" % url_string)
-
-
def dump_packages(spec, path):
"""Dump all package information for a spec and its dependencies.
@@ -1461,7 +1754,7 @@ def dump_packages(spec, path):
if node is not spec:
# Locate the dependency package in the install tree and find
# its provenance information.
- source = spack.install_layout.build_packages_path(node)
+ source = spack.store.layout.build_packages_path(node)
source_repo_root = join_path(source, node.namespace)
# There's no provenance installed for the source package. Skip it.
@@ -1514,166 +1807,6 @@ def _hms(seconds):
return ' '.join(parts)
-class StagedPackage(Package):
- """A Package subclass where the install() is split up into stages."""
-
- def install_setup(self):
- """Creates a spack_setup.py script to configure the package later."""
- raise InstallError(
- "Package %s provides no install_setup() method!" % self.name)
-
- def install_configure(self):
- """Runs the configure process."""
- raise InstallError(
- "Package %s provides no install_configure() method!" % self.name)
-
- def install_build(self):
- """Runs the build process."""
- raise InstallError(
- "Package %s provides no install_build() method!" % self.name)
-
- def install_install(self):
- """Runs the install process."""
- raise InstallError(
- "Package %s provides no install_install() method!" % self.name)
-
- def install(self, spec, prefix):
- if 'setup' in self.install_phases:
- self.install_setup()
-
- if 'configure' in self.install_phases:
- self.install_configure()
-
- if 'build' in self.install_phases:
- self.install_build()
-
- if 'install' in self.install_phases:
- self.install_install()
- else:
- # Create a dummy file so the build doesn't fail.
- # That way, the module file will also be created.
- with open(os.path.join(prefix, 'dummy'), 'w'):
- pass
-
-
-# stackoverflow.com/questions/12791997/how-do-you-do-a-simple-chmod-x-from-within-python
-def make_executable(path):
- mode = os.stat(path).st_mode
- mode |= (mode & 0o444) >> 2 # copy R bits to X
- os.chmod(path, mode)
-
-
-class CMakePackage(StagedPackage):
-
- def make_make(self):
- import multiprocessing
- # number of jobs spack will to build with.
- jobs = multiprocessing.cpu_count()
- if not self.parallel:
- jobs = 1
- elif self.make_jobs:
- jobs = self.make_jobs
-
- make = spack.build_environment.MakeExecutable('make', jobs)
- return make
-
- def configure_args(self):
- """Returns package-specific arguments to be provided to
- the configure command.
- """
- return list()
-
- def configure_env(self):
- """Returns package-specific environment under which the
- configure command should be run.
- """
- return dict()
-
- def transitive_inc_path(self):
- return ';'.join(
- os.path.join(dep, 'include')
- for dep in os.environ['SPACK_DEPENDENCIES'].split(os.pathsep)
- )
-
- def install_setup(self):
- cmd = [str(which('cmake'))]
- cmd += spack.build_environment.get_std_cmake_args(self)
- cmd += ['-DCMAKE_INSTALL_PREFIX=%s' % os.environ['SPACK_PREFIX'],
- '-DCMAKE_C_COMPILER=%s' % os.environ['SPACK_CC'],
- '-DCMAKE_CXX_COMPILER=%s' % os.environ['SPACK_CXX'],
- '-DCMAKE_Fortran_COMPILER=%s' % os.environ['SPACK_FC']]
- cmd += self.configure_args()
-
- env = {
- 'PATH': os.environ['PATH'],
- 'SPACK_TRANSITIVE_INCLUDE_PATH': self.transitive_inc_path(),
- 'CMAKE_PREFIX_PATH': os.environ['CMAKE_PREFIX_PATH']
- }
-
- setup_fname = 'spconfig.py'
- with open(setup_fname, 'w') as fout:
- fout.write(r"""#!%s
-#
-
-import sys
-import os
-import subprocess
-
-def cmdlist(str):
- return list(x.strip().replace("'",'') for x in str.split('\n') if x)
-env = dict(os.environ)
-""" % sys.executable)
-
- env_vars = sorted(list(env.keys()))
- for name in env_vars:
- val = env[name]
- if string.find(name, 'PATH') < 0:
- fout.write('env[%s] = %s\n' % (repr(name), repr(val)))
- else:
- if name == 'SPACK_TRANSITIVE_INCLUDE_PATH':
- sep = ';'
- else:
- sep = ':'
-
- fout.write('env[%s] = "%s".join(cmdlist("""\n'
- % (repr(name), sep))
- for part in string.split(val, sep):
- fout.write(' %s\n' % part)
- fout.write('"""))\n')
-
- fout.write("env['CMAKE_TRANSITIVE_INCLUDE_PATH'] = "
- "env['SPACK_TRANSITIVE_INCLUDE_PATH'] # Deprecated\n")
- fout.write('\ncmd = cmdlist("""\n')
- fout.write('%s\n' % cmd[0])
- for arg in cmd[1:]:
- fout.write(' %s\n' % arg)
- fout.write('""") + sys.argv[1:]\n')
- fout.write('\nproc = subprocess.Popen(cmd, env=env)\n')
- fout.write('proc.wait()\n')
- make_executable(setup_fname)
-
- def install_configure(self):
- cmake = which('cmake')
- with working_dir(self.build_directory, create=True):
- env = os.environ
- env.update(self.configure_env())
- env['SPACK_TRANSITIVE_INCLUDE_PATH'] = self.transitive_inc_path()
-
- options = self.configure_args()
- options += spack.build_environment.get_std_cmake_args(self)
- cmake(self.source_directory, *options)
-
- def install_build(self):
- make = self.make_make()
- with working_dir(self.build_directory, create=False):
- make()
-
- def install_install(self):
- make = self.make_make()
- with working_dir(self.build_directory, create=False):
- make('install')
-
-
class FetchError(spack.error.SpackError):
"""Raised when something goes wrong during fetch."""
diff --git a/lib/spack/spack/patch.py b/lib/spack/spack/patch.py
index 0bd9f5d29d..ee83748319 100644
--- a/lib/spack/spack/patch.py
+++ b/lib/spack/spack/patch.py
@@ -24,62 +24,106 @@
##############################################################################
import os
-from llnl.util.filesystem import join_path
-
import spack
-import spack.stage
import spack.error
+import spack.stage
+import spack.fetch_strategy as fs
+from llnl.util.filesystem import join_path
from spack.util.executable import which
-# Patch tool for patching archives.
-_patch = which("patch", required=True)
-
class Patch(object):
- """This class describes a patch to be applied to some expanded
- source code."""
+ """Base class to describe a patch that needs to be applied to some
+ expanded source code.
+ """
+
+ @staticmethod
+ def create(pkg, path_or_url, level, **kwargs):
+ """
+ Factory method that creates an instance of some class derived from
+ Patch
+
+ Args:
+ pkg: package that needs to be patched
+ path_or_url: path or url where the patch is found
+ level: patch level
+
+ Returns:
+ instance of some Patch class
+ """
+ # Check if we are dealing with a URL
+ if '://' in path_or_url:
+ return UrlPatch(pkg, path_or_url, level, **kwargs)
+ # Assume patches are stored in the repository
+ return FilePatch(pkg, path_or_url, level)
def __init__(self, pkg, path_or_url, level):
- self.pkg_name = pkg.name
+ # Check on level (must be an integer > 0)
+ if not isinstance(level, int) or not level >= 0:
+ raise ValueError("Patch level needs to be a non-negative integer.")
+ # Attributes shared by all patch subclasses
self.path_or_url = path_or_url
- self.path = None
- self.url = None
self.level = level
+ # self.path needs to be computed by derived classes
+ # before a call to apply
+ self.path = None
if not isinstance(self.level, int) or not self.level >= 0:
raise ValueError("Patch level needs to be a non-negative integer.")
- if '://' in path_or_url:
- self.url = path_or_url
- else:
- pkg_dir = spack.repo.dirname_for_package_name(self.pkg_name)
- self.path = join_path(pkg_dir, path_or_url)
- if not os.path.isfile(self.path):
- raise NoSuchPatchFileError(pkg_name, self.path)
-
def apply(self, stage):
- """Fetch this patch, if necessary, and apply it to the source
- code in the supplied stage.
+ """Apply the patch at self.path to the source code in the
+ supplied stage
+
+ Args:
+ stage: stage for the package that needs to be patched
"""
stage.chdir_to_source()
+ # Use -N to allow the same patches to be applied multiple times.
+ _patch = which("patch", required=True)
+ _patch('-s', '-p', str(self.level), '-i', self.path)
+
+
+class FilePatch(Patch):
+ """Describes a patch that is retrieved from a file in the repository"""
+ def __init__(self, pkg, path_or_url, level):
+ super(FilePatch, self).__init__(pkg, path_or_url, level)
- patch_stage = None
- try:
- if self.url:
- # use an anonymous stage to fetch the patch if it is a URL
- patch_stage = spack.stage.Stage(self.url)
- patch_stage.fetch()
- patch_file = patch_stage.archive_file
- else:
- patch_file = self.path
-
- # Use -N to allow the same patches to be applied multiple times.
- _patch('-s', '-p', str(self.level), '-i', patch_file)
-
- finally:
- if patch_stage:
- patch_stage.destroy()
+ pkg_dir = spack.repo.dirname_for_package_name(pkg.name)
+ self.path = join_path(pkg_dir, path_or_url)
+ if not os.path.isfile(self.path):
+ raise NoSuchPatchFileError(pkg.name, self.path)
+
+
+class UrlPatch(Patch):
+ """Describes a patch that is retrieved from a URL"""
+ def __init__(self, pkg, path_or_url, level, **kwargs):
+ super(UrlPatch, self).__init__(pkg, path_or_url, level)
+ self.url = path_or_url
+ self.md5 = kwargs.get('md5')
+
+ def apply(self, stage):
+ """Retrieve the patch in a temporary stage, computes
+ self.path and calls `super().apply(stage)`
+
+ Args:
+ stage: stage for the package that needs to be patched
+ """
+ fetcher = fs.URLFetchStrategy(self.url, digest=self.md5)
+ mirror = join_path(
+ os.path.dirname(stage.mirror_path),
+ os.path.basename(self.url)
+ )
+ with spack.stage.Stage(fetcher, mirror_path=mirror) as patch_stage:
+ patch_stage.fetch()
+ patch_stage.check()
+ patch_stage.cache_local()
+ patch_stage.expand_archive()
+ self.path = os.path.abspath(
+ os.listdir(patch_stage.path).pop()
+ )
+ super(UrlPatch, self).apply(stage)
class NoSuchPatchFileError(spack.error.SpackError):
diff --git a/lib/spack/spack/platforms/cray.py b/lib/spack/spack/platforms/cray.py
index 0059b49ff1..94e2949e4a 100644
--- a/lib/spack/spack/platforms/cray.py
+++ b/lib/spack/spack/platforms/cray.py
@@ -1,44 +1,28 @@
import os
import re
-import spack.config
import llnl.util.tty as tty
+from spack import build_env_path
from spack.util.executable import which
from spack.architecture import Platform, Target, NoPlatformError
from spack.operating_systems.linux_distro import LinuxDistro
from spack.operating_systems.cnl import Cnl
from llnl.util.filesystem import join_path
-# Craype- module prefixes that are not valid CPU targets.
-NON_TARGETS = ('hugepages', 'network', 'target', 'accel', 'xtpe')
-
-
-def _target_from_clean_env(name):
- '''Return the default back_end target as loaded in a clean login session.
-
- A bash subshell is launched with a wiped environment and the list of loaded
- modules is parsed for the first acceptable CrayPE target.
- '''
- # Based on the incantation:
- # echo "$(env - USER=$USER /bin/bash -l -c 'module list -lt')"
- targets = []
- if name != 'front_end':
- env = which('env')
- env.add_default_arg('-')
- # CAUTION - $USER is generally needed to initialize the environment.
- # There may be other variables needed for general success.
- output = env('USER=%s' % os.environ['USER'],
- '/bin/bash', '--noprofile', '--norc', '-c',
- '. /etc/profile; module list -lt',
- output=str, error=str)
- default_modules = [i for i in output.splitlines()
- if len(i.split()) == 1]
- tty.debug("Found default modules:",
- *[" " + mod for mod in default_modules])
- pattern = 'craype-(?!{0})(\S*)'.format('|'.join(NON_TARGETS))
- for mod in default_modules:
- if 'craype-' in mod:
- targets.extend(re.findall(pattern, mod))
- return targets[0] if targets else None
+
+def _get_modules_in_modulecmd_output(output):
+ '''Return list of valid modules parsed from modulecmd output string.'''
+ return [i for i in output.splitlines()
+ if len(i.split()) == 1]
+
+
+def _fill_craype_targets_from_modules(targets, modules):
+ '''Extend CrayPE CPU targets list with those found in list of modules.'''
+ # Craype- module prefixes that are not valid CPU targets.
+ non_targets = ('hugepages', 'network', 'target', 'accel', 'xtpe')
+ pattern = r'craype-(?!{0})(\S*)'.format('|'.join(non_targets))
+ for mod in modules:
+ if 'craype-' in mod:
+ targets.extend(re.findall(pattern, mod))
class Cray(Platform):
@@ -56,26 +40,26 @@ class Cray(Platform):
'''
super(Cray, self).__init__('cray')
- # Get targets from config or make best guess from environment:
- conf = spack.config.get_config('targets')
+ # Make all craype targets available.
+ for target in self._avail_targets():
+ name = target.replace('-', '_')
+ self.add_target(name, Target(name, 'craype-%s' % target))
+
+ # Get aliased targets from config or best guess from environment:
for name in ('front_end', 'back_end'):
_target = getattr(self, name, None)
if _target is None:
_target = os.environ.get('SPACK_' + name.upper())
- if _target is None:
- _target = conf.get(name)
- if _target is None:
- _target = _target_from_clean_env(name)
- setattr(self, name, _target)
-
+ if _target is None and name == 'back_end':
+ _target = self._default_target_from_env()
if _target is not None:
- self.add_target(name, Target(_target, 'craype-' + _target))
- self.add_target(_target, Target(_target, 'craype-' + _target))
+ safe_name = _target.replace('-', '_')
+ setattr(self, name, safe_name)
+ self.add_target(name, self.targets[safe_name])
if self.back_end is not None:
self.default = self.back_end
- self.add_target(
- 'default', Target(self.default, 'craype-' + self.default))
+ self.add_target('default', self.targets[self.back_end])
else:
raise NoPlatformError()
@@ -90,16 +74,54 @@ class Cray(Platform):
self.add_operating_system(self.front_os, front_distro)
@classmethod
- def setup_platform_environment(self, pkg, env):
+ def setup_platform_environment(cls, pkg, env):
""" Change the linker to default dynamic to be more
similar to linux/standard linker behavior
"""
env.set('CRAYPE_LINK_TYPE', 'dynamic')
- cray_wrapper_names = join_path(spack.build_env_path, 'cray')
+ cray_wrapper_names = join_path(build_env_path, 'cray')
if os.path.isdir(cray_wrapper_names):
env.prepend_path('PATH', cray_wrapper_names)
env.prepend_path('SPACK_ENV_PATH', cray_wrapper_names)
@classmethod
- def detect(self):
+ def detect(cls):
return os.environ.get('CRAYPE_VERSION') is not None
+
+ def _default_target_from_env(self):
+ '''Set and return the default CrayPE target loaded in a clean login
+ session.
+
+ A bash subshell is launched with a wiped environment and the list of
+ loaded modules is parsed for the first acceptable CrayPE target.
+ '''
+ # Based on the incantation:
+ # echo "$(env - USER=$USER /bin/bash -l -c 'module list -lt')"
+ if getattr(self, 'default', None) is None:
+ env = which('env')
+ env.add_default_arg('-')
+ # CAUTION - $USER is generally needed in the sub-environment.
+ # There may be other variables needed for general success.
+ output = env('USER=%s' % os.environ['USER'],
+ 'HOME=%s' % os.environ['HOME'],
+ '/bin/bash', '--noprofile', '--norc', '-c',
+ '. /etc/profile; module list -lt',
+ output=str, error=str)
+ self._defmods = _get_modules_in_modulecmd_output(output)
+ targets = []
+ _fill_craype_targets_from_modules(targets, self._defmods)
+ self.default = targets[0] if targets else None
+ tty.debug("Found default modules:",
+ *[" %s" % mod for mod in self._defmods])
+ return self.default
+
+ def _avail_targets(self):
+ '''Return a list of available CrayPE CPU targets.'''
+ if getattr(self, '_craype_targets', None) is None:
+ module = which('modulecmd', required=True)
+ module.add_default_arg('python')
+ output = module('avail', '-t', 'craype-', output=str, error=str)
+ craype_modules = _get_modules_in_modulecmd_output(output)
+ self._craype_targets = targets = []
+ _fill_craype_targets_from_modules(targets, craype_modules)
+ return self._craype_targets
diff --git a/lib/spack/spack/platforms/darwin.py b/lib/spack/spack/platforms/darwin.py
index 974ce3a3f9..ffefd36546 100644
--- a/lib/spack/spack/platforms/darwin.py
+++ b/lib/spack/spack/platforms/darwin.py
@@ -1,4 +1,4 @@
-import subprocess
+import platform
from spack.architecture import Platform, Target
from spack.operating_systems.mac_os import MacOs
@@ -22,6 +22,4 @@ class Darwin(Platform):
@classmethod
def detect(self):
- platform = subprocess.Popen(['uname', '-a'], stdout=subprocess.PIPE)
- platform, _ = platform.communicate()
- return 'darwin' in platform.strip().lower()
+ return 'darwin' in platform.system().lower()
diff --git a/lib/spack/spack/platforms/linux.py b/lib/spack/spack/platforms/linux.py
index 38d2cdbfec..cee0cfd6e9 100644
--- a/lib/spack/spack/platforms/linux.py
+++ b/lib/spack/spack/platforms/linux.py
@@ -1,4 +1,3 @@
-import subprocess
import platform
from spack.architecture import Platform, Target
from spack.operating_systems.linux_distro import LinuxDistro
@@ -27,6 +26,4 @@ class Linux(Platform):
@classmethod
def detect(self):
- platform = subprocess.Popen(['uname', '-a'], stdout=subprocess.PIPE)
- platform, _ = platform.communicate()
- return 'linux' in platform.strip().lower()
+ return 'linux' in platform.system().lower()
diff --git a/lib/spack/spack/preferred_packages.py b/lib/spack/spack/preferred_packages.py
index 45a41c8e2b..08f9c3cfa8 100644
--- a/lib/spack/spack/preferred_packages.py
+++ b/lib/spack/spack/preferred_packages.py
@@ -28,9 +28,6 @@ from spack.version import *
class PreferredPackages(object):
- # Arbitrary, but consistent
- _default_order = {'compiler': ['gcc', 'intel', 'clang', 'pgi', 'xlc']}
-
def __init__(self):
self.preferred = spack.config.get_config('packages')
self._spec_for_pkgname_cache = {}
@@ -128,9 +125,6 @@ class PreferredPackages(object):
key = (pkgname, component, second_key)
if key not 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]
diff --git a/lib/spack/spack/provider_index.py b/lib/spack/spack/provider_index.py
index 3f9cd285e7..6bcf98009e 100644
--- a/lib/spack/spack/provider_index.py
+++ b/lib/spack/spack/provider_index.py
@@ -28,10 +28,11 @@ The ``virtual`` module contains utility classes for virtual dependencies.
from itertools import product as iproduct
from pprint import pformat
-import yaml
+import spack.util.spack_yaml as syaml
from yaml.error import MarkedYAMLError
import spack
+import spack.error
class ProviderIndex(object):
@@ -189,23 +190,22 @@ class ProviderIndex(object):
lambda vpkg, pset: [
vpkg.to_node_dict(), [p.to_node_dict() for p in pset]], list)
- yaml.dump({'provider_index': {'providers': provider_list}},
- stream=stream)
+ syaml.dump({'provider_index': {'providers': provider_list}},
+ stream=stream)
@staticmethod
def from_yaml(stream):
try:
- yfile = yaml.load(stream)
+ yfile = syaml.load(stream)
except MarkedYAMLError, e:
raise spack.spec.SpackYAMLError(
"error parsing YAML ProviderIndex cache:", str(e))
if not isinstance(yfile, dict):
- raise spack.spec.SpackYAMLError(
- "YAML ProviderIndex was not a dict.")
+ raise ProviderIndexError("YAML ProviderIndex was not a dict.")
if 'provider_index' not in yfile:
- raise spack.spec.SpackYAMLError(
+ raise ProviderIndexError(
"YAML ProviderIndex does not start with 'provider_index'")
index = ProviderIndex()
@@ -232,7 +232,8 @@ class ProviderIndex(object):
spdict[provided_spec] = opdict[provided_spec]
continue
- spdict[provided_spec] += opdict[provided_spec]
+ spdict[provided_spec] = \
+ spdict[provided_spec].union(opdict[provided_spec])
def remove_provider(self, pkg_name):
"""Remove a provider from the ProviderIndex."""
@@ -291,3 +292,7 @@ def _transform(providers, transform_fun, out_mapping_type=dict):
(name, out_mapping_type([
transform_fun(vpkg, pset) for vpkg, pset in mapiter(mappings)]))
for name, mappings in providers.items())
+
+
+class ProviderIndexError(spack.error.SpackError):
+ """Raised when there is a problem with a ProviderIndex."""
diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py
index 2d8dc39648..94b79accdb 100644
--- a/lib/spack/spack/repository.py
+++ b/lib/spack/spack/repository.py
@@ -42,9 +42,9 @@ from llnl.util.filesystem import *
import spack
import spack.error
-import spack.config
import spack.spec
from spack.provider_index import ProviderIndex
+from spack.util.path import canonicalize_path
from spack.util.naming import *
#
@@ -94,19 +94,6 @@ class SpackNamespace(ModuleType):
return getattr(self, name)
-def substitute_spack_prefix(path):
- """Replaces instances of $spack with Spack's prefix."""
- return re.sub(r'^\$spack', spack.prefix, path)
-
-
-def canonicalize_path(path):
- """Substitute $spack, expand user home, take abspath."""
- path = substitute_spack_prefix(path)
- path = os.path.expanduser(path)
- path = os.path.abspath(path)
- return path
-
-
class RepoPath(object):
"""A RepoPath is a list of repos that function as one.
@@ -128,6 +115,7 @@ class RepoPath(object):
# If repo_dirs is empty, just use the configuration
if not repo_dirs:
+ import spack.config
repo_dirs = spack.config.get_config('repos')
if not repo_dirs:
raise NoRepoConfiguredError(
@@ -198,15 +186,19 @@ class RepoPath(object):
def get_repo(self, namespace, default=NOT_PROVIDED):
"""Get a repository by namespace.
- Arguments
- namespace
- Look up this namespace in the RepoPath, and return
- it if found.
-
- Optional Arguments
- default
- If default is provided, return it when the namespace
- isn't found. If not, raise an UnknownNamespaceError.
+
+ Arguments:
+
+ namespace:
+
+ Look up this namespace in the RepoPath, and return it if found.
+
+ Optional Arguments:
+
+ default:
+
+ If default is provided, return it when the namespace
+ isn't found. If not, raise an UnknownNamespaceError.
"""
fullspace = '%s.%s' % (self.super_namespace, namespace)
if fullspace not in self.by_namespace:
@@ -628,12 +620,12 @@ class Repo(object):
# Read the old ProviderIndex, or make a new one.
key = self._cache_file
- index_existed = spack.user_cache.init_entry(key)
+ index_existed = spack.misc_cache.init_entry(key)
if index_existed and not self._needs_update:
- with spack.user_cache.read_transaction(key) as f:
+ with spack.misc_cache.read_transaction(key) as f:
self._provider_index = ProviderIndex.from_yaml(f)
else:
- with spack.user_cache.write_transaction(key) as (old, new):
+ with spack.misc_cache.write_transaction(key) as (old, new):
if old:
self._provider_index = ProviderIndex.from_yaml(old)
else:
@@ -709,7 +701,7 @@ class Repo(object):
self._all_package_names = []
# Get index modification time.
- index_mtime = spack.user_cache.mtime(self._cache_file)
+ index_mtime = spack.misc_cache.mtime(self._cache_file)
for pkg_name in os.listdir(self.packages_path):
# Skip non-directories in the package root.
diff --git a/lib/spack/spack/schema/compilers.py b/lib/spack/spack/schema/compilers.py
index 2ffac03a66..282eddf91b 100644
--- a/lib/spack/spack/schema/compilers.py
+++ b/lib/spack/spack/schema/compilers.py
@@ -22,7 +22,11 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for compiler configuration files."""
+"""Schema for compilers.yaml configuration file.
+
+.. literalinclude:: ../spack/schema/compilers.py
+ :lines: 32-
+"""
schema = {
@@ -31,7 +35,7 @@ schema = {
'type': 'object',
'additionalProperties': False,
'patternProperties': {
- 'compilers:?': { # optional colon for overriding site config.
+ 'compilers': {
'type': 'array',
'items': {
'compiler': {
@@ -52,7 +56,11 @@ schema = {
'f77': {'anyOf': [{'type': 'string'},
{'type': 'null'}]},
'fc': {'anyOf': [{'type': 'string'},
- {'type': 'null'}]},
+ {'type': 'null'}]}}},
+ 'flags': {
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'properties': {
'cflags': {'anyOf': [{'type': 'string'},
{'type': 'null'}]},
'cxxflags': {'anyOf': [{'type': 'string'},
@@ -71,7 +79,27 @@ schema = {
{'type': 'null'}]},
'modules': {'anyOf': [{'type': 'string'},
{'type': 'null'},
- {'type': 'array'}]}
+ {'type': 'array'}]},
+ 'environment': {
+ 'type': 'object',
+ 'default': {},
+ 'additionalProperties': False,
+ 'properties': {
+ 'set': {
+ 'type': 'object',
+ 'patternProperties': {
+ r'\w[\w-]*': { # variable name
+ 'type': 'string'
+ }
+ }
+ }
+ }
+ },
+ 'extra_rpaths': {
+ 'type': 'array',
+ 'default': [],
+ 'items': {'type': 'string'}
+ }
},
},
},
diff --git a/lib/spack/spack/schema/config.py b/lib/spack/spack/schema/config.py
new file mode 100644
index 0000000000..e51fa69afe
--- /dev/null
+++ b/lib/spack/spack/schema/config.py
@@ -0,0 +1,67 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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
+##############################################################################
+"""Schema for config.yaml configuration file.
+
+.. literalinclude:: ../spack/schema/config.py
+ :lines: 32-
+"""
+
+
+schema = {
+ '$schema': 'http://json-schema.org/schema#',
+ 'title': 'Spack module file configuration file schema',
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'patternProperties': {
+ 'config': {
+ 'type': 'object',
+ 'default': {},
+ 'additionalProperties': False,
+ 'properties': {
+ 'install_tree': {'type': 'string'},
+ 'build_stage': {
+ 'oneOf': [
+ {'type': 'string'},
+ {'type': 'array',
+ 'items': {'type': 'string'}}],
+ },
+ 'module_roots': {
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'properties': {
+ 'tcl': {'type': 'string'},
+ 'lmod': {'type': 'string'},
+ 'dotkit': {'type': 'string'},
+ },
+ },
+ 'source_cache': {'type': 'string'},
+ 'misc_cache': {'type': 'string'},
+ 'verify_ssl': {'type': 'boolean'},
+ 'checksum': {'type': 'boolean'},
+ 'dirty': {'type': 'boolean'},
+ }
+ },
+ },
+}
diff --git a/lib/spack/spack/schema/mirrors.py b/lib/spack/spack/schema/mirrors.py
index ff599b9c7d..60b865bb42 100644
--- a/lib/spack/spack/schema/mirrors.py
+++ b/lib/spack/spack/schema/mirrors.py
@@ -22,7 +22,11 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for mirror configuration files."""
+"""Schema for mirrors.yaml configuration file.
+
+.. literalinclude:: ../spack/schema/mirrors.py
+ :lines: 32-
+"""
schema = {
@@ -31,7 +35,7 @@ schema = {
'type': 'object',
'additionalProperties': False,
'patternProperties': {
- r'mirrors:?': {
+ r'mirrors': {
'type': 'object',
'default': {},
'additionalProperties': False,
diff --git a/lib/spack/spack/schema/modules.py b/lib/spack/spack/schema/modules.py
index f8066919f1..2059e14fa6 100644
--- a/lib/spack/spack/schema/modules.py
+++ b/lib/spack/spack/schema/modules.py
@@ -22,7 +22,11 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for mirror configuration files."""
+"""Schema for modules.yaml configuration file.
+
+.. literalinclude:: ../spack/schema/modules.py
+ :lines: 32-
+"""
schema = {
@@ -123,7 +127,7 @@ schema = {
}
},
'patternProperties': {
- r'modules:?': {
+ r'modules': {
'type': 'object',
'default': {},
'additionalProperties': False,
@@ -139,7 +143,20 @@ schema = {
'default': [],
'items': {
'type': 'string',
- 'enum': ['tcl', 'dotkit']}},
+ 'enum': ['tcl', 'dotkit', 'lmod']}},
+ 'lmod': {
+ 'allOf': [
+ # Base configuration
+ {'$ref': '#/definitions/module_type_configuration'},
+ {
+ 'core_compilers': {
+ '$ref': '#/definitions/array_of_strings'
+ },
+ 'hierarchical_scheme': {
+ '$ref': '#/definitions/array_of_strings'
+ }
+ } # Specific lmod extensions
+ ]},
'tcl': {
'allOf': [
# Base configuration
diff --git a/lib/spack/spack/schema/packages.py b/lib/spack/spack/schema/packages.py
index e19f3f533b..bf5648b1b7 100644
--- a/lib/spack/spack/schema/packages.py
+++ b/lib/spack/spack/schema/packages.py
@@ -22,7 +22,11 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for packages.yaml configuration files."""
+"""Schema for packages.yaml configuration files.
+
+.. literalinclude:: ../spack/schema/packages.py
+ :lines: 32-
+"""
schema = {
@@ -31,7 +35,7 @@ schema = {
'type': 'object',
'additionalProperties': False,
'patternProperties': {
- r'packages:?': {
+ r'packages': {
'type': 'object',
'default': {},
'additionalProperties': False,
diff --git a/lib/spack/spack/schema/repos.py b/lib/spack/spack/schema/repos.py
index 9f01942422..c7a3495ae1 100644
--- a/lib/spack/spack/schema/repos.py
+++ b/lib/spack/spack/schema/repos.py
@@ -22,7 +22,11 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-"""Schema for repository configuration files."""
+"""Schema for repos.yaml configuration file.
+
+.. literalinclude:: ../spack/schema/repos.py
+ :lines: 32-
+"""
schema = {
@@ -31,7 +35,7 @@ schema = {
'type': 'object',
'additionalProperties': False,
'patternProperties': {
- r'repos:?': {
+ r'repos': {
'type': 'array',
'default': [],
'items': {
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 316b7d6a8c..951e9ae652 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -98,11 +98,10 @@ expansion when it is the first character in an id typed on the command line.
import base64
import hashlib
import imp
-import sys
+import ctypes
from StringIO import StringIO
from operator import attrgetter
-import yaml
from yaml.error import MarkedYAMLError
import llnl.util.tty as tty
@@ -112,6 +111,7 @@ from llnl.util.tty.color import *
import spack
import spack.architecture
+import spack.store
import spack.compilers as compilers
import spack.error
import spack.parse
@@ -119,9 +119,45 @@ from spack.build_environment import get_path_from_module, load_module
from spack.util.naming import mod_to_class
from spack.util.prefix import Prefix
from spack.util.string import *
+import spack.util.spack_yaml as syaml
+from spack.util.spack_yaml import syaml_dict
+from spack.util.crypto import prefix_bits
from spack.version import *
from spack.provider_index import ProviderIndex
+__all__ = [
+ 'Spec',
+ 'alldeps',
+ 'nolink',
+ 'nobuild',
+ 'canonical_deptype',
+ 'validate_deptype',
+ 'parse',
+ 'parse_anonymous_spec',
+ 'SpecError',
+ 'SpecParseError',
+ 'DuplicateDependencyError',
+ 'DuplicateVariantError',
+ 'DuplicateCompilerSpecError',
+ 'UnsupportedCompilerError',
+ 'UnknownVariantError',
+ 'DuplicateArchitectureError',
+ 'InconsistentSpecError',
+ 'InvalidDependencyError',
+ 'InvalidDependencyTypeError',
+ 'NoProviderError',
+ 'MultipleProviderError',
+ 'UnsatisfiableSpecError',
+ 'UnsatisfiableSpecNameError',
+ 'UnsatisfiableVersionSpecError',
+ 'UnsatisfiableCompilerSpecError',
+ 'UnsatisfiableVariantSpecError',
+ 'UnsatisfiableCompilerFlagSpecError',
+ 'UnsatisfiableArchitectureSpecError',
+ 'UnsatisfiableProviderSpecError',
+ 'UnsatisfiableDependencySpecError',
+ 'SpackYAMLError',
+ 'AmbiguousHashError']
# Valid pattern for an identifier in Spack
identifier_re = r'\w[\w-]*'
@@ -155,12 +191,49 @@ _any_version = VersionList([':'])
# Special types of dependencies.
alldeps = ('build', 'link', 'run')
-nolink = ('build', 'run')
+nolink = ('build', 'run')
+nobuild = ('link', 'run')
+norun = ('link', 'build')
special_types = {
'alldeps': alldeps,
+ 'all': alldeps, # allow "all" as string but not symbol.
'nolink': nolink,
+ 'nobuild': nobuild,
+ 'norun': norun,
}
+legal_deps = tuple(special_types) + alldeps
+
+"""Max integer helps avoid passing too large a value to cyaml."""
+maxint = 2 ** (ctypes.sizeof(ctypes.c_int) * 8 - 1) - 1
+
+
+def validate_deptype(deptype):
+ if isinstance(deptype, str):
+ if deptype not in legal_deps:
+ raise InvalidDependencyTypeError(
+ "Invalid dependency type: %s" % deptype)
+
+ elif isinstance(deptype, (list, tuple)):
+ for t in deptype:
+ validate_deptype(t)
+
+ elif deptype is None:
+ raise InvalidDependencyTypeError("deptype cannot be None!")
+
+
+def canonical_deptype(deptype):
+ if deptype is None:
+ return alldeps
+
+ elif isinstance(deptype, str):
+ return special_types.get(deptype, (deptype,))
+
+ elif isinstance(deptype, (tuple, list)):
+ return (sum((canonical_deptype(d) for d in deptype), ()))
+
+ return deptype
+
def colorize_spec(spec):
"""Returns a spec colorized according to the colors specified in
@@ -266,9 +339,10 @@ class CompilerSpec(object):
return (self.name, self.versions)
def to_dict(self):
- d = {'name': self.name}
+ d = syaml_dict([('name', self.name)])
d.update(self.versions.to_dict())
- return {'compiler': d}
+
+ return syaml_dict([('compiler', d)])
@staticmethod
def from_dict(d):
@@ -401,10 +475,10 @@ class FlagMap(HashableMap):
def satisfies(self, other, strict=False):
if strict or (self.spec and self.spec._concrete):
- return all(f in self and set(self[f]) <= set(other[f])
+ return all(f in self and set(self[f]) == set(other[f])
for f in other)
else:
- return all(set(self[f]) <= set(other[f])
+ return all(set(self[f]) == set(other[f])
for f in other if (other[f] != [] and f in self))
def constrain(self, other):
@@ -504,6 +578,7 @@ class Spec(object):
self.variants.spec = self
self.namespace = other.namespace
self._hash = other._hash
+ self._cmp_key_cache = other._cmp_key_cache
# Specs are by default not assumed to be normal, but in some
# cases we've read them from a file want to assume normal.
@@ -524,6 +599,14 @@ class Spec(object):
# XXX(deptype): default deptypes
self._add_dependency(spec, ('build', 'link'))
+ def __getattr__(self, item):
+ """Delegate to self.package if the attribute is not in the spec"""
+ # This line is to avoid infinite recursion in case package is
+ # not present among self attributes
+ if item.endswith('libs'):
+ return getattr(self.package, item)
+ raise AttributeError()
+
def get_dependency(self, name):
dep = self._dependencies.get(name)
if dep is not None:
@@ -531,17 +614,8 @@ class Spec(object):
raise InvalidDependencyException(
self.name + " does not depend on " + comma_or(name))
- def _deptype_norm(self, deptype):
- if deptype is None:
- return alldeps
- # Force deptype to be a set object so that we can do set intersections.
- if isinstance(deptype, str):
- # Support special deptypes.
- return special_types.get(deptype, (deptype,))
- return deptype
-
def _find_deps(self, where, deptype):
- deptype = self._deptype_norm(deptype)
+ deptype = canonical_deptype(deptype)
return [dep.spec
for dep in where.values()
@@ -554,7 +628,7 @@ class Spec(object):
return self._find_deps(self._dependents, deptype)
def _find_deps_dict(self, where, deptype):
- deptype = self._deptype_norm(deptype)
+ deptype = canonical_deptype(deptype)
return dict((dep.spec.name, dep)
for dep in where.values()
@@ -783,7 +857,7 @@ class Spec(object):
children in the dependency DAG.
cover [=nodes|edges|paths]
- Determines how extensively to cover the dag. Possible vlaues:
+ Determines how extensively to cover the dag. Possible values:
'nodes': Visit each node in the dag only once. Every node
yielded by this function will be unique.
@@ -858,9 +932,10 @@ class Spec(object):
# Edge traversal yields but skips children of visited nodes
if not (key in visited and cover == 'edges'):
# This code determines direction and yields the children/parents
+
successors = deps
if direction == 'parents':
- successors = self.dependents_dict()
+ successors = self.dependents_dict() # TODO: deptype?
visited.add(key)
for name in sorted(successors):
@@ -890,57 +965,59 @@ class Spec(object):
@property
def prefix(self):
- return Prefix(spack.install_layout.path_for_spec(self))
+ return Prefix(spack.store.layout.path_for_spec(self))
def dag_hash(self, length=None):
- """
- Return a hash of the entire spec DAG, including connectivity.
- """
+ """Return a hash of the entire spec DAG, including connectivity."""
if self._hash:
return self._hash[:length]
else:
- # XXX(deptype): ignore 'build' dependencies here
- yaml_text = yaml.dump(
- self.to_node_dict(), default_flow_style=True, width=sys.maxint)
+ yaml_text = syaml.dump(
+ self.to_node_dict(), default_flow_style=True, width=maxint)
sha = hashlib.sha1(yaml_text)
- b32_hash = base64.b32encode(sha.digest()).lower()[:length]
+ b32_hash = base64.b32encode(sha.digest()).lower()
if self.concrete:
self._hash = b32_hash
- return b32_hash
+ return b32_hash[:length]
- def to_node_dict(self):
- d = {}
+ def dag_hash_bit_prefix(self, bits):
+ """Get the first <bits> bits of the DAG hash as an integer type."""
+ return base32_prefix_bits(self.dag_hash(), bits)
- params = dict((name, v.value) for name, v in self.variants.items())
- params.update(dict((name, value)
- for name, value in self.compiler_flags.items()))
+ def to_node_dict(self):
+ d = syaml_dict()
- if params:
- d['parameters'] = params
+ if self.versions:
+ d.update(self.versions.to_dict())
- if self.dependencies():
- deps = self.dependencies_dict(deptype=('link', 'run'))
- d['dependencies'] = dict(
- (name, {
- 'hash': dspec.spec.dag_hash(),
- 'type': [str(s) for s in dspec.deptypes]})
- for name, dspec in deps.items())
+ if self.compiler:
+ d.update(self.compiler.to_dict())
if self.namespace:
d['namespace'] = self.namespace
+ params = syaml_dict(sorted(
+ (name, v.value) for name, v in self.variants.items()))
+ params.update(sorted(self.compiler_flags.items()))
+ if params:
+ d['parameters'] = params
+
if self.architecture:
- # TODO: Fix the target.to_dict to account for the tuple
- # Want it to be a dict of dicts
d['arch'] = self.architecture.to_dict()
- if self.compiler:
- d.update(self.compiler.to_dict())
-
- if self.versions:
- d.update(self.versions.to_dict())
+ # TODO: restore build dependencies here once we have less picky
+ # TODO: concretization.
+ deps = self.dependencies_dict(deptype=('link', 'run'))
+ if deps:
+ d['dependencies'] = syaml_dict([
+ (name,
+ syaml_dict([
+ ('hash', dspec.spec.dag_hash()),
+ ('type', sorted(str(s) for s in dspec.deptypes))])
+ ) for name, dspec in sorted(deps.items())
+ ])
- return {self.name: d}
+ return syaml_dict([(self.name, d)])
def to_yaml(self, stream=None):
node_list = []
@@ -948,8 +1025,9 @@ class Spec(object):
node = s.to_node_dict()
node[s.name]['hash'] = s.dag_hash()
node_list.append(node)
- return yaml.dump({'spec': node_list},
- stream=stream, default_flow_style=False)
+ return syaml.dump(
+ syaml_dict([('spec', node_list)]),
+ stream=stream, default_flow_style=False)
@staticmethod
def from_node_dict(node):
@@ -1023,7 +1101,7 @@ class Spec(object):
"""
try:
- yfile = yaml.load(stream)
+ yfile = syaml.load(stream)
except MarkedYAMLError as e:
raise SpackYAMLError("error parsing YAML spec:", str(e))
@@ -1278,15 +1356,15 @@ class Spec(object):
# Mark everything in the spec as concrete, as well.
self._mark_concrete()
- def _mark_concrete(self):
+ def _mark_concrete(self, value=True):
"""Mark this spec and its dependencies as concrete.
Only for internal use -- client code should use "concretize"
unless there is a need to force a spec to be concrete.
"""
for s in self.traverse(deptype_query=alldeps):
- s._normal = True
- s._concrete = True
+ s._normal = value
+ s._concrete = value
def concretized(self):
"""This is a non-destructive version of concretize(). First clones,
@@ -1349,12 +1427,11 @@ class Spec(object):
# parser doesn't allow it. Spack must be broken!
raise InconsistentSpecError("Invalid Spec DAG: %s" % e.message)
- def index(self):
+ def index(self, deptype=None):
"""Return DependencyMap that points to all the dependencies in this
spec."""
dm = DependencyMap()
- # XXX(deptype): use a deptype kwarg.
- for spec in self.traverse():
+ for spec in self.traverse(deptype=deptype):
dm[spec.name] = spec
return dm
@@ -1533,6 +1610,10 @@ class Spec(object):
if self._normal and not force:
return False
+ # avoid any assumptions about concreteness when forced
+ if force:
+ self._mark_concrete(False)
+
# Ensure first that all packages & compilers in the DAG exist.
self.validate_names()
# Get all the dependencies into one DependencyMap
@@ -1553,7 +1634,7 @@ class Spec(object):
# actually deps of this package. Raise an error.
extra = set(spec_deps.keys()).difference(visited)
if extra:
- raise InvalidDependencyException(
+ raise InvalidDependencyError(
self.name + " does not depend on " + comma_or(extra))
# Mark the spec as normal once done.
@@ -1865,7 +1946,7 @@ class Spec(object):
"""Return list of any virtual deps in this spec."""
return [spec for spec in self.traverse() if spec.virtual]
- def _dup(self, other, **kwargs):
+ def _dup(self, other, deps=True, cleardeps=True):
"""Copy the spec other into self. This is an overwriting
copy. It does not copy any dependents (parents), but by default
copies dependencies.
@@ -1896,7 +1977,7 @@ class Spec(object):
self.versions = other.versions.copy()
self.architecture = other.architecture
self.compiler = other.compiler.copy() if other.compiler else None
- if kwargs.get('cleardeps', True):
+ if cleardeps:
self._dependents = DependencyMap()
self._dependencies = DependencyMap()
self.compiler_flags = other.compiler_flags.copy()
@@ -1905,20 +1986,17 @@ class Spec(object):
self.external = other.external
self.external_module = other.external_module
self.namespace = other.namespace
- self._hash = other._hash
+
+ self.external = other.external
+ self.external_module = other.external_module
# If we copy dependencies, preserve DAG structure in the new spec
- if kwargs.get('deps', True):
+ if deps:
# This copies the deps from other using _dup(deps=False)
- # XXX(deptype): We can keep different instances of specs here iff
- # it is only a 'build' dependency (from its parent).
- # All other instances must be shared (due to symbol
- # and PATH contention). These should probably search
- # for any existing installation which can satisfy the
- # build and latch onto that because if 3 things need
- # the same build dependency and it is *not*
- # available, we only want to build it once.
- new_nodes = other.flat_dependencies(deptype_query=alldeps)
+ deptypes = alldeps
+ if isinstance(deps, (tuple, list)):
+ deptypes = deps
+ new_nodes = other.flat_dependencies(deptypes=deptypes)
new_nodes[self.name] = self
stack = [other]
@@ -1927,6 +2005,9 @@ class Spec(object):
new_spec = new_nodes[cur_spec.name]
for depspec in cur_spec._dependencies.values():
+ if not any(d in deptypes for d in depspec.deptypes):
+ continue
+
stack.append(depspec.spec)
# XXX(deptype): add any new deptypes that may have appeared
@@ -1935,20 +2016,38 @@ class Spec(object):
new_spec._add_dependency(
new_nodes[depspec.spec.name], depspec.deptypes)
- # Since we preserved structure, we can copy _normal safely.
- self._normal = other._normal
- self._concrete = other._concrete
- self.external = other.external
- self.external_module = other.external_module
+ # These fields are all cached results of expensive operations.
+ # If we preserved the original structure, we can copy them
+ # safely. If not, they need to be recomputed.
+ if deps is True or deps == alldeps:
+ self._hash = other._hash
+ self._cmp_key_cache = other._cmp_key_cache
+ self._normal = other._normal
+ self._concrete = other._concrete
+ else:
+ self._hash = None
+ self._cmp_key_cache = None
+ self._normal = False
+ self._concrete = False
+
return changed
- def copy(self, **kwargs):
+ def copy(self, deps=True):
"""Return a copy of this spec.
- By default, returns a deep copy. Supply dependencies=False
- to get a shallow copy.
+
+ By default, returns a deep copy. To control how dependencies are
+ copied, supply:
+
+ deps=True: deep copy
+
+ deps=False: shallow copy (no dependencies)
+
+ deps=('link', 'build'):
+ only build and link dependencies. Similar for other deptypes.
+
"""
clone = Spec.__new__(Spec)
- clone._dup(self, **kwargs)
+ clone._dup(self, deps=deps)
return clone
@property
@@ -2059,10 +2158,17 @@ class Spec(object):
1. A tuple describing this node in the DAG.
2. The hash of each of this node's dependencies' cmp_keys.
"""
- dep_dict = self.dependencies_dict(deptype=('link', 'run'))
- return self._cmp_node() + (
- tuple(hash(dep_dict[name])
- for name in sorted(dep_dict)),)
+ if self._cmp_key_cache:
+ return self._cmp_key_cache
+
+ dep_tuple = tuple(
+ (d.spec.name, hash(d.spec), tuple(sorted(d.deptypes)))
+ for name, d in sorted(self._dependencies.items()))
+
+ key = (self._cmp_node(), dep_tuple)
+ if self._concrete:
+ self._cmp_key_cache = key
+ return key
def colorized(self):
return colorize_spec(self)
@@ -2085,7 +2191,7 @@ class Spec(object):
$# 7-char prefix of DAG hash with '-' prefix
$$ $
- You can also use full-string versions, which elide the prefixes:
+ You can also use full-string versions, which elide the prefixes::
${PACKAGE} Package name
${VERSION} Version
@@ -2096,22 +2202,27 @@ class Spec(object):
${OPTIONS} Options
${ARCHITECTURE} Architecture
${SHA1} Dependencies 8-char sha1 prefix
+ ${HASH:len} DAG hash with optional length specifier
${SPACK_ROOT} The spack root directory
${SPACK_INSTALL} The default spack install directory,
${SPACK_PREFIX}/opt
+ ${PREFIX} The package prefix
+
+ Note these are case-insensitive: for example you can specify either
+ ``${PACKAGE}`` or ``${package}``.
- Optionally you can provide a width, e.g. $20_ for a 20-wide name.
+ 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.
+ ``$-20_`` for a left-justified name.
Anything else is copied verbatim into the output stream.
*Example:* ``$_$@$+`` translates to the name, version, and options
of the package, but no dependencies, arch, or compiler.
- TODO: allow, e.g., $6# to customize short hash length
- TODO: allow, e.g., $## for full hash.
+ TODO: allow, e.g., ``$6#`` to customize short hash length
+ TODO: allow, e.g., ``$##`` for full hash.
"""
color = kwargs.get('color', False)
length = len(format_string)
@@ -2191,6 +2302,7 @@ class Spec(object):
"'%s'" % format_string)
named_str += c
continue
+ named_str = named_str.upper()
if named_str == 'PACKAGE':
name = self.name if self.name else ''
write(fmt % self.name, '@')
@@ -2203,7 +2315,7 @@ class Spec(object):
elif named_str == 'COMPILERNAME':
if self.compiler:
write(fmt % self.compiler.name, '%')
- elif named_str == 'COMPILERVER':
+ elif named_str in ['COMPILERVER', 'COMPILERVERSION']:
if self.compiler:
write(fmt % self.compiler.versions, '%')
elif named_str == 'COMPILERFLAGS':
@@ -2221,7 +2333,16 @@ class Spec(object):
elif named_str == 'SPACK_ROOT':
out.write(fmt % spack.prefix)
elif named_str == 'SPACK_INSTALL':
- out.write(fmt % spack.install_path)
+ out.write(fmt % spack.store.root)
+ elif named_str == 'PREFIX':
+ out.write(fmt % self.prefix)
+ elif named_str.startswith('HASH'):
+ if named_str.startswith('HASH:'):
+ _, hashlen = named_str.split(':')
+ hashlen = int(hashlen)
+ else:
+ hashlen = None
+ out.write(fmt % (self.dag_hash(hashlen)))
named = False
@@ -2275,12 +2396,24 @@ class Spec(object):
def __str__(self):
return self.format() + self.dep_string()
+ def _install_status(self):
+ """Helper for tree to print DB install status."""
+ if not self.concrete:
+ return None
+ try:
+ record = spack.store.db.get_record(self)
+ return record.installed
+ except KeyError:
+ return None
+
def tree(self, **kwargs):
"""Prints out this spec and its dependencies, tree-formatted
with indentation."""
color = kwargs.pop('color', False)
depth = kwargs.pop('depth', False)
- showid = kwargs.pop('ids', False)
+ hashes = kwargs.pop('hashes', True)
+ hlen = kwargs.pop('hashlen', None)
+ install_status = kwargs.pop('install_status', True)
cover = kwargs.pop('cover', 'nodes')
indent = kwargs.pop('indent', 0)
fmt = kwargs.pop('format', '$_$@$%@+$+$=')
@@ -2289,8 +2422,6 @@ class Spec(object):
check_kwargs(kwargs, self.tree)
out = ""
- cur_id = 0
- ids = {}
for d, node in self.traverse(
order='pre', cover=cover, depth=True, deptypes=deptypes):
if prefix is not None:
@@ -2298,11 +2429,17 @@ class Spec(object):
out += " " * indent
if depth:
out += "%-4d" % d
- if not id(node) in ids:
- cur_id += 1
- ids[id(node)] = cur_id
- if showid:
- out += "%-4d" % ids[id(node)]
+ if install_status:
+ status = node._install_status()
+ if status is None:
+ out += " " # Package isn't installed
+ elif status:
+ out += colorize("@g{[+]} ", color=color) # installed
+ else:
+ out += colorize("@r{[-]} ", color=color) # missing
+
+ if hashes:
+ out += colorize('@K{%s} ', color=color) % node.dag_hash(hlen)
out += (" " * d)
if d > 0:
out += "^"
@@ -2416,7 +2553,7 @@ class SpecParser(spack.parse.Parser):
def spec_by_hash(self):
self.expect(ID)
- specs = spack.installed_db.query()
+ specs = spack.store.db.query()
matches = [spec for spec in specs if
spec.dag_hash()[:len(self.token.value)] == self.token.value]
@@ -2457,6 +2594,7 @@ class SpecParser(spack.parse.Parser):
spec._dependencies = DependencyMap()
spec.namespace = spec_namespace
spec._hash = None
+ spec._cmp_key_cache = None
spec._normal = False
spec._concrete = False
@@ -2624,18 +2762,22 @@ def parse_anonymous_spec(spec_like, pkg_name):
return anon_spec
-class SpecError(spack.error.SpackError):
+def base32_prefix_bits(hash_string, bits):
+ """Return the first <bits> bits of a base32 string as an integer."""
+ if bits > len(hash_string) * 5:
+ raise ValueError("Too many bits! Requested %d bit prefix of '%s'."
+ % (bits, hash_string))
- """Superclass for all errors that occur while constructing specs."""
+ hash_bytes = base64.b32decode(hash_string, casefold=True)
+ return prefix_bits(hash_bytes, bits)
- def __init__(self, message):
- super(SpecError, self).__init__(message)
+class SpecError(spack.error.SpackError):
+ """Superclass for all errors that occur while constructing specs."""
-class SpecParseError(SpecError):
+class SpecParseError(SpecError):
"""Wrapper for ParseError for when we're parsing specs."""
-
def __init__(self, parse_error):
super(SpecParseError, self).__init__(parse_error.message)
self.string = parse_error.string
@@ -2643,79 +2785,53 @@ class SpecParseError(SpecError):
class DuplicateDependencyError(SpecError):
-
"""Raised when the same dependency occurs in a spec twice."""
- def __init__(self, message):
- super(DuplicateDependencyError, self).__init__(message)
-
class DuplicateVariantError(SpecError):
-
"""Raised when the same variant occurs in a spec twice."""
- def __init__(self, message):
- super(DuplicateVariantError, self).__init__(message)
-
class DuplicateCompilerSpecError(SpecError):
-
"""Raised when the same compiler occurs in a spec twice."""
- def __init__(self, message):
- super(DuplicateCompilerSpecError, self).__init__(message)
-
class UnsupportedCompilerError(SpecError):
-
"""Raised when the user asks for a compiler spack doesn't know about."""
-
def __init__(self, compiler_name):
super(UnsupportedCompilerError, self).__init__(
"The '%s' compiler is not yet supported." % compiler_name)
class UnknownVariantError(SpecError):
-
"""Raised when the same variant occurs in a spec twice."""
-
def __init__(self, pkg, variant):
super(UnknownVariantError, self).__init__(
"Package %s has no variant %s!" % (pkg, variant))
class DuplicateArchitectureError(SpecError):
-
"""Raised when the same architecture occurs in a spec twice."""
- def __init__(self, message):
- super(DuplicateArchitectureError, self).__init__(message)
-
class InconsistentSpecError(SpecError):
-
"""Raised when two nodes in the same spec DAG have inconsistent
constraints."""
- def __init__(self, message):
- super(InconsistentSpecError, self).__init__(message)
-
-
-class InvalidDependencyException(SpecError):
+class InvalidDependencyError(SpecError):
"""Raised when a dependency in a spec is not actually a dependency
of the package."""
- def __init__(self, message):
- super(InvalidDependencyException, self).__init__(message)
+class InvalidDependencyTypeError(SpecError):
+ """Raised when a dependency type is not a legal Spack dep type."""
-class NoProviderError(SpecError):
+class NoProviderError(SpecError):
"""Raised when there is no package that provides a particular
virtual dependency.
"""
-
def __init__(self, vpkg):
super(NoProviderError, self).__init__(
"No providers found for virtual package: '%s'" % vpkg)
@@ -2723,11 +2839,9 @@ class NoProviderError(SpecError):
class MultipleProviderError(SpecError):
-
"""Raised when there is no package that provides a particular
virtual dependency.
"""
-
def __init__(self, vpkg, providers):
"""Takes the name of the vpkg"""
super(MultipleProviderError, self).__init__(
@@ -2738,10 +2852,8 @@ class MultipleProviderError(SpecError):
class UnsatisfiableSpecError(SpecError):
-
"""Raised when a spec conflicts with package constraints.
Provide the requirement that was violated when raising."""
-
def __init__(self, provided, required, constraint_type):
super(UnsatisfiableSpecError, self).__init__(
"%s does not satisfy %s" % (provided, required))
@@ -2751,89 +2863,70 @@ class UnsatisfiableSpecError(SpecError):
class UnsatisfiableSpecNameError(UnsatisfiableSpecError):
-
"""Raised when two specs aren't even for the same package."""
-
def __init__(self, provided, required):
super(UnsatisfiableSpecNameError, self).__init__(
provided, required, "name")
class UnsatisfiableVersionSpecError(UnsatisfiableSpecError):
-
"""Raised when a spec version conflicts with package constraints."""
-
def __init__(self, provided, required):
super(UnsatisfiableVersionSpecError, self).__init__(
provided, required, "version")
class UnsatisfiableCompilerSpecError(UnsatisfiableSpecError):
-
"""Raised when a spec comiler conflicts with package constraints."""
-
def __init__(self, provided, required):
super(UnsatisfiableCompilerSpecError, self).__init__(
provided, required, "compiler")
class UnsatisfiableVariantSpecError(UnsatisfiableSpecError):
-
"""Raised when a spec variant conflicts with package constraints."""
-
def __init__(self, provided, required):
super(UnsatisfiableVariantSpecError, self).__init__(
provided, required, "variant")
class UnsatisfiableCompilerFlagSpecError(UnsatisfiableSpecError):
-
"""Raised when a spec variant conflicts with package constraints."""
-
def __init__(self, provided, required):
super(UnsatisfiableCompilerFlagSpecError, self).__init__(
provided, required, "compiler_flags")
class UnsatisfiableArchitectureSpecError(UnsatisfiableSpecError):
-
"""Raised when a spec architecture conflicts with package constraints."""
-
def __init__(self, provided, required):
super(UnsatisfiableArchitectureSpecError, self).__init__(
provided, required, "architecture")
class UnsatisfiableProviderSpecError(UnsatisfiableSpecError):
-
"""Raised when a provider is supplied but constraints don't match
a vpkg requirement"""
-
def __init__(self, provided, required):
super(UnsatisfiableProviderSpecError, self).__init__(
provided, required, "provider")
+
# TODO: get rid of this and be more specific about particular incompatible
# dep constraints
-
-
class UnsatisfiableDependencySpecError(UnsatisfiableSpecError):
-
"""Raised when some dependency of constrained specs are incompatible"""
-
def __init__(self, provided, required):
super(UnsatisfiableDependencySpecError, self).__init__(
provided, required, "dependency")
class SpackYAMLError(spack.error.SpackError):
-
def __init__(self, msg, yaml_error):
super(SpackYAMLError, self).__init__(msg, str(yaml_error))
class AmbiguousHashError(SpecError):
-
def __init__(self, msg, *specs):
super(AmbiguousHashError, self).__init__(msg)
for spec in specs:
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 7676cb9ab6..4157511ce0 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -23,12 +23,16 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import os
+import sys
import errno
+import hashlib
import shutil
import tempfile
+import getpass
from urlparse import urljoin
import llnl.util.tty as tty
+import llnl.util.lock
from llnl.util.filesystem import *
import spack.util.pattern as pattern
@@ -38,8 +42,72 @@ import spack.config
import spack.fetch_strategy as fs
import spack.error
from spack.version import *
+from spack.util.path import canonicalize_path
+from spack.util.crypto import prefix_bits, bit_length
-STAGE_PREFIX = 'spack-stage-'
+_stage_prefix = 'spack-stage-'
+
+
+def _first_accessible_path(paths):
+ """Find a tmp dir that exists that we can access."""
+ for path in paths:
+ try:
+ # try to create the path if it doesn't exist.
+ path = canonicalize_path(path)
+ mkdirp(path)
+
+ # ensure accessible
+ if not can_access(path):
+ continue
+
+ # return it if successful.
+ return path
+
+ except OSError:
+ tty.debug('OSError while checking temporary path: %s' % path)
+ continue
+
+ return None
+
+
+# cached temporary root
+_tmp_root = None
+_use_tmp_stage = True
+
+
+def get_tmp_root():
+ global _tmp_root, _use_tmp_stage
+
+ if not _use_tmp_stage:
+ return None
+
+ if _tmp_root is None:
+ config = spack.config.get_config('config')
+ candidates = config['build_stage']
+ if isinstance(candidates, basestring):
+ candidates = [candidates]
+
+ path = _first_accessible_path(candidates)
+ if not path:
+ raise StageError("No accessible stage paths in %s", candidates)
+
+ # Return None to indicate we're using a local staging area.
+ if path == canonicalize_path(spack.stage_path):
+ _use_tmp_stage = False
+ return None
+
+ # ensure that any temp path is unique per user, so users don't
+ # fight over shared temporary space.
+ user = getpass.getuser()
+ if user not in path:
+ path = os.path.join(path, user, 'spack-stage')
+ else:
+ path = os.path.join(path, 'spack-stage')
+
+ mkdirp(path)
+ _tmp_root = path
+
+ return _tmp_root
class Stage(object):
@@ -49,16 +117,14 @@ class Stage(object):
some source code is downloaded and built before being installed.
It handles fetching the source code, either as an archive to be
expanded or by checking it out of a repository. A stage's
- lifecycle looks like this:
+ lifecycle looks like this::
- ```
- with Stage() as stage: # Context manager creates and destroys the
- # stage directory
- stage.fetch() # Fetch a source archive into the stage.
- stage.expand_archive() # Expand the source archive.
- <install> # Build and install the archive. (handled by
- # user of Stage)
- ```
+ with Stage() as stage: # Context manager creates and destroys the
+ # stage directory
+ stage.fetch() # Fetch a source archive into the stage.
+ stage.expand_archive() # Expand the source archive.
+ <install> # Build and install the archive.
+ # (handled by user of Stage)
When used as a context manager, the stage is automatically
destroyed if no exception is raised by the context. If an
@@ -66,19 +132,17 @@ class Stage(object):
destroyed, for potential reuse later.
You can also use the stage's create/destroy functions manually,
- like this:
-
- ```
- stage = Stage()
- try:
- stage.create() # Explicitly create the stage directory.
- stage.fetch() # Fetch a source archive into the stage.
- stage.expand_archive() # Expand the source archive.
- <install> # Build and install the archive. (handled by
- # user of Stage)
- finally:
- stage.destroy() # Explicitly destroy the stage directory.
- ```
+ like this::
+
+ stage = Stage()
+ try:
+ stage.create() # Explicitly create the stage directory.
+ stage.fetch() # Fetch a source archive into the stage.
+ stage.expand_archive() # Expand the source archive.
+ <install> # Build and install the archive.
+ # (handled by user of Stage)
+ finally:
+ stage.destroy() # Explicitly destroy the stage directory.
If spack.use_tmp_stage is True, spack will attempt to create
stages in a tmp directory. Otherwise, stages are created directly
@@ -92,8 +156,13 @@ 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=False, path=None):
+ """Shared dict of all stage locks."""
+ stage_locks = {}
+
+ def __init__(
+ self, url_or_fetch_strategy,
+ name=None, mirror_path=None, keep=False, path=None, lock=True,
+ alternate_fetchers=None):
"""Create a stage object.
Parameters:
url_or_fetch_strategy
@@ -129,6 +198,7 @@ class Stage(object):
self.fetcher.set_stage(self)
# self.fetcher can change with mirrors.
self.default_fetcher = self.fetcher
+ self.alternate_fetchers = alternate_fetchers
# used for mirrored archives of repositories.
self.skip_checksum_for_mirror = True
@@ -137,9 +207,8 @@ class Stage(object):
# TODO : won't be the same as the temporary stage area in tmp_root
self.name = name
if name is None:
- self.name = STAGE_PREFIX + next(tempfile._get_candidate_names())
+ self.name = _stage_prefix + next(tempfile._get_candidate_names())
self.mirror_path = mirror_path
- self.tmp_root = find_tmp_root()
# Try to construct here a temporary name for the stage directory
# If this is a named stage, then construct a named path.
@@ -151,6 +220,20 @@ class Stage(object):
# Flag to decide whether to delete the stage folder on exit or not
self.keep = keep
+ # File lock for the stage directory. We use one file for all
+ # stage locks. See Spec.prefix_lock for details on this approach.
+ self._lock = None
+ if lock:
+ if self.name not in Stage.stage_locks:
+ sha1 = hashlib.sha1(self.name).digest()
+ lock_id = prefix_bits(sha1, bit_length(sys.maxsize))
+ stage_lock_path = join_path(spack.stage_path, '.lock')
+
+ Stage.stage_locks[self.name] = llnl.util.lock.Lock(
+ stage_lock_path, lock_id, 1)
+
+ self._lock = Stage.stage_locks[self.name]
+
def __enter__(self):
"""
Entering a stage context will create the stage directory
@@ -158,6 +241,8 @@ class Stage(object):
Returns:
self
"""
+ if self._lock is not None:
+ self._lock.acquire_write(timeout=60)
self.create()
return self
@@ -179,6 +264,9 @@ class Stage(object):
if exc_type is None and not self.keep:
self.destroy()
+ if self._lock is not None:
+ self._lock.release_write()
+
def _need_to_create_path(self):
"""Makes sure nothing weird has happened since the last time we
looked at path. Returns True if path already exists and is ok.
@@ -194,10 +282,11 @@ class Stage(object):
# Path looks ok, but need to check the target of the link.
if os.path.islink(self.path):
- real_path = os.path.realpath(self.path)
- real_tmp = os.path.realpath(self.tmp_root)
+ tmp_root = get_tmp_root()
+ if tmp_root is not None:
+ real_path = os.path.realpath(self.path)
+ real_tmp = os.path.realpath(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 (real_path.startswith(real_tmp) and
@@ -220,9 +309,9 @@ class Stage(object):
def expected_archive_files(self):
"""Possible archive file paths."""
paths = []
- if isinstance(self.fetcher, fs.URLFetchStrategy):
+ if isinstance(self.default_fetcher, fs.URLFetchStrategy):
paths.append(os.path.join(
- self.path, os.path.basename(self.fetcher.url)))
+ self.path, os.path.basename(self.default_fetcher.url)))
if self.mirror_path:
paths.append(os.path.join(
@@ -231,18 +320,18 @@ class Stage(object):
return paths
@property
+ def save_filename(self):
+ possible_filenames = self.expected_archive_files
+ if possible_filenames:
+ # This prefers using the URL associated with the default fetcher if
+ # available, so that the fetched resource name matches the remote
+ # name
+ return possible_filenames[0]
+
+ @property
def archive_file(self):
"""Path to the source archive within this stage directory."""
- paths = []
- if isinstance(self.fetcher, fs.URLFetchStrategy):
- paths.append(os.path.join(
- self.path, os.path.basename(self.fetcher.url)))
-
- if self.mirror_path:
- paths.append(os.path.join(
- self.path, os.path.basename(self.mirror_path)))
-
- for path in paths:
+ for path in self.expected_archive_files:
if os.path.exists(path):
return path
else:
@@ -305,8 +394,12 @@ class Stage(object):
# then use the same digest. `spack mirror` ensures that
# the checksum will be the same.
digest = None
+ expand = True
+ extension = None
if isinstance(self.default_fetcher, fs.URLFetchStrategy):
digest = self.default_fetcher.digest
+ expand = self.default_fetcher.expand_archive
+ extension = self.default_fetcher.extension
# Have to skip the checksum for things archived from
# repositories. How can this be made safer?
@@ -314,27 +407,17 @@ class Stage(object):
# Add URL strategies for all the mirrors with the digest
for url in urls:
- fetchers.insert(0, fs.URLFetchStrategy(url, digest))
- fetchers.insert(0, spack.fetch_cache.fetcher(self.mirror_path,
- digest))
-
- # Look for the archive in list_url
- package_name = os.path.dirname(self.mirror_path)
- pkg = spack.repo.get(package_name)
- if pkg.list_url is not None and pkg.url is not None:
- try:
- archive_version = spack.url.parse_version(
- self.default_fetcher.url)
- versions = pkg.fetch_remote_versions()
- try:
- url_from_list = versions[Version(archive_version)]
- fetchers.append(fs.URLFetchStrategy(
- url_from_list, digest))
- except KeyError:
- tty.msg("Can not find version %s in url_list" %
- archive_version)
- except:
- tty.msg("Could not determine url from list_url.")
+ fetchers.insert(
+ 0, fs.URLFetchStrategy(
+ url, digest, expand=expand, extension=extension))
+ if self.default_fetcher.cachable:
+ fetchers.insert(
+ 0, spack.fetch_cache.fetcher(
+ self.mirror_path, digest, expand=expand,
+ extension=extension))
+
+ if self.alternate_fetchers:
+ fetchers.extend(self.alternate_fetchers)
for fetcher in fetchers:
try:
@@ -342,6 +425,9 @@ class Stage(object):
self.fetcher = fetcher
self.fetcher.fetch()
break
+ except spack.fetch_strategy.NoCacheError as e:
+ # Don't bother reporting when something is not cached.
+ continue
except spack.error.SpackError as e:
tty.msg("Fetching from %s failed." % fetcher)
tty.debug(e)
@@ -385,11 +471,11 @@ class Stage(object):
"""
path = self.source_path
if not path:
- tty.die("Attempt to chdir before expanding archive.")
+ raise StageError("Attempt to chdir before expanding archive.")
else:
os.chdir(path)
if not os.listdir(path):
- tty.die("Archive was empty for %s" % self.name)
+ raise StageError("Archive was empty for %s" % self.name)
def restage(self):
"""Removes the expanded archive path if it exists, then re-expands
@@ -398,26 +484,29 @@ class Stage(object):
self.fetcher.reset()
def create(self):
- """
- Creates the stage directory
+ """Creates the stage directory.
- If self.tmp_root evaluates to False, the stage directory is
- created directly under spack.stage_path, otherwise this will
- attempt to create a stage in a temporary directory and link it
- into spack.stage_path.
+ If get_tmp_root() is None, the stage directory is created
+ directly under spack.stage_path, otherwise this will attempt to
+ create a stage in a temporary directory and link it into
+ spack.stage_path.
Spack will use the first writable location in spack.tmp_dirs
to create a stage. If there is no valid location in tmp_dirs,
fall back to making the stage inside spack.stage_path.
+
"""
# Create the top-level stage directory
mkdirp(spack.stage_path)
- remove_dead_links(spack.stage_path)
+ remove_if_dead_link(self.path)
+
# If a tmp_root exists then create a directory there and then link it
# in the stage area, otherwise create the stage directory in self.path
if self._need_to_create_path():
- if self.tmp_root:
- tmp_dir = tempfile.mkdtemp('', STAGE_PREFIX, self.tmp_root)
+ tmp_root = get_tmp_root()
+ if tmp_root is not None:
+ tmp_dir = tempfile.mkdtemp('', _stage_prefix, tmp_root)
+ tty.debug('link %s -> %s' % (self.path, tmp_dir))
os.symlink(tmp_dir, self.path)
else:
mkdirp(self.path)
@@ -513,6 +602,10 @@ class StageComposite:
def archive_file(self):
return self[0].archive_file
+ @property
+ def mirror_path(self):
+ return self[0].mirror_path
+
class DIYStage(object):
"""Simple class that allows any directory to be a spack stage."""
@@ -538,7 +631,7 @@ class DIYStage(object):
def chdir_to_source(self):
self.chdir()
- def fetch(self, mirror_only):
+ def fetch(self, *args, **kwargs):
tty.msg("No need to fetch for DIY.")
def check(self):
@@ -578,25 +671,6 @@ def purge():
remove_linked_tree(stage_path)
-def find_tmp_root():
- if spack.use_tmp_stage:
- for tmp in spack.tmp_dirs:
- try:
- # Replace %u with username
- expanded = expand_user(tmp)
-
- # try to create a directory for spack stuff
- mkdirp(expanded)
-
- # return it if successful.
- return expanded
-
- except OSError:
- continue
-
- return None
-
-
class StageError(spack.error.SpackError):
""""Superclass for all errors encountered during staging."""
diff --git a/lib/spack/spack/store.py b/lib/spack/spack/store.py
new file mode 100644
index 0000000000..3f559315d2
--- /dev/null
+++ b/lib/spack/spack/store.py
@@ -0,0 +1,75 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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
+##############################################################################
+"""Components that manage Spack's installation tree.
+
+An install tree, or "build store" consists of two parts:
+
+ 1. A package database that tracks what is installed.
+ 2. A directory layout that determines how the installations
+ are laid out.
+
+The store contains all the install prefixes for packages installed by
+Spack. The simplest store could just contain prefixes named by DAG hash,
+but we use a fancier directory layout to make browsing the store and
+debugging easier.
+
+The directory layout is currently hard-coded to be a YAMLDirectoryLayout,
+so called because it stores build metadata within each prefix, in
+`spec.yaml` files. In future versions of Spack we may consider allowing
+install trees to define their own layouts with some per-tree
+configuration.
+
+"""
+import os
+import spack
+import spack.config
+from spack.util.path import canonicalize_path
+from spack.database import Database
+from spack.directory_layout import YamlDirectoryLayout
+
+__author__ = "Benedikt Hegner (CERN)"
+__all__ = ['db', 'layout', 'root']
+
+#
+# Read in the config
+#
+config = spack.config.get_config("config")
+
+#
+# Set up the install path
+#
+root = canonicalize_path(
+ config.get('install_tree', os.path.join(spack.opt_path, 'spack')))
+
+#
+# Set up the installed packages database
+#
+db = Database(root)
+
+#
+# This controls how spack lays out install prefixes and
+# stage directories.
+#
+layout = YamlDirectoryLayout(root)
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index db683917b5..c0a4c7354f 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -41,7 +41,7 @@ test_names = [
'cc',
'cmd.find',
'cmd.module',
- 'cmd.test_install',
+ 'cmd.install',
'cmd.uninstall',
'concretize',
'concretize_preferences',
@@ -53,6 +53,7 @@ test_names = [
'git_fetch',
'hg_fetch',
'install',
+ 'library_list',
'link_tree',
'lock',
'make_executable',
@@ -77,7 +78,7 @@ test_names = [
'url_substitution',
'versions',
'provider_index',
- 'yaml',
+ 'spack_yaml',
# This test needs to be last until global compiler cache is fixed.
'cmd.test_compiler_cmd',
]
@@ -106,25 +107,33 @@ def run(names, outputDir, verbose=False):
sys.exit(1)
tally = Tally()
- for test in names:
- module = 'spack.test.' + test
- print(module)
- tty.msg("Running test: %s" % test)
+ modules = ['spack.test.' + test for test in names]
+ runOpts = ["--with-%s" % spack.test.tally_plugin.Tally.name]
- 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",
- "--xunit-file={0}".format(xmlOutputPath)]
- argv = [""] + runOpts + [module]
- nose.run(argv=argv, addplugins=[tally])
+ if outputDir:
+ xmlOutputFname = "unittests-{0}.xml".format(test)
+ xmlOutputPath = join_path(outputDir, xmlOutputFname)
+ runOpts += ["--with-xunit",
+ "--xunit-file={0}".format(xmlOutputPath)]
+ argv = [""] + runOpts + modules
+ nose.run(argv=argv, addplugins=[tally])
succeeded = not tally.failCount and not tally.errorCount
- tty.msg("Tests Complete.", "%5d tests run" % tally.numberOfTestsRun,
- "%5d failures" % tally.failCount, "%5d errors" % tally.errorCount)
+ tty.msg(
+ "Tests Complete.",
+ "%5d tests run" % tally.numberOfTestsRun,
+ "%5d failures" % tally.failCount,
+ "%5d errors" % tally.errorCount
+ )
+
+ if tally.fail_list:
+ items = [x for x in tally.fail_list]
+ tty.msg('List of failing tests:', *items)
+
+ if tally.error_list:
+ items = [x for x in tally.error_list]
+ tty.msg('List of tests with errors:', *items)
if succeeded:
tty.info("OK", format='g')
diff --git a/lib/spack/spack/test/cc.py b/lib/spack/spack/test/cc.py
index f3e4bb31d2..74b6b31654 100644
--- a/lib/spack/spack/test/cc.py
+++ b/lib/spack/spack/test/cc.py
@@ -51,7 +51,7 @@ test_command = [
'arg5', 'arg6']
-class CompilerTest(unittest.TestCase):
+class CompilerWrapperTest(unittest.TestCase):
def setUp(self):
self.cc = Executable(join_path(spack.build_env_path, "cc"))
@@ -223,6 +223,8 @@ class CompilerTest(unittest.TestCase):
def test_dep_include(self):
"""Ensure a single dependency include directory is added."""
os.environ['SPACK_DEPENDENCIES'] = self.dep4
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
self.check_cc('dump-args', test_command,
self.realcc + ' ' +
'-Wl,-rpath,' + self.prefix + '/lib ' +
@@ -233,6 +235,8 @@ class CompilerTest(unittest.TestCase):
def test_dep_lib(self):
"""Ensure a single dependency RPATH is added."""
os.environ['SPACK_DEPENDENCIES'] = self.dep2
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
self.check_cc('dump-args', test_command,
self.realcc + ' ' +
'-Wl,-rpath,' + self.prefix + '/lib ' +
@@ -241,10 +245,34 @@ class CompilerTest(unittest.TestCase):
'-Wl,-rpath,' + self.dep2 + '/lib64 ' +
' '.join(test_command))
+ def test_dep_lib_no_rpath(self):
+ """Ensure a single dependency link flag is added with no dep RPATH."""
+ os.environ['SPACK_DEPENDENCIES'] = self.dep2
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ self.check_cc('dump-args', test_command,
+ self.realcc + ' ' +
+ '-Wl,-rpath,' + self.prefix + '/lib ' +
+ '-Wl,-rpath,' + self.prefix + '/lib64 ' +
+ '-L' + self.dep2 + '/lib64 ' +
+ ' '.join(test_command))
+
+ def test_dep_lib_no_lib(self):
+ """Ensure a single dependency RPATH is added with no -L."""
+ os.environ['SPACK_DEPENDENCIES'] = self.dep2
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ self.check_cc('dump-args', test_command,
+ self.realcc + ' ' +
+ '-Wl,-rpath,' + self.prefix + '/lib ' +
+ '-Wl,-rpath,' + self.prefix + '/lib64 ' +
+ '-Wl,-rpath,' + self.dep2 + '/lib64 ' +
+ ' '.join(test_command))
+
def test_all_deps(self):
"""Ensure includes and RPATHs for all deps are added. """
os.environ['SPACK_DEPENDENCIES'] = ':'.join([
self.dep1, self.dep2, self.dep3, self.dep4])
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
# This is probably more constrained than it needs to be; it
# checks order within prepended args and doesn't strictly have
@@ -273,6 +301,8 @@ class CompilerTest(unittest.TestCase):
"""Ensure no (extra) -I args or -Wl, are passed in ld mode."""
os.environ['SPACK_DEPENDENCIES'] = ':'.join([
self.dep1, self.dep2, self.dep3, self.dep4])
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
self.check_ld('dump-args', test_command,
'ld ' +
@@ -290,10 +320,46 @@ class CompilerTest(unittest.TestCase):
' '.join(test_command))
+ def test_ld_deps_no_rpath(self):
+ """Ensure SPACK_RPATH_DEPS controls RPATHs for ld."""
+ os.environ['SPACK_DEPENDENCIES'] = ':'.join([
+ self.dep1, self.dep2, self.dep3, self.dep4])
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+
+ self.check_ld('dump-args', test_command,
+ 'ld ' +
+ '-rpath ' + self.prefix + '/lib ' +
+ '-rpath ' + self.prefix + '/lib64 ' +
+
+ '-L' + self.dep3 + '/lib64 ' +
+ '-L' + self.dep2 + '/lib64 ' +
+ '-L' + self.dep1 + '/lib ' +
+
+ ' '.join(test_command))
+
+ def test_ld_deps_no_link(self):
+ """Ensure SPACK_LINK_DEPS controls -L for ld."""
+ os.environ['SPACK_DEPENDENCIES'] = ':'.join([
+ self.dep1, self.dep2, self.dep3, self.dep4])
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+
+ self.check_ld('dump-args', test_command,
+ 'ld ' +
+ '-rpath ' + self.prefix + '/lib ' +
+ '-rpath ' + self.prefix + '/lib64 ' +
+
+ '-rpath ' + self.dep3 + '/lib64 ' +
+ '-rpath ' + self.dep2 + '/lib64 ' +
+ '-rpath ' + self.dep1 + '/lib ' +
+
+ ' '.join(test_command))
+
def test_ld_deps_reentrant(self):
"""Make sure ld -r is handled correctly on OS's where it doesn't
support rpaths."""
os.environ['SPACK_DEPENDENCIES'] = ':'.join([self.dep1])
+ os.environ['SPACK_RPATH_DEPS'] = os.environ['SPACK_DEPENDENCIES']
+ os.environ['SPACK_LINK_DEPS'] = os.environ['SPACK_DEPENDENCIES']
os.environ['SPACK_SHORT_SPEC'] = "foo@1.2=linux-x86_64"
reentrant_test_command = ['-r'] + test_command
diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py
index fa82db7733..4788da8ec6 100644
--- a/lib/spack/spack/test/cmd/find.py
+++ b/lib/spack/spack/test/cmd/find.py
@@ -52,5 +52,3 @@ class FindTest(unittest.TestCase):
args.implicit = True
q_args = query_arguments(args)
self.assertEqual(q_args['explicit'], False)
- args.explicit = True
- self.assertRaises(SystemExit, query_arguments, args)
diff --git a/lib/spack/spack/test/cmd/test_install.py b/lib/spack/spack/test/cmd/install.py
index 39287d5d6d..591bf02340 100644
--- a/lib/spack/spack/test/cmd/test_install.py
+++ b/lib/spack/spack/test/cmd/install.py
@@ -23,20 +23,23 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import StringIO
+import argparse
+import codecs
import collections
-import os
-import unittest
import contextlib
+import unittest
+import llnl.util.filesystem
import spack
import spack.cmd
+import spack.cmd.install as install
FILE_REGISTRY = collections.defaultdict(StringIO.StringIO)
# Monkey-patch open to write module files to a StringIO instance
@contextlib.contextmanager
-def mock_open(filename, mode):
+def mock_open(filename, mode, *args):
if not mode == 'wb':
message = 'test.test_install : unexpected opening mode for mock_open'
raise RuntimeError(message)
@@ -51,11 +54,6 @@ def mock_open(filename, mode):
handle.close()
-# The use of __import__ is necessary to maintain a name with hyphen (which
-# cannot be an identifier in python)
-test_install = __import__("spack.cmd.test-install", fromlist=['test_install'])
-
-
class MockSpec(object):
def __init__(self, name, version, hashStr=None):
@@ -107,6 +105,8 @@ class MockPackage(object):
self.build_log_path = buildLogPath
def do_install(self, *args, **kwargs):
+ for x in self.spec.dependencies():
+ x.package.do_install(*args, **kwargs)
self.installed = True
@@ -124,36 +124,28 @@ class MockPackageDb(object):
def mock_fetch_log(path):
return []
-specX = MockSpec('X', "1.2.0")
-specY = MockSpec('Y', "2.3.8")
+specX = MockSpec('X', '1.2.0')
+specY = MockSpec('Y', '2.3.8')
specX._dependencies['Y'] = spack.DependencySpec(specY, spack.alldeps)
pkgX = MockPackage(specX, 'logX')
pkgY = MockPackage(specY, 'logY')
-
-
-class MockArgs(object):
-
- def __init__(self, package):
- self.package = package
- self.jobs = None
- self.no_checksum = False
- self.output = None
+specX.package = pkgX
+specY.package = pkgY
# TODO: add test(s) where Y fails to install
-class TestInstallTest(unittest.TestCase):
- """
- Tests test-install where X->Y
- """
+class InstallTestJunitLog(unittest.TestCase):
+ """Tests test-install where X->Y"""
def setUp(self):
- super(TestInstallTest, self).setUp()
-
+ super(InstallTestJunitLog, self).setUp()
+ install.PackageBase = MockPackage
# Monkey patch parse specs
+
def monkey_parse_specs(x, concretize):
- if x == 'X':
+ if x == ['X']:
return [specX]
- elif x == 'Y':
+ elif x == ['Y']:
return [specY]
return []
@@ -161,11 +153,12 @@ class TestInstallTest(unittest.TestCase):
spack.cmd.parse_specs = monkey_parse_specs
# Monkey patch os.mkdirp
- self.os_mkdir = os.mkdir
- os.mkdir = lambda x: True
+ self.mkdirp = llnl.util.filesystem.mkdirp
+ llnl.util.filesystem.mkdirp = lambda x: True
# Monkey patch open
- test_install.open = mock_open
+ self.codecs_open = codecs.open
+ codecs.open = mock_open
# Clean FILE_REGISTRY
FILE_REGISTRY.clear()
@@ -180,21 +173,24 @@ class TestInstallTest(unittest.TestCase):
def tearDown(self):
# Remove the monkey patched test_install.open
- test_install.open = open
+ codecs.open = self.codecs_open
# Remove the monkey patched os.mkdir
- os.mkdir = self.os_mkdir
- del self.os_mkdir
+ llnl.util.filesystem.mkdirp = self.mkdirp
+ del self.mkdirp
# Remove the monkey patched parse_specs
spack.cmd.parse_specs = self.parse_specs
del self.parse_specs
- super(TestInstallTest, self).tearDown()
+ super(InstallTestJunitLog, self).tearDown()
spack.repo = self.saved_db
def test_installing_both(self):
- test_install.test_install(None, MockArgs('X'))
+ parser = argparse.ArgumentParser()
+ install.setup_parser(parser)
+ args = parser.parse_args(['--log-format=junit', 'X'])
+ install.install(parser, args)
self.assertEqual(len(FILE_REGISTRY), 1)
for _, content in FILE_REGISTRY.items():
self.assertTrue('tests="2"' in content)
@@ -204,7 +200,10 @@ class TestInstallTest(unittest.TestCase):
def test_dependency_already_installed(self):
pkgX.installed = True
pkgY.installed = True
- test_install.test_install(None, MockArgs('X'))
+ parser = argparse.ArgumentParser()
+ install.setup_parser(parser)
+ args = parser.parse_args(['--log-format=junit', 'X'])
+ install.install(parser, args)
self.assertEqual(len(FILE_REGISTRY), 1)
for _, content in FILE_REGISTRY.items():
self.assertTrue('tests="2"' in content)
diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py
index 3a0ce32e6c..39f9c5649f 100644
--- a/lib/spack/spack/test/cmd/module.py
+++ b/lib/spack/spack/test/cmd/module.py
@@ -34,7 +34,7 @@ class TestModule(spack.test.mock_database.MockDatabase):
def _get_module_files(self, args):
return [modules.module_types[args.module_type](spec).file_name
- for spec in args.specs]
+ for spec in args.specs()]
def test_module_common_operations(self):
parser = argparse.ArgumentParser()
diff --git a/lib/spack/spack/test/cmd/uninstall.py b/lib/spack/spack/test/cmd/uninstall.py
index 4ccb9ddbf4..6a86a1543f 100644
--- a/lib/spack/spack/test/cmd/uninstall.py
+++ b/lib/spack/spack/test/cmd/uninstall.py
@@ -23,7 +23,7 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import spack.test.mock_database
-
+import spack.store
from spack.cmd.uninstall import uninstall
@@ -51,7 +51,7 @@ class TestUninstall(spack.test.mock_database.MockDatabase):
args = MockArgs(['callpath'], all=True, dependents=True)
uninstall(parser, args)
- all_specs = spack.install_layout.all_specs()
+ all_specs = spack.store.layout.all_specs()
self.assertEqual(len(all_specs), 7)
# query specs with multiple configurations
mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py
index 2c8bedc33f..575e912609 100644
--- a/lib/spack/spack/test/concretize_preferences.py
+++ b/lib/spack/spack/test/concretize_preferences.py
@@ -104,3 +104,10 @@ class ConcretizePreferencesTest(MockPackagesTest):
self.update_packages('all', 'providers', {'mpi': ['zmpi']})
spec = self.concretize('mpileaks')
self.assertTrue('zmpi', spec)
+
+ def test_develop(self):
+ """Test conretization with develop version
+ """
+ spec = Spec('builtin.mock.develop-test')
+ spec.concretize()
+ self.assertEqual(spec.version, spack.spec.Version('0.2.15'))
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
index 0822e44db8..adc0795916 100644
--- a/lib/spack/spack/test/config.py
+++ b/lib/spack/spack/test/config.py
@@ -24,87 +24,147 @@
##############################################################################
import os
import shutil
+import getpass
+import yaml
from tempfile import mkdtemp
import spack
import spack.config
+from spack.util.path import canonicalize_path
from ordereddict_backport import OrderedDict
from spack.test.mock_packages_test import *
# Some sample compiler config data
-a_comps = [
- {'compiler': {
- 'paths': {
- "cc": "/gcc473",
- "cxx": "/g++473",
- "f77": None,
- "fc": None
- },
- 'modules': None,
- 'spec': 'gcc@4.7.3',
- 'operating_system': 'CNL10'
- }},
- {'compiler': {
- 'paths': {
- "cc": "/gcc450",
- "cxx": "/g++450",
- "f77": 'gfortran',
- "fc": 'gfortran'
- },
- 'modules': None,
- 'spec': 'gcc@4.5.0',
- 'operating_system': 'CNL10'
- }},
- {'compiler': {
- 'paths': {
- "cc": "<overwritten>",
- "cxx": "<overwritten>",
- "f77": '<overwritten>',
- "fc": '<overwritten>'},
- 'modules': None,
- 'spec': 'clang@3.3',
- 'operating_system': 'CNL10'
- }}
-]
-
-b_comps = [
- {'compiler': {
- 'paths': {
- "cc": "/icc100",
- "cxx": "/icp100",
- "f77": None,
- "fc": None
- },
- 'modules': None,
- 'spec': 'icc@10.0',
- 'operating_system': 'CNL10'
- }},
- {'compiler': {
- 'paths': {
- "cc": "/icc111",
- "cxx": "/icp111",
- "f77": 'ifort',
- "fc": 'ifort'
- },
- 'modules': None,
- 'spec': 'icc@11.1',
- 'operating_system': 'CNL10'
- }},
- {'compiler': {
- 'paths': {
- "cc": "<overwritten>",
- "cxx": "<overwritten>",
- "f77": '<overwritten>',
- "fc": '<overwritten>'},
- 'modules': None,
- 'spec': 'clang@3.3',
- 'operating_system': 'CNL10'
- }}
-]
+a_comps = {
+ 'compilers': [
+ {'compiler': {
+ 'paths': {
+ "cc": "/gcc473",
+ "cxx": "/g++473",
+ "f77": None,
+ "fc": None
+ },
+ 'modules': None,
+ 'spec': 'gcc@4.7.3',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "/gcc450",
+ "cxx": "/g++450",
+ "f77": 'gfortran',
+ "fc": 'gfortran'
+ },
+ 'modules': None,
+ 'spec': 'gcc@4.5.0',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "/gcc422",
+ "cxx": "/g++422",
+ "f77": 'gfortran',
+ "fc": 'gfortran'
+ },
+ 'flags': {
+ "cppflags": "-O0 -fpic",
+ "fflags": "-f77",
+ },
+ 'modules': None,
+ 'spec': 'gcc@4.2.2',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "<overwritten>",
+ "cxx": "<overwritten>",
+ "f77": '<overwritten>',
+ "fc": '<overwritten>'},
+ 'modules': None,
+ 'spec': 'clang@3.3',
+ 'operating_system': 'CNL10'
+ }}
+ ]
+}
+
+b_comps = {
+ 'compilers': [
+ {'compiler': {
+ 'paths': {
+ "cc": "/icc100",
+ "cxx": "/icp100",
+ "f77": None,
+ "fc": None
+ },
+ 'modules': None,
+ 'spec': 'icc@10.0',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "/icc111",
+ "cxx": "/icp111",
+ "f77": 'ifort',
+ "fc": 'ifort'
+ },
+ 'modules': None,
+ 'spec': 'icc@11.1',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "/icc123",
+ "cxx": "/icp123",
+ "f77": 'ifort',
+ "fc": 'ifort'
+ },
+ 'flags': {
+ "cppflags": "-O3",
+ "fflags": "-f77rtl",
+ },
+ 'modules': None,
+ 'spec': 'icc@12.3',
+ 'operating_system': 'CNL10'
+ }},
+ {'compiler': {
+ 'paths': {
+ "cc": "<overwritten>",
+ "cxx": "<overwritten>",
+ "f77": '<overwritten>',
+ "fc": '<overwritten>'},
+ 'modules': None,
+ 'spec': 'clang@3.3',
+ 'operating_system': 'CNL10'
+ }}
+ ]
+}
# Some Sample repo data
-repos_low = ["/some/path"]
-repos_high = ["/some/other/path"]
+repos_low = {'repos': ["/some/path"]}
+repos_high = {'repos': ["/some/other/path"]}
+
+
+# sample config data
+config_low = {
+ 'config': {
+ 'install_tree': 'install_tree_path',
+ 'build_stage': ['path1', 'path2', 'path3']}}
+
+config_override_all = {
+ 'config:': {
+ 'install_tree:': 'override_all'}}
+
+config_override_key = {
+ 'config': {
+ 'install_tree:': 'override_key'}}
+
+config_merge_list = {
+ 'config': {
+ 'build_stage': ['patha', 'pathb']}}
+
+config_override_list = {
+ 'config': {
+ 'build_stage:': ['patha', 'pathb']}}
class ConfigTest(MockPackagesTest):
@@ -112,73 +172,183 @@ class ConfigTest(MockPackagesTest):
def setUp(self):
super(ConfigTest, self).setUp()
self.tmp_dir = mkdtemp('.tmp', 'spack-config-test-')
+ self.a_comp_specs = [
+ ac['compiler']['spec'] for ac in a_comps['compilers']]
+ self.b_comp_specs = [
+ bc['compiler']['spec'] for bc in b_comps['compilers']]
+
spack.config.config_scopes = OrderedDict()
- spack.config.ConfigScope(
- 'test_low_priority', os.path.join(self.tmp_dir, 'low'))
- spack.config.ConfigScope('test_high_priority',
- os.path.join(self.tmp_dir, 'high'))
+ for priority in ['low', 'high']:
+ scope_dir = os.path.join(self.tmp_dir, priority)
+ spack.config.ConfigScope(priority, scope_dir)
def tearDown(self):
super(ConfigTest, self).tearDown()
shutil.rmtree(self.tmp_dir, True)
- def check_config(self, comps, *compiler_names):
+ def write_config_file(self, config, data, scope):
+ scope_dir = os.path.join(self.tmp_dir, scope)
+ mkdirp(scope_dir)
+
+ path = os.path.join(scope_dir, config + '.yaml')
+ with open(path, 'w') as f:
+ print yaml
+ yaml.dump(data, f)
+
+ def check_compiler_config(self, comps, *compiler_names):
"""Check that named compilers in comps match Spack's config."""
config = spack.config.get_config('compilers')
compiler_list = ['cc', 'cxx', 'f77', 'fc']
+ flag_list = ['cflags', 'cxxflags', 'fflags', 'cppflags',
+ 'ldflags', 'ldlibs']
param_list = ['modules', 'paths', 'spec', 'operating_system']
for compiler in config:
conf = compiler['compiler']
if conf['spec'] in compiler_names:
- comp = None
- for c in comps:
- if c['compiler']['spec'] == conf['spec']:
- comp = c['compiler']
- break
+ comp = next((c['compiler'] for c in comps if
+ c['compiler']['spec'] == conf['spec']), None)
if not comp:
self.fail('Bad config spec')
for p in param_list:
self.assertEqual(conf[p], comp[p])
+ for f in flag_list:
+ expected = comp.get('flags', {}).get(f, None)
+ actual = conf.get('flags', {}).get(f, None)
+ self.assertEqual(expected, actual)
for c in compiler_list:
expected = comp['paths'][c]
actual = conf['paths'][c]
self.assertEqual(expected, actual)
def test_write_list_in_memory(self):
- spack.config.update_config('repos', repos_low, 'test_low_priority')
- spack.config.update_config('repos', repos_high, 'test_high_priority')
+ spack.config.update_config('repos', repos_low['repos'], scope='low')
+ spack.config.update_config('repos', repos_high['repos'], scope='high')
+
config = spack.config.get_config('repos')
- self.assertEqual(config, repos_high + repos_low)
+ self.assertEqual(config, repos_high['repos'] + repos_low['repos'])
def test_write_key_in_memory(self):
# Write b_comps "on top of" a_comps.
- spack.config.update_config('compilers', a_comps, 'test_low_priority')
- spack.config.update_config('compilers', b_comps, 'test_high_priority')
+ spack.config.update_config(
+ 'compilers', a_comps['compilers'], scope='low')
+ spack.config.update_config(
+ 'compilers', b_comps['compilers'], scope='high')
# Make sure the config looks how we expect.
- self.check_config(a_comps, 'gcc@4.7.3', 'gcc@4.5.0')
- self.check_config(b_comps, 'icc@10.0', 'icc@11.1', 'clang@3.3')
+ self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
+ self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
def test_write_key_to_disk(self):
# Write b_comps "on top of" a_comps.
- spack.config.update_config('compilers', a_comps, 'test_low_priority')
- spack.config.update_config('compilers', b_comps, 'test_high_priority')
+ spack.config.update_config(
+ 'compilers', a_comps['compilers'], scope='low')
+ spack.config.update_config(
+ 'compilers', b_comps['compilers'], scope='high')
# Clear caches so we're forced to read from disk.
spack.config.clear_config_caches()
# Same check again, to ensure consistency.
- self.check_config(a_comps, 'gcc@4.7.3', 'gcc@4.5.0')
- self.check_config(b_comps, 'icc@10.0', 'icc@11.1', 'clang@3.3')
+ self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
+ self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
def test_write_to_same_priority_file(self):
# Write b_comps in the same file as a_comps.
- spack.config.update_config('compilers', a_comps, 'test_low_priority')
- spack.config.update_config('compilers', b_comps, 'test_low_priority')
+ spack.config.update_config(
+ 'compilers', a_comps['compilers'], scope='low')
+ spack.config.update_config(
+ 'compilers', b_comps['compilers'], scope='low')
# Clear caches so we're forced to read from disk.
spack.config.clear_config_caches()
# Same check again, to ensure consistency.
- self.check_config(a_comps, 'gcc@4.7.3', 'gcc@4.5.0')
- self.check_config(b_comps, 'icc@10.0', 'icc@11.1', 'clang@3.3')
+ self.check_compiler_config(a_comps['compilers'], *self.a_comp_specs)
+ self.check_compiler_config(b_comps['compilers'], *self.b_comp_specs)
+
+ def check_canonical(self, var, expected):
+ """Ensure that <expected> is substituted properly for <var> in strings
+ containing <var> in various positions."""
+ path = '/foo/bar/baz'
+
+ self.assertEqual(canonicalize_path(var + path),
+ expected + path)
+
+ self.assertEqual(canonicalize_path(path + var),
+ path + '/' + expected)
+
+ self.assertEqual(canonicalize_path(path + var + path),
+ expected + path)
+
+ def test_substitute_config_variables(self):
+ prefix = spack.prefix.lstrip('/')
+
+ self.assertEqual(os.path.join('/foo/bar/baz', prefix),
+ canonicalize_path('/foo/bar/baz/$spack'))
+
+ self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'),
+ canonicalize_path('$spack/foo/bar/baz/'))
+
+ self.assertEqual(os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
+ canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/'))
+
+ self.assertEqual(os.path.join('/foo/bar/baz', prefix),
+ canonicalize_path('/foo/bar/baz/${spack}'))
+
+ self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'),
+ canonicalize_path('${spack}/foo/bar/baz/'))
+
+ self.assertEqual(
+ os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
+ canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/'))
+
+ self.assertNotEqual(
+ os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'),
+ canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/'))
+
+ def test_substitute_user(self):
+ user = getpass.getuser()
+ self.assertEqual('/foo/bar/' + user + '/baz',
+ canonicalize_path('/foo/bar/$user/baz'))
+
+ def test_substitute_tempdir(self):
+ tempdir = tempfile.gettempdir()
+ self.assertEqual(tempdir, canonicalize_path('$tempdir'))
+ self.assertEqual(tempdir + '/foo/bar/baz',
+ canonicalize_path('$tempdir/foo/bar/baz'))
+
+ def test_read_config(self):
+ self.write_config_file('config', config_low, 'low')
+ self.assertEqual(spack.config.get_config('config'),
+ config_low['config'])
+
+ def test_read_config_override_all(self):
+ self.write_config_file('config', config_low, 'low')
+ self.write_config_file('config', config_override_all, 'high')
+ self.assertEqual(spack.config.get_config('config'), {
+ 'install_tree': 'override_all'
+ })
+
+ def test_read_config_override_key(self):
+ self.write_config_file('config', config_low, 'low')
+ self.write_config_file('config', config_override_key, 'high')
+ self.assertEqual(spack.config.get_config('config'), {
+ 'install_tree': 'override_key',
+ 'build_stage': ['path1', 'path2', 'path3']
+ })
+
+ def test_read_config_merge_list(self):
+ self.write_config_file('config', config_low, 'low')
+ self.write_config_file('config', config_merge_list, 'high')
+ self.assertEqual(spack.config.get_config('config'), {
+ 'install_tree': 'install_tree_path',
+ 'build_stage': ['patha', 'pathb', 'path1', 'path2', 'path3']
+ })
+
+ def test_read_config_override_list(self):
+ self.write_config_file('config', config_low, 'low')
+ self.write_config_file('config', config_override_list, 'high')
+ self.assertEqual(spack.config.get_config('config'), {
+ 'install_tree': 'install_tree_path',
+ 'build_stage': ['patha', 'pathb']
+ })
diff --git a/lib/spack/spack/test/data/sourceme_parameters.sh b/lib/spack/spack/test/data/sourceme_parameters.sh
new file mode 100644
index 0000000000..3826eb092b
--- /dev/null
+++ b/lib/spack/spack/test/data/sourceme_parameters.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+if [[ "$1" == "intel64" ]] ; then
+ export FOO='intel64'
+else
+ export FOO='default'
+fi \ No newline at end of file
diff --git a/lib/spack/spack/test/database.py b/lib/spack/spack/test/database.py
index 22b1f17890..1a1281e10e 100644
--- a/lib/spack/spack/test/database.py
+++ b/lib/spack/spack/test/database.py
@@ -26,10 +26,11 @@
These tests check the database is functioning properly,
both in memory and in its file
"""
-import os.path
import multiprocessing
+import os.path
import spack
+import spack.store
from llnl.util.filesystem import join_path
from llnl.util.tty.colify import colify
from spack.test.mock_database import MockDatabase
@@ -40,16 +41,16 @@ def _print_ref_counts():
recs = []
def add_rec(spec):
- cspecs = spack.installed_db.query(spec, installed=any)
+ cspecs = spack.store.db.query(spec, installed=any)
if not cspecs:
recs.append("[ %-7s ] %-20s-" % ('', spec))
else:
key = cspecs[0].dag_hash()
- rec = spack.installed_db.get_record(cspecs[0])
+ rec = spack.store.db.get_record(cspecs[0])
recs.append("[ %-7s ] %-20s%d" % (key[:7], spec, rec.ref_count))
- with spack.installed_db.read_transaction():
+ with spack.store.db.read_transaction():
add_rec('mpileaks ^mpich')
add_rec('callpath ^mpich')
add_rec('mpich')
@@ -82,22 +83,22 @@ class DatabaseTest(MockDatabase):
def test_010_all_install_sanity(self):
"""Ensure that the install layout reflects what we think it does."""
- all_specs = spack.install_layout.all_specs()
+ all_specs = spack.store.layout.all_specs()
self.assertEqual(len(all_specs), 13)
# query specs with multiple configurations
mpileaks_specs = [s for s in all_specs if s.satisfies('mpileaks')]
callpath_specs = [s for s in all_specs if s.satisfies('callpath')]
- mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
+ mpi_specs = [s for s in all_specs if s.satisfies('mpi')]
self.assertEqual(len(mpileaks_specs), 3)
self.assertEqual(len(callpath_specs), 3)
self.assertEqual(len(mpi_specs), 3)
# query specs with single configurations
- dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
+ dyninst_specs = [s for s in all_specs if s.satisfies('dyninst')]
libdwarf_specs = [s for s in all_specs if s.satisfies('libdwarf')]
- libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
+ libelf_specs = [s for s in all_specs if s.satisfies('libelf')]
self.assertEqual(len(dyninst_specs), 1)
self.assertEqual(len(libdwarf_specs), 1)
@@ -113,34 +114,54 @@ class DatabaseTest(MockDatabase):
def test_015_write_and_read(self):
# write and read DB
- with spack.installed_db.write_transaction():
- specs = spack.installed_db.query()
- recs = [spack.installed_db.get_record(s) for s in specs]
+ with spack.store.db.write_transaction():
+ specs = spack.store.db.query()
+ recs = [spack.store.db.get_record(s) for s in specs]
for spec, rec in zip(specs, recs):
- new_rec = spack.installed_db.get_record(spec)
+ new_rec = spack.store.db.get_record(spec)
self.assertEqual(new_rec.ref_count, rec.ref_count)
self.assertEqual(new_rec.spec, rec.spec)
self.assertEqual(new_rec.path, rec.path)
self.assertEqual(new_rec.installed, rec.installed)
+ def _check_merkleiness(self):
+ """Ensure the spack database is a valid merkle graph."""
+ all_specs = spack.store.db.query(installed=any)
+
+ seen = {}
+ for spec in all_specs:
+ for dep in spec.dependencies():
+ hash_key = dep.dag_hash()
+ if hash_key not in seen:
+ seen[hash_key] = id(dep)
+ else:
+ self.assertEqual(seen[hash_key], id(dep))
+
def _check_db_sanity(self):
"""Utiilty function to check db against install layout."""
- expected = sorted(spack.install_layout.all_specs())
- actual = sorted(self.installed_db.query())
+ expected = sorted(spack.store.layout.all_specs())
+ actual = sorted(self.install_db.query())
self.assertEqual(len(expected), len(actual))
for e, a in zip(expected, actual):
self.assertEqual(e, a)
+ self._check_merkleiness()
+
def test_020_db_sanity(self):
"""Make sure query() returns what's actually in the db."""
self._check_db_sanity()
+ def test_025_reindex(self):
+ """Make sure reindex works and ref counts are valid."""
+ spack.store.db.reindex(spack.store.layout)
+ self._check_db_sanity()
+
def test_030_db_sanity_from_another_process(self):
def read_and_modify():
self._check_db_sanity() # check that other process can read DB
- with self.installed_db.write_transaction():
+ with self.install_db.write_transaction():
self._mock_remove('mpileaks ^zmpi')
p = multiprocessing.Process(target=read_and_modify, args=())
@@ -148,40 +169,40 @@ class DatabaseTest(MockDatabase):
p.join()
# ensure child process change is visible in parent process
- with self.installed_db.read_transaction():
- self.assertEqual(len(self.installed_db.query('mpileaks ^zmpi')), 0)
+ with self.install_db.read_transaction():
+ self.assertEqual(len(self.install_db.query('mpileaks ^zmpi')), 0)
def test_040_ref_counts(self):
"""Ensure that we got ref counts right when we read the DB."""
- self.installed_db._check_ref_counts()
+ self.install_db._check_ref_counts()
def test_050_basic_query(self):
"""Ensure querying database is consistent with what is installed."""
# query everything
- self.assertEqual(len(spack.installed_db.query()), 13)
+ self.assertEqual(len(spack.store.db.query()), 13)
# query specs with multiple configurations
- mpileaks_specs = self.installed_db.query('mpileaks')
- callpath_specs = self.installed_db.query('callpath')
- mpi_specs = self.installed_db.query('mpi')
+ mpileaks_specs = self.install_db.query('mpileaks')
+ callpath_specs = self.install_db.query('callpath')
+ mpi_specs = self.install_db.query('mpi')
self.assertEqual(len(mpileaks_specs), 3)
self.assertEqual(len(callpath_specs), 3)
self.assertEqual(len(mpi_specs), 3)
# query specs with single configurations
- dyninst_specs = self.installed_db.query('dyninst')
- libdwarf_specs = self.installed_db.query('libdwarf')
- libelf_specs = self.installed_db.query('libelf')
+ dyninst_specs = self.install_db.query('dyninst')
+ libdwarf_specs = self.install_db.query('libdwarf')
+ libelf_specs = self.install_db.query('libelf')
self.assertEqual(len(dyninst_specs), 1)
self.assertEqual(len(libdwarf_specs), 1)
self.assertEqual(len(libelf_specs), 1)
# Query by dependency
- self.assertEqual(len(self.installed_db.query('mpileaks ^mpich')), 1)
- self.assertEqual(len(self.installed_db.query('mpileaks ^mpich2')), 1)
- self.assertEqual(len(self.installed_db.query('mpileaks ^zmpi')), 1)
+ self.assertEqual(len(self.install_db.query('mpileaks ^mpich')), 1)
+ self.assertEqual(len(self.install_db.query('mpileaks ^mpich2')), 1)
+ self.assertEqual(len(self.install_db.query('mpileaks ^zmpi')), 1)
def _check_remove_and_add_package(self, spec):
"""Remove a spec from the DB, then add it and make sure everything's
@@ -189,13 +210,13 @@ class DatabaseTest(MockDatabase):
removed, that it's back when added again, and that ref
counts are consistent.
"""
- original = self.installed_db.query()
- self.installed_db._check_ref_counts()
+ original = self.install_db.query()
+ self.install_db._check_ref_counts()
# Remove spec
- concrete_spec = self.installed_db.remove(spec)
- self.installed_db._check_ref_counts()
- remaining = self.installed_db.query()
+ concrete_spec = self.install_db.remove(spec)
+ self.install_db._check_ref_counts()
+ remaining = self.install_db.query()
# ensure spec we removed is gone
self.assertEqual(len(original) - 1, len(remaining))
@@ -203,13 +224,14 @@ class DatabaseTest(MockDatabase):
self.assertTrue(concrete_spec not in remaining)
# add it back and make sure everything is ok.
- self.installed_db.add(concrete_spec, "")
- installed = self.installed_db.query()
- self.assertEqual(len(installed), len(original))
+ self.install_db.add(concrete_spec, spack.store.layout)
+ installed = self.install_db.query()
+ self.assertTrue(concrete_spec in installed)
+ self.assertEqual(installed, original)
# sanity check against direcory layout and check ref counts.
self._check_db_sanity()
- self.installed_db._check_ref_counts()
+ self.install_db._check_ref_counts()
def test_060_remove_and_add_root_package(self):
self._check_remove_and_add_package('mpileaks ^mpich')
@@ -218,95 +240,95 @@ class DatabaseTest(MockDatabase):
self._check_remove_and_add_package('dyninst')
def test_080_root_ref_counts(self):
- rec = self.installed_db.get_record('mpileaks ^mpich')
+ rec = self.install_db.get_record('mpileaks ^mpich')
# Remove a top-level spec from the DB
- self.installed_db.remove('mpileaks ^mpich')
+ self.install_db.remove('mpileaks ^mpich')
# record no longer in DB
self.assertEqual(
- self.installed_db.query('mpileaks ^mpich', installed=any), [])
+ self.install_db.query('mpileaks ^mpich', installed=any), [])
# record's deps have updated ref_counts
self.assertEqual(
- self.installed_db.get_record('callpath ^mpich').ref_count, 0)
- self.assertEqual(self.installed_db.get_record('mpich').ref_count, 1)
+ self.install_db.get_record('callpath ^mpich').ref_count, 0)
+ self.assertEqual(self.install_db.get_record('mpich').ref_count, 1)
# Put the spec back
- self.installed_db.add(rec.spec, rec.path)
+ self.install_db.add(rec.spec, spack.store.layout)
# record is present again
self.assertEqual(
- len(self.installed_db.query('mpileaks ^mpich', installed=any)), 1)
+ len(self.install_db.query('mpileaks ^mpich', installed=any)), 1)
# dependencies have ref counts updated
self.assertEqual(
- self.installed_db.get_record('callpath ^mpich').ref_count, 1)
- self.assertEqual(self.installed_db.get_record('mpich').ref_count, 2)
+ self.install_db.get_record('callpath ^mpich').ref_count, 1)
+ self.assertEqual(self.install_db.get_record('mpich').ref_count, 2)
def test_090_non_root_ref_counts(self):
- self.installed_db.get_record('mpileaks ^mpich')
- self.installed_db.get_record('callpath ^mpich')
+ self.install_db.get_record('mpileaks ^mpich')
+ self.install_db.get_record('callpath ^mpich')
# "force remove" a non-root spec from the DB
- self.installed_db.remove('callpath ^mpich')
+ self.install_db.remove('callpath ^mpich')
# record still in DB but marked uninstalled
self.assertEqual(
- self.installed_db.query('callpath ^mpich', installed=True), [])
+ self.install_db.query('callpath ^mpich', installed=True), [])
self.assertEqual(
- len(self.installed_db.query('callpath ^mpich', installed=any)), 1)
+ len(self.install_db.query('callpath ^mpich', installed=any)), 1)
# record and its deps have same ref_counts
- self.assertEqual(self.installed_db.get_record(
+ self.assertEqual(self.install_db.get_record(
'callpath ^mpich', installed=any).ref_count, 1)
- self.assertEqual(self.installed_db.get_record('mpich').ref_count, 2)
+ self.assertEqual(self.install_db.get_record('mpich').ref_count, 2)
# remove only dependent of uninstalled callpath record
- self.installed_db.remove('mpileaks ^mpich')
+ self.install_db.remove('mpileaks ^mpich')
# record and parent are completely gone.
self.assertEqual(
- self.installed_db.query('mpileaks ^mpich', installed=any), [])
+ self.install_db.query('mpileaks ^mpich', installed=any), [])
self.assertEqual(
- self.installed_db.query('callpath ^mpich', installed=any), [])
+ self.install_db.query('callpath ^mpich', installed=any), [])
# mpich ref count updated properly.
- mpich_rec = self.installed_db.get_record('mpich')
+ mpich_rec = self.install_db.get_record('mpich')
self.assertEqual(mpich_rec.ref_count, 0)
def test_100_no_write_with_exception_on_remove(self):
def fail_while_writing():
- with self.installed_db.write_transaction():
+ with self.install_db.write_transaction():
self._mock_remove('mpileaks ^zmpi')
raise Exception()
- with self.installed_db.read_transaction():
+ with self.install_db.read_transaction():
self.assertEqual(
- len(self.installed_db.query('mpileaks ^zmpi', installed=any)),
+ len(self.install_db.query('mpileaks ^zmpi', installed=any)),
1)
self.assertRaises(Exception, fail_while_writing)
# reload DB and make sure zmpi is still there.
- with self.installed_db.read_transaction():
+ with self.install_db.read_transaction():
self.assertEqual(
- len(self.installed_db.query('mpileaks ^zmpi', installed=any)),
+ len(self.install_db.query('mpileaks ^zmpi', installed=any)),
1)
def test_110_no_write_with_exception_on_install(self):
def fail_while_writing():
- with self.installed_db.write_transaction():
+ with self.install_db.write_transaction():
self._mock_install('cmake')
raise Exception()
- with self.installed_db.read_transaction():
+ with self.install_db.read_transaction():
self.assertEqual(
- self.installed_db.query('cmake', installed=any), [])
+ self.install_db.query('cmake', installed=any), [])
self.assertRaises(Exception, fail_while_writing)
# reload DB and make sure cmake was not written.
- with self.installed_db.read_transaction():
+ with self.install_db.read_transaction():
self.assertEqual(
- self.installed_db.query('cmake', installed=any), [])
+ self.install_db.query('cmake', installed=any), [])
diff --git a/lib/spack/spack/test/directory_layout.py b/lib/spack/spack/test/directory_layout.py
index 2d0565acae..fdaa43464b 100644
--- a/lib/spack/spack/test/directory_layout.py
+++ b/lib/spack/spack/test/directory_layout.py
@@ -91,22 +91,33 @@ class DirectoryLayoutTest(MockPackagesTest):
# Make sure spec file can be read back in to get the original spec
spec_from_file = self.layout.read_spec(spec_path)
- self.assertEqual(spec, spec_from_file)
- self.assertTrue(spec.eq_dag, spec_from_file)
+
+ # currently we don't store build dependency information when
+ # we write out specs to the filesystem.
+
+ # TODO: fix this when we can concretize more loosely based on
+ # TODO: what is installed. We currently omit these to
+ # TODO: increase reuse of build dependencies.
+ stored_deptypes = ('link', 'run')
+ expected = spec.copy(deps=stored_deptypes)
+ self.assertEqual(expected, spec_from_file)
+ self.assertTrue(expected.eq_dag, spec_from_file)
self.assertTrue(spec_from_file.concrete)
# Ensure that specs that come out "normal" are really normal.
with open(spec_path) as spec_file:
read_separately = Spec.from_yaml(spec_file.read())
- read_separately.normalize()
- self.assertEqual(read_separately, spec_from_file)
+ # TODO: revise this when build deps are in dag_hash
+ norm = read_separately.normalized().copy(deps=stored_deptypes)
+ self.assertEqual(norm, spec_from_file)
- read_separately.concretize()
- self.assertEqual(read_separately, spec_from_file)
+ # TODO: revise this when build deps are in dag_hash
+ conc = read_separately.concretized().copy(deps=stored_deptypes)
+ self.assertEqual(conc, spec_from_file)
# Make sure the hash of the read-in spec is the same
- self.assertEqual(spec.dag_hash(), spec_from_file.dag_hash())
+ self.assertEqual(expected.dag_hash(), spec_from_file.dag_hash())
# Ensure directories are properly removed
self.layout.remove_install_directory(spec)
diff --git a/lib/spack/spack/test/environment.py b/lib/spack/spack/test/environment.py
index 9b5d75f273..2b499869c5 100644
--- a/lib/spack/spack/test/environment.py
+++ b/lib/spack/spack/test/environment.py
@@ -30,6 +30,7 @@ from llnl.util.filesystem import join_path
from spack.environment import EnvironmentModifications
from spack.environment import SetEnv, UnsetEnv
from spack.environment import RemovePath, PrependPath, AppendPath
+from spack.util.environment import filter_system_paths, filter_system_bin_paths
class EnvironmentTest(unittest.TestCase):
@@ -59,6 +60,41 @@ class EnvironmentTest(unittest.TestCase):
env.apply_modifications()
self.assertRaises(KeyError, os.environ.__getitem__, 'UNSET_ME')
+ def test_filter_system_paths(self):
+ filtered = filter_system_paths([
+ '/usr/local/Cellar/gcc/5.3.0/lib',
+ '/usr/local/lib',
+ '/usr/local/include',
+ '/usr/local/lib64',
+ '/usr/local/opt/some-package/lib',
+ '/usr/opt/lib',
+ '/lib',
+ '/lib64',
+ '/include',
+ '/opt/some-package/include',
+ ])
+ self.assertEqual(filtered,
+ ['/usr/local/Cellar/gcc/5.3.0/lib',
+ '/usr/local/opt/some-package/lib',
+ '/usr/opt/lib',
+ '/opt/some-package/include'])
+
+ filtered = filter_system_bin_paths([
+ '/usr/local/Cellar/gcc/5.3.0/bin',
+ '/usr/local/bin',
+ '/usr/local/opt/some-package/bin',
+ '/usr/opt/bin',
+ '/bin',
+ '/opt/some-package/bin',
+ ])
+ self.assertEqual(filtered,
+ ['/usr/local/bin',
+ '/bin',
+ '/usr/local/Cellar/gcc/5.3.0/bin',
+ '/usr/local/opt/some-package/bin',
+ '/usr/opt/bin',
+ '/opt/some-package/bin'])
+
def test_set_path(self):
env = EnvironmentModifications()
env.set_path('A', ['foo', 'bar', 'baz'])
@@ -119,7 +155,8 @@ class EnvironmentTest(unittest.TestCase):
'spack', 'test', 'data')
files = [
join_path(datadir, 'sourceme_first.sh'),
- join_path(datadir, 'sourceme_second.sh')
+ join_path(datadir, 'sourceme_second.sh'),
+ join_path(datadir, 'sourceme_parameters.sh intel64')
]
env = EnvironmentModifications.from_sourcing_files(*files)
modifications = env.group_by_name()
@@ -134,6 +171,11 @@ class EnvironmentTest(unittest.TestCase):
self.assertEqual(len(modifications['NEW_VAR']), 1)
self.assertTrue(isinstance(modifications['NEW_VAR'][0], SetEnv))
self.assertEqual(modifications['NEW_VAR'][0].value, 'new')
+
+ self.assertEqual(len(modifications['FOO']), 1)
+ self.assertTrue(isinstance(modifications['FOO'][0], SetEnv))
+ self.assertEqual(modifications['FOO'][0].value, 'intel64')
+
# Unset variables
self.assertEqual(len(modifications['EMPTY_PATH_LIST']), 1)
self.assertTrue(isinstance(
diff --git a/lib/spack/spack/test/git_fetch.py b/lib/spack/spack/test/git_fetch.py
index 0d1a8fe949..7aff98cc54 100644
--- a/lib/spack/spack/test/git_fetch.py
+++ b/lib/spack/spack/test/git_fetch.py
@@ -56,12 +56,13 @@ class GitFetchTest(MockPackagesTest):
def try_fetch(self, rev, test_file, args):
"""Tries to:
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args.
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
"""
self.pkg.versions[ver('git')] = args
diff --git a/lib/spack/spack/test/hg_fetch.py b/lib/spack/spack/test/hg_fetch.py
index 44af6730a1..03e35ea093 100644
--- a/lib/spack/spack/test/hg_fetch.py
+++ b/lib/spack/spack/test/hg_fetch.py
@@ -52,12 +52,13 @@ class HgFetchTest(MockPackagesTest):
def try_fetch(self, rev, test_file, args):
"""Tries to:
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args.
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
"""
self.pkg.versions[ver('hg')] = args
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
index 232d5aeeaf..0524c14cfd 100644
--- a/lib/spack/spack/test/install.py
+++ b/lib/spack/spack/test/install.py
@@ -26,6 +26,7 @@ import shutil
import tempfile
import spack
+import spack.store
from llnl.util.filesystem import *
from spack.directory_layout import YamlDirectoryLayout
from spack.database import Database
@@ -49,11 +50,11 @@ class InstallTest(MockPackagesTest):
# Use a fake install directory to avoid conflicts bt/w
# installed pkgs and mock packages.
self.tmpdir = tempfile.mkdtemp()
- self.orig_layout = spack.install_layout
- self.orig_db = spack.installed_db
+ self.orig_layout = spack.store.layout
+ self.orig_db = spack.store.db
- spack.install_layout = YamlDirectoryLayout(self.tmpdir)
- spack.installed_db = Database(self.tmpdir)
+ spack.store.layout = YamlDirectoryLayout(self.tmpdir)
+ spack.store.db = Database(self.tmpdir)
def tearDown(self):
super(InstallTest, self).tearDown()
@@ -63,8 +64,8 @@ class InstallTest(MockPackagesTest):
spack.do_checksum = True
# restore spack's layout.
- spack.install_layout = self.orig_layout
- spack.installed_db = self.orig_db
+ spack.store.layout = self.orig_layout
+ spack.store.db = self.orig_db
shutil.rmtree(self.tmpdir, ignore_errors=True)
def fake_fetchify(self, pkg):
@@ -91,7 +92,7 @@ class InstallTest(MockPackagesTest):
pkg.remove_prefix()
raise
- def test_install_environment(self):
+ def test_store(self):
spec = Spec('cmake-client').concretized()
for s in spec.traverse():
diff --git a/lib/spack/spack/test/library_list.py b/lib/spack/spack/test/library_list.py
new file mode 100644
index 0000000000..7fc2fd222f
--- /dev/null
+++ b/lib/spack/spack/test/library_list.py
@@ -0,0 +1,111 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+
+import unittest
+
+from llnl.util.filesystem import LibraryList
+
+
+class LibraryListTest(unittest.TestCase):
+ def setUp(self):
+ l = [
+ '/dir1/liblapack.a',
+ '/dir2/libfoo.dylib',
+ '/dir1/libblas.a',
+ '/dir3/libbar.so',
+ 'libbaz.so'
+ ]
+ self.liblist = LibraryList(l)
+
+ def test_repr(self):
+ x = eval(repr(self.liblist))
+ self.assertEqual(self.liblist, x)
+
+ def test_joined_and_str(self):
+ s1 = self.liblist.joined()
+ self.assertEqual(
+ s1,
+ '/dir1/liblapack.a /dir2/libfoo.dylib /dir1/libblas.a /dir3/libbar.so libbaz.so' # NOQA: ignore=E501
+ )
+ s2 = str(self.liblist)
+ self.assertEqual(s1, s2)
+ s3 = self.liblist.joined(';')
+ self.assertEqual(
+ s3,
+ '/dir1/liblapack.a;/dir2/libfoo.dylib;/dir1/libblas.a;/dir3/libbar.so;libbaz.so' # NOQA: ignore=E501
+ )
+
+ def test_flags(self):
+ search_flags = self.liblist.search_flags
+ self.assertTrue('-L/dir1' in search_flags)
+ self.assertTrue('-L/dir2' in search_flags)
+ self.assertTrue('-L/dir3' in search_flags)
+ self.assertTrue(isinstance(search_flags, str))
+
+ link_flags = self.liblist.link_flags
+ self.assertEqual(
+ link_flags,
+ '-llapack -lfoo -lblas -lbar -lbaz'
+ )
+
+ ld_flags = self.liblist.ld_flags
+ self.assertEqual(ld_flags, search_flags + ' ' + link_flags)
+
+ def test_paths_manipulation(self):
+ names = self.liblist.names
+ self.assertEqual(names, ['lapack', 'foo', 'blas', 'bar', 'baz'])
+
+ directories = self.liblist.directories
+ self.assertEqual(directories, ['/dir1', '/dir2', '/dir3'])
+
+ def test_get_item(self):
+ a = self.liblist[0]
+ self.assertEqual(a, '/dir1/liblapack.a')
+
+ b = self.liblist[:]
+ self.assertEqual(type(b), type(self.liblist))
+ self.assertEqual(self.liblist, b)
+ self.assertTrue(self.liblist is not b)
+
+ def test_add(self):
+ pylist = [
+ '/dir1/liblapack.a', # removed from the final list
+ '/dir2/libbaz.so',
+ '/dir4/libnew.a'
+ ]
+ another = LibraryList(pylist)
+ l = self.liblist + another
+ self.assertEqual(len(l), 7)
+ # Invariant : l == l + l
+ self.assertEqual(l, l + l)
+ # Always produce an instance of LibraryList
+ self.assertEqual(
+ type(self.liblist),
+ type(self.liblist + pylist)
+ )
+ self.assertEqual(
+ type(pylist + self.liblist),
+ type(self.liblist)
+ )
diff --git a/lib/spack/spack/test/lock.py b/lib/spack/spack/test/lock.py
index fb96539897..4f62cd85e9 100644
--- a/lib/spack/spack/test/lock.py
+++ b/lib/spack/spack/test/lock.py
@@ -25,6 +25,7 @@
"""
These tests ensure that our lock works correctly.
"""
+import os
import shutil
import tempfile
import unittest
@@ -44,7 +45,6 @@ class LockTest(unittest.TestCase):
def setUp(self):
self.tempdir = tempfile.mkdtemp()
self.lock_path = join_path(self.tempdir, 'lockfile')
- touch(self.lock_path)
def tearDown(self):
shutil.rmtree(self.tempdir, ignore_errors=True)
@@ -64,98 +64,237 @@ class LockTest(unittest.TestCase):
#
# Process snippets below can be composed into tests.
#
- def acquire_write(self, barrier):
- lock = Lock(self.lock_path)
- lock.acquire_write() # grab exclusive lock
- barrier.wait()
- barrier.wait() # hold the lock until exception raises in other procs.
-
- def acquire_read(self, barrier):
- lock = Lock(self.lock_path)
- lock.acquire_read() # grab shared lock
- barrier.wait()
- barrier.wait() # hold the lock until exception raises in other procs.
-
- def timeout_write(self, barrier):
- lock = Lock(self.lock_path)
- barrier.wait() # wait for lock acquire in first process
- self.assertRaises(LockError, lock.acquire_write, 0.1)
- barrier.wait()
+ def acquire_write(self, start=0, length=0):
+ def fn(barrier):
+ lock = Lock(self.lock_path, start, length)
+ lock.acquire_write() # grab exclusive lock
+ barrier.wait()
+ barrier.wait() # hold the lock until timeout in other procs.
+ return fn
+
+ def acquire_read(self, start=0, length=0):
+ def fn(barrier):
+ lock = Lock(self.lock_path, start, length)
+ lock.acquire_read() # grab shared lock
+ barrier.wait()
+ barrier.wait() # hold the lock until timeout in other procs.
+ return fn
+
+ def timeout_write(self, start=0, length=0):
+ def fn(barrier):
+ lock = Lock(self.lock_path, start, length)
+ barrier.wait() # wait for lock acquire in first process
+ self.assertRaises(LockError, lock.acquire_write, 0.1)
+ barrier.wait()
+ return fn
- def timeout_read(self, barrier):
- lock = Lock(self.lock_path)
- barrier.wait() # wait for lock acquire in first process
- self.assertRaises(LockError, lock.acquire_read, 0.1)
- barrier.wait()
+ def timeout_read(self, start=0, length=0):
+ def fn(barrier):
+ lock = Lock(self.lock_path, start, length)
+ barrier.wait() # wait for lock acquire in first process
+ self.assertRaises(LockError, lock.acquire_read, 0.1)
+ barrier.wait()
+ return fn
#
# Test that exclusive locks on other processes time out when an
# exclusive lock is held.
#
def test_write_lock_timeout_on_write(self):
- self.multiproc_test(self.acquire_write, self.timeout_write)
+ self.multiproc_test(self.acquire_write(), self.timeout_write())
def test_write_lock_timeout_on_write_2(self):
self.multiproc_test(
- self.acquire_write, self.timeout_write, self.timeout_write)
+ self.acquire_write(), self.timeout_write(), self.timeout_write())
def test_write_lock_timeout_on_write_3(self):
self.multiproc_test(
- self.acquire_write, self.timeout_write, self.timeout_write,
- self.timeout_write)
+ self.acquire_write(), self.timeout_write(), self.timeout_write(),
+ self.timeout_write())
+
+ def test_write_lock_timeout_on_write_ranges(self):
+ self.multiproc_test(
+ self.acquire_write(0, 1), self.timeout_write(0, 1))
+
+ def test_write_lock_timeout_on_write_ranges_2(self):
+ self.multiproc_test(
+ self.acquire_write(0, 64), self.acquire_write(65, 1),
+ self.timeout_write(0, 1), self.timeout_write(63, 1))
+
+ def test_write_lock_timeout_on_write_ranges_3(self):
+ self.multiproc_test(
+ self.acquire_write(0, 1), self.acquire_write(1, 1),
+ self.timeout_write(), self.timeout_write(), self.timeout_write())
+
+ def test_write_lock_timeout_on_write_ranges_4(self):
+ self.multiproc_test(
+ self.acquire_write(0, 1), self.acquire_write(1, 1),
+ self.acquire_write(2, 456), self.acquire_write(500, 64),
+ self.timeout_write(), self.timeout_write(), self.timeout_write())
#
# Test that shared locks on other processes time out when an
# exclusive lock is held.
#
def test_read_lock_timeout_on_write(self):
- self.multiproc_test(self.acquire_write, self.timeout_read)
+ self.multiproc_test(self.acquire_write(), self.timeout_read())
def test_read_lock_timeout_on_write_2(self):
self.multiproc_test(
- self.acquire_write, self.timeout_read, self.timeout_read)
+ self.acquire_write(), self.timeout_read(), self.timeout_read())
def test_read_lock_timeout_on_write_3(self):
self.multiproc_test(
- self.acquire_write, self.timeout_read, self.timeout_read,
- self.timeout_read)
+ self.acquire_write(), self.timeout_read(), self.timeout_read(),
+ self.timeout_read())
+
+ def test_read_lock_timeout_on_write_ranges(self):
+ """small write lock, read whole file."""
+ self.multiproc_test(self.acquire_write(0, 1), self.timeout_read())
+
+ def test_read_lock_timeout_on_write_ranges_2(self):
+ """small write lock, small read lock"""
+ self.multiproc_test(self.acquire_write(0, 1), self.timeout_read(0, 1))
+
+ def test_read_lock_timeout_on_write_ranges_3(self):
+ """two write locks, overlapping read locks"""
+ self.multiproc_test(
+ self.acquire_write(0, 1), self.acquire_write(64, 128),
+ self.timeout_read(0, 1), self.timeout_read(128, 256))
#
# Test that exclusive locks time out when shared locks are held.
#
def test_write_lock_timeout_on_read(self):
- self.multiproc_test(self.acquire_read, self.timeout_write)
+ self.multiproc_test(self.acquire_read(), self.timeout_write())
def test_write_lock_timeout_on_read_2(self):
self.multiproc_test(
- self.acquire_read, self.timeout_write, self.timeout_write)
+ self.acquire_read(), self.timeout_write(), self.timeout_write())
def test_write_lock_timeout_on_read_3(self):
self.multiproc_test(
- self.acquire_read, self.timeout_write, self.timeout_write,
- self.timeout_write)
+ self.acquire_read(), self.timeout_write(), self.timeout_write(),
+ self.timeout_write())
+
+ def test_write_lock_timeout_on_read_ranges(self):
+ self.multiproc_test(self.acquire_read(0, 1), self.timeout_write())
+
+ def test_write_lock_timeout_on_read_ranges_2(self):
+ self.multiproc_test(self.acquire_read(0, 1), self.timeout_write(0, 1))
+
+ def test_write_lock_timeout_on_read_ranges_3(self):
+ self.multiproc_test(
+ self.acquire_read(0, 1), self.acquire_read(10, 1),
+ self.timeout_write(0, 1), self.timeout_write(10, 1))
+
+ def test_write_lock_timeout_on_read_ranges_4(self):
+ self.multiproc_test(
+ self.acquire_read(0, 64),
+ self.timeout_write(10, 1), self.timeout_write(32, 1))
+
+ def test_write_lock_timeout_on_read_ranges_5(self):
+ self.multiproc_test(
+ self.acquire_read(64, 128),
+ self.timeout_write(65, 1), self.timeout_write(127, 1),
+ self.timeout_write(90, 10))
#
# Test that exclusive locks time while lots of shared locks are held.
#
def test_write_lock_timeout_with_multiple_readers_2_1(self):
self.multiproc_test(
- self.acquire_read, self.acquire_read, self.timeout_write)
+ self.acquire_read(), self.acquire_read(), self.timeout_write())
def test_write_lock_timeout_with_multiple_readers_2_2(self):
self.multiproc_test(
- self.acquire_read, self.acquire_read, self.timeout_write,
- self.timeout_write)
+ self.acquire_read(), self.acquire_read(), self.timeout_write(),
+ self.timeout_write())
def test_write_lock_timeout_with_multiple_readers_3_1(self):
self.multiproc_test(
- self.acquire_read, self.acquire_read, self.acquire_read,
- self.timeout_write)
+ self.acquire_read(), self.acquire_read(), self.acquire_read(),
+ self.timeout_write())
def test_write_lock_timeout_with_multiple_readers_3_2(self):
self.multiproc_test(
- self.acquire_read, self.acquire_read, self.acquire_read,
- self.timeout_write, self.timeout_write)
+ self.acquire_read(), self.acquire_read(), self.acquire_read(),
+ self.timeout_write(), self.timeout_write())
+
+ def test_write_lock_timeout_with_multiple_readers_2_1_ranges(self):
+ self.multiproc_test(
+ self.acquire_read(0, 10), self.acquire_read(5, 10),
+ self.timeout_write(5, 5))
+
+ def test_write_lock_timeout_with_multiple_readers_2_3_ranges(self):
+ self.multiproc_test(
+ self.acquire_read(0, 10), self.acquire_read(5, 15),
+ self.timeout_write(0, 1), self.timeout_write(11, 3),
+ self.timeout_write(7, 1))
+
+ def test_write_lock_timeout_with_multiple_readers_3_1_ranges(self):
+ self.multiproc_test(
+ self.acquire_read(0, 5), self.acquire_read(5, 5),
+ self.acquire_read(10, 5),
+ self.timeout_write(0, 15))
+
+ def test_write_lock_timeout_with_multiple_readers_3_2_ranges(self):
+ self.multiproc_test(
+ self.acquire_read(0, 5), self.acquire_read(5, 5),
+ self.acquire_read(10, 5),
+ self.timeout_write(3, 10), self.timeout_write(5, 1))
+
+ #
+ # Test that read can be upgraded to write.
+ #
+ def test_upgrade_read_to_write(self):
+ # ensure lock file exists the first time, so we open it read-only
+ # to begin wtih.
+ touch(self.lock_path)
+
+ lock = Lock(self.lock_path)
+ self.assertTrue(lock._reads == 0)
+ self.assertTrue(lock._writes == 0)
+
+ lock.acquire_read()
+ self.assertTrue(lock._reads == 1)
+ self.assertTrue(lock._writes == 0)
+ self.assertTrue(lock._file.mode == 'r+')
+
+ lock.acquire_write()
+ self.assertTrue(lock._reads == 1)
+ self.assertTrue(lock._writes == 1)
+ self.assertTrue(lock._file.mode == 'r+')
+
+ lock.release_write()
+ self.assertTrue(lock._reads == 1)
+ self.assertTrue(lock._writes == 0)
+ self.assertTrue(lock._file.mode == 'r+')
+
+ lock.release_read()
+ self.assertTrue(lock._reads == 0)
+ self.assertTrue(lock._writes == 0)
+ self.assertTrue(lock._file is None)
+
+ #
+ # Test that read-only file can be read-locked but not write-locked.
+ #
+ def test_upgrade_read_to_write_fails_with_readonly_file(self):
+ # ensure lock file exists the first time, so we open it read-only
+ # to begin wtih.
+ touch(self.lock_path)
+ os.chmod(self.lock_path, 0444)
+
+ lock = Lock(self.lock_path)
+ self.assertTrue(lock._reads == 0)
+ self.assertTrue(lock._writes == 0)
+
+ lock.acquire_read()
+ self.assertTrue(lock._reads == 1)
+ self.assertTrue(lock._writes == 0)
+ self.assertTrue(lock._file.mode == 'r')
+
+ self.assertRaises(LockError, lock.acquire_write)
#
# Longer test case that ensures locks are reusable. Ordering is
diff --git a/lib/spack/spack/test/mirror.py b/lib/spack/spack/test/mirror.py
index d6d7b30b7c..f9ff190468 100644
--- a/lib/spack/spack/test/mirror.py
+++ b/lib/spack/spack/test/mirror.py
@@ -52,9 +52,10 @@ class MirrorTest(MockPackagesTest):
def set_up_package(self, name, MockRepoClass, url_attr):
"""Set up a mock package to be mirrored.
- Each package needs us to:
- 1. Set up a mock repo/archive to fetch from.
- 2. Point the package's version args at that repo.
+ Each package needs us to:
+
+ 1. Set up a mock repo/archive to fetch from.
+ 2. Point the package's version args at that repo.
"""
# Set up packages to point at mock repos.
spec = Spec(name)
diff --git a/lib/spack/spack/test/mock_database.py b/lib/spack/spack/test/mock_database.py
index d5867f06ec..1eaec2b598 100644
--- a/lib/spack/spack/test/mock_database.py
+++ b/lib/spack/spack/test/mock_database.py
@@ -26,6 +26,7 @@ import shutil
import tempfile
import spack
+import spack.store
from spack.spec import Spec
from spack.database import Database
from spack.directory_layout import YamlDirectoryLayout
@@ -41,7 +42,7 @@ class MockDatabase(MockPackagesTest):
pkg.do_install(fake=True)
def _mock_remove(self, spec):
- specs = spack.installed_db.query(spec)
+ specs = spack.store.db.query(spec)
assert len(specs) == 1
spec = specs[0]
spec.package.do_uninstall(spec)
@@ -54,17 +55,17 @@ class MockDatabase(MockPackagesTest):
# Make a fake install directory
self.install_path = tempfile.mkdtemp()
- self.spack_install_path = spack.install_path
- spack.install_path = self.install_path
+ self.spack_install_path = spack.store.root
+ spack.store.root = self.install_path
self.install_layout = YamlDirectoryLayout(self.install_path)
- self.spack_install_layout = spack.install_layout
- spack.install_layout = self.install_layout
+ self.spack_install_layout = spack.store.layout
+ spack.store.layout = self.install_layout
# Make fake database and fake install directory.
- self.installed_db = Database(self.install_path)
- self.spack_installed_db = spack.installed_db
- spack.installed_db = self.installed_db
+ self.install_db = Database(self.install_path)
+ self.spack_install_db = spack.store.db
+ spack.store.db = self.install_db
# make a mock database with some packages installed note that
# the ref count for dyninst here will be 3, as it's recycled
@@ -90,18 +91,18 @@ class MockDatabase(MockPackagesTest):
#
# Transaction used to avoid repeated writes.
- with spack.installed_db.write_transaction():
+ with spack.store.db.write_transaction():
self._mock_install('mpileaks ^mpich')
self._mock_install('mpileaks ^mpich2')
self._mock_install('mpileaks ^zmpi')
def tearDown(self):
- with spack.installed_db.write_transaction():
- for spec in spack.installed_db.query():
+ with spack.store.db.write_transaction():
+ for spec in spack.store.db.query():
spec.package.do_uninstall(spec)
super(MockDatabase, self).tearDown()
shutil.rmtree(self.install_path)
- spack.install_path = self.spack_install_path
- spack.install_layout = self.spack_install_layout
- spack.installed_db = self.spack_installed_db
+ spack.store.root = self.spack_install_path
+ spack.store.layout = self.spack_install_layout
+ spack.store.db = self.spack_install_db
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
index 82c2712b0e..a1919cd437 100644
--- a/lib/spack/spack/test/mock_packages_test.py
+++ b/lib/spack/spack/test/mock_packages_test.py
@@ -136,6 +136,31 @@ compilers:
f77: None
fc: None
modules: 'None'
+- compiler:
+ spec: gcc@4.7.2
+ operating_system: redhat6
+ paths:
+ cc: /path/to/gcc472
+ cxx: /path/to/g++472
+ f77: /path/to/gfortran472
+ fc: /path/to/gfortran472
+ flags:
+ cflags: -O0
+ cxxflags: -O0
+ fflags: -O0
+ modules: 'None'
+- compiler:
+ spec: clang@3.5
+ operating_system: redhat6
+ paths:
+ cc: /path/to/clang35
+ cxx: /path/to/clang++35
+ f77: None
+ fc: None
+ flags:
+ cflags: -O3
+ cxxflags: -O3
+ modules: 'None'
""".format(linux_os_name, linux_os_version)
mock_packages_config = """\
@@ -155,6 +180,27 @@ packages:
externalmodule@1.0%gcc@4.5.0: external-module
"""
+mock_config = """\
+config:
+ install_tree: $spack/opt/spack
+ build_stage:
+ - $tempdir
+ - /nfs/tmp2/$user
+ - $spack/var/spack/stage
+ source_cache: $spack/var/spack/cache
+ misc_cache: ~/.spack/cache
+ verify_ssl: true
+ checksum: true
+ dirty: True
+"""
+
+# these are written out to mock config files.
+mock_configs = {
+ 'config.yaml': mock_config,
+ 'compilers.yaml': mock_compiler_config,
+ 'packages.yaml': mock_packages_config,
+}
+
class MockPackagesTest(unittest.TestCase):
@@ -165,23 +211,22 @@ class MockPackagesTest(unittest.TestCase):
self.db = RepoPath(spack.mock_packages_path)
spack.repo.swap(self.db)
- spack.config.clear_config_caches()
- self.real_scopes = spack.config.config_scopes
-
# Mock up temporary configuration directories
self.temp_config = tempfile.mkdtemp()
self.mock_site_config = os.path.join(self.temp_config, 'site')
self.mock_user_config = os.path.join(self.temp_config, 'user')
mkdirp(self.mock_site_config)
mkdirp(self.mock_user_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])
+ for filename, data in mock_configs.items():
+ conf_yaml = os.path.join(self.mock_site_config, filename)
with open(conf_yaml, 'w') as f:
- f.write(confs[1])
+ f.write(data)
# TODO: Mocking this up is kind of brittle b/c ConfigScope
# TODO: constructor modifies config_scopes. Make it cleaner.
+ spack.config.clear_config_caches()
+ self.real_scopes = spack.config.config_scopes
+
spack.config.config_scopes = OrderedDict()
spack.config.ConfigScope('site', self.mock_site_config)
spack.config.ConfigScope('user', self.mock_user_config)
@@ -216,6 +261,7 @@ class MockPackagesTest(unittest.TestCase):
"""Restore the real packages path after any test."""
spack.repo.swap(self.db)
spack.config.config_scopes = self.real_scopes
+
shutil.rmtree(self.temp_config, ignore_errors=True)
spack.config.clear_config_caches()
diff --git a/lib/spack/spack/test/modules.py b/lib/spack/spack/test/modules.py
index 5e280d8e43..42f072debb 100644
--- a/lib/spack/spack/test/modules.py
+++ b/lib/spack/spack/test/modules.py
@@ -27,6 +27,8 @@ from contextlib import contextmanager
import StringIO
import spack.modules
+import spack.spec
+import llnl.util.filesystem
from spack.test.mock_packages_test import MockPackagesTest
FILE_REGISTRY = collections.defaultdict(StringIO.StringIO)
@@ -49,105 +51,10 @@ def mock_open(filename, mode):
handle.close()
-configuration_autoload_direct = {
- 'enable': ['tcl'],
- 'tcl': {
- 'all': {
- 'autoload': 'direct'
- }
- }
-}
-
-configuration_autoload_all = {
- 'enable': ['tcl'],
- 'tcl': {
- 'all': {
- 'autoload': 'all'
- }
- }
-}
-
-configuration_prerequisites_direct = {
- 'enable': ['tcl'],
- 'tcl': {
- 'all': {
- 'prerequisites': 'direct'
- }
- }
-}
-
-configuration_prerequisites_all = {
- 'enable': ['tcl'],
- 'tcl': {
- 'all': {
- 'prerequisites': 'all'
- }
- }
-}
-
-configuration_alter_environment = {
- 'enable': ['tcl'],
- 'tcl': {
- 'all': {
- 'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']},
- 'environment': {
- 'set': {'{name}_ROOT': '{prefix}'}
- }
- },
- 'platform=test target=x86_64': {
- 'environment': {
- 'set': {'FOO': 'foo'},
- 'unset': ['BAR']
- }
- },
- 'platform=test target=x86_32': {
- 'load': ['foo/bar']
- }
- }
-}
-
-configuration_blacklist = {
- 'enable': ['tcl'],
- 'tcl': {
- 'whitelist': ['zmpi'],
- 'blacklist': ['callpath', 'mpi'],
- 'all': {
- 'autoload': 'direct'
- }
- }
-}
-
-configuration_conflicts = {
- 'enable': ['tcl'],
- 'tcl': {
- 'naming_scheme': '{name}/{version}-{compiler.name}',
- 'all': {
- 'conflict': ['{name}', 'intel/14.0.1']
- }
- }
-}
-
-configuration_wrong_conflicts = {
- 'enable': ['tcl'],
- 'tcl': {
- 'naming_scheme': '{name}/{version}-{compiler.name}',
- 'all': {
- 'conflict': ['{name}/{compiler.name}']
- }
- }
-}
-
-configuration_suffix = {
- 'enable': ['tcl'],
- 'tcl': {
- 'mpileaks': {
- 'suffixes': {
- '+debug': 'foo',
- '~debug': 'bar'
- }
- }
- }
-}
+# Spec strings that will be used throughout the tests
+mpich_spec_string = 'mpich@3.0.4'
+mpileaks_spec_string = 'mpileaks'
+libdwarf_spec_string = 'libdwarf arch=x64-linux'
class HelperFunctionsTests(MockPackagesTest):
@@ -187,61 +94,198 @@ class HelperFunctionsTests(MockPackagesTest):
self.assertTrue('CPATH' in names)
-class TclTests(MockPackagesTest):
+class ModuleFileGeneratorTests(MockPackagesTest):
+ """
+ Base class to test module file generators. Relies on child having defined
+ a 'factory' attribute to create an instance of the generator to be tested.
+ """
def setUp(self):
- super(TclTests, self).setUp()
- self.configuration_obj = spack.modules.CONFIGURATION
+ super(ModuleFileGeneratorTests, self).setUp()
+ self.configuration_instance = spack.modules._module_config
+ self.module_types_instance = spack.modules.module_types
spack.modules.open = mock_open
+ spack.modules.mkdirp = lambda x: None
# Make sure that a non-mocked configuration will trigger an error
- spack.modules.CONFIGURATION = None
+ spack.modules._module_config = None
+ spack.modules.module_types = {self.factory.name: self.factory}
def tearDown(self):
del spack.modules.open
- spack.modules.CONFIGURATION = self.configuration_obj
- super(TclTests, self).tearDown()
+ spack.modules.module_types = self.module_types_instance
+ spack.modules._module_config = self.configuration_instance
+ spack.modules.mkdirp = llnl.util.filesystem.mkdirp
+ super(ModuleFileGeneratorTests, self).tearDown()
def get_modulefile_content(self, spec):
spec.concretize()
- generator = spack.modules.TclModule(spec)
+ generator = self.factory(spec)
generator.write()
content = FILE_REGISTRY[generator.file_name].split('\n')
+ generator.remove()
return content
+
+class TclTests(ModuleFileGeneratorTests):
+
+ factory = spack.modules.TclModule
+
+ configuration_autoload_direct = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'all': {
+ 'autoload': 'direct'
+ }
+ }
+ }
+
+ configuration_autoload_all = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'all': {
+ 'autoload': 'all'
+ }
+ }
+ }
+
+ configuration_prerequisites_direct = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'all': {
+ 'prerequisites': 'direct'
+ }
+ }
+ }
+
+ configuration_prerequisites_all = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'all': {
+ 'prerequisites': 'all'
+ }
+ }
+ }
+
+ configuration_alter_environment = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'all': {
+ 'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']},
+ 'environment': {
+ 'set': {'${PACKAGE}_ROOT': '${PREFIX}'}
+ }
+ },
+ 'platform=test target=x86_64': {
+ 'environment': {
+ 'set': {'FOO': 'foo'},
+ 'unset': ['BAR']
+ }
+ },
+ 'platform=test target=x86_32': {
+ 'load': ['foo/bar']
+ }
+ }
+ }
+
+ configuration_blacklist = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'whitelist': ['zmpi'],
+ 'blacklist': ['callpath', 'mpi'],
+ 'all': {
+ 'autoload': 'direct'
+ }
+ }
+ }
+
+ configuration_conflicts = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'naming_scheme': '${PACKAGE}/${VERSION}-${COMPILERNAME}',
+ 'all': {
+ 'conflict': ['${PACKAGE}', 'intel/14.0.1']
+ }
+ }
+ }
+
+ configuration_wrong_conflicts = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'naming_scheme': '${PACKAGE}/${VERSION}-${COMPILERNAME}',
+ 'all': {
+ 'conflict': ['${PACKAGE}/${COMPILERNAME}']
+ }
+ }
+ }
+
+ configuration_suffix = {
+ 'enable': ['tcl'],
+ 'tcl': {
+ 'mpileaks': {
+ 'suffixes': {
+ '+debug': 'foo',
+ '~debug': 'bar'
+ }
+ }
+ }
+ }
+
def test_simple_case(self):
- spack.modules.CONFIGURATION = configuration_autoload_direct
- spec = spack.spec.Spec('mpich@3.0.4')
+ spack.modules._module_config = self.configuration_autoload_direct
+ spec = spack.spec.Spec(mpich_spec_string)
content = self.get_modulefile_content(spec)
self.assertTrue('module-whatis "mpich @3.0.4"' in content)
self.assertRaises(TypeError, spack.modules.dependencies,
spec, 'non-existing-tag')
def test_autoload(self):
- spack.modules.CONFIGURATION = configuration_autoload_direct
- spec = spack.spec.Spec('mpileaks')
+ spack.modules._module_config = self.configuration_autoload_direct
+ spec = spack.spec.Spec(mpileaks_spec_string)
content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
- spack.modules.CONFIGURATION = configuration_autoload_all
- spec = spack.spec.Spec('mpileaks')
+ spack.modules._module_config = self.configuration_autoload_all
+ spec = spack.spec.Spec(mpileaks_spec_string)
content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 5)
self.assertEqual(len([x for x in content if 'module load ' in x]), 5)
+ # dtbuild1 has
+ # - 1 ('run',) dependency
+ # - 1 ('build','link') dependency
+ # - 1 ('build',) dependency
+ # Just make sure the 'build' dependency is not there
+ spack.modules._module_config = self.configuration_autoload_direct
+ spec = spack.spec.Spec('dtbuild1')
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
+ self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
+
+ # dtbuild1 has
+ # - 1 ('run',) dependency
+ # - 1 ('build','link') dependency
+ # - 1 ('build',) dependency
+ # Just make sure the 'build' dependency is not there
+ spack.modules._module_config = self.configuration_autoload_all
+ spec = spack.spec.Spec('dtbuild1')
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2)
+ self.assertEqual(len([x for x in content if 'module load ' in x]), 2)
+
def test_prerequisites(self):
- spack.modules.CONFIGURATION = configuration_prerequisites_direct
+ spack.modules._module_config = self.configuration_prerequisites_direct
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'prereq' in x]), 2)
- spack.modules.CONFIGURATION = configuration_prerequisites_all
+ spack.modules._module_config = self.configuration_prerequisites_all
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'prereq' in x]), 5)
def test_alter_environment(self):
- spack.modules.CONFIGURATION = configuration_alter_environment
+ spack.modules._module_config = self.configuration_alter_environment
spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
content = self.get_modulefile_content(spec)
self.assertEqual(
@@ -271,7 +315,7 @@ class TclTests(MockPackagesTest):
len([x for x in content if 'setenv LIBDWARF_ROOT' in x]), 1)
def test_blacklist(self):
- spack.modules.CONFIGURATION = configuration_blacklist
+ spack.modules._module_config = self.configuration_blacklist
spec = spack.spec.Spec('mpileaks ^zmpi')
content = self.get_modulefile_content(spec)
self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1)
@@ -285,7 +329,7 @@ class TclTests(MockPackagesTest):
self.assertEqual(len([x for x in content if 'module load ' in x]), 1)
def test_conflicts(self):
- spack.modules.CONFIGURATION = configuration_conflicts
+ spack.modules._module_config = self.configuration_conflicts
spec = spack.spec.Spec('mpileaks')
content = self.get_modulefile_content(spec)
self.assertEqual(
@@ -295,11 +339,11 @@ class TclTests(MockPackagesTest):
self.assertEqual(
len([x for x in content if x == 'conflict intel/14.0.1']), 1)
- spack.modules.CONFIGURATION = configuration_wrong_conflicts
+ spack.modules._module_config = self.configuration_wrong_conflicts
self.assertRaises(SystemExit, self.get_modulefile_content, spec)
def test_suffixes(self):
- spack.modules.CONFIGURATION = configuration_suffix
+ spack.modules._module_config = self.configuration_suffix
spec = spack.spec.Spec('mpileaks+debug arch=x86-linux')
spec.concretize()
generator = spack.modules.TclModule(spec)
@@ -311,28 +355,158 @@ class TclTests(MockPackagesTest):
self.assertTrue('bar' in generator.use_name)
-configuration_dotkit = {
- 'enable': ['dotkit'],
- 'dotkit': {
- 'all': {
- 'prerequisites': 'direct'
+class LmodTests(ModuleFileGeneratorTests):
+ factory = spack.modules.LmodModule
+
+ configuration_autoload_direct = {
+ 'enable': ['lmod'],
+ 'lmod': {
+ 'all': {
+ 'autoload': 'direct'
+ }
}
}
-}
+
+ configuration_autoload_all = {
+ 'enable': ['lmod'],
+ 'lmod': {
+ 'all': {
+ 'autoload': 'all'
+ }
+ }
+ }
+
+ configuration_no_hash = {
+ 'enable': ['lmod'],
+ 'lmod': {
+ 'hash_length': 0
+ }
+ }
+
+ configuration_alter_environment = {
+ 'enable': ['lmod'],
+ 'lmod': {
+ 'all': {
+ 'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']}
+ },
+ 'platform=test target=x86_64': {
+ 'environment': {
+ 'set': {'FOO': 'foo'},
+ 'unset': ['BAR']
+ }
+ },
+ 'platform=test target=x86_32': {
+ 'load': ['foo/bar']
+ }
+ }
+ }
+
+ configuration_blacklist = {
+ 'enable': ['lmod'],
+ 'lmod': {
+ 'blacklist': ['callpath'],
+ 'all': {
+ 'autoload': 'direct'
+ }
+ }
+ }
+
+ def test_simple_case(self):
+ spack.modules._module_config = self.configuration_autoload_direct
+ spec = spack.spec.Spec(mpich_spec_string)
+ content = self.get_modulefile_content(spec)
+ self.assertTrue('-- -*- lua -*-' in content)
+ self.assertTrue('whatis([[Name : mpich]])' in content)
+ self.assertTrue('whatis([[Version : 3.0.4]])' in content)
+
+ def test_autoload(self):
+ spack.modules._module_config = self.configuration_autoload_direct
+ spec = spack.spec.Spec(mpileaks_spec_string)
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(
+ len([x for x in content if 'if not isloaded(' in x]), 2)
+ self.assertEqual(len([x for x in content if 'load(' in x]), 2)
+
+ spack.modules._module_config = self.configuration_autoload_all
+ spec = spack.spec.Spec(mpileaks_spec_string)
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(
+ len([x for x in content if 'if not isloaded(' in x]), 5)
+ self.assertEqual(len([x for x in content if 'load(' in x]), 5)
+
+ def test_alter_environment(self):
+ spack.modules._module_config = self.configuration_alter_environment
+ spec = spack.spec.Spec('mpileaks platform=test target=x86_64')
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(
+ len([x
+ for x in content
+ if x.startswith('prepend_path("CMAKE_PREFIX_PATH"')]), 0)
+ self.assertEqual(
+ len([x for x in content if 'setenv("FOO", "foo")' in x]), 1)
+ self.assertEqual(
+ len([x for x in content if 'unsetenv("BAR")' in x]), 1)
+
+ spec = spack.spec.Spec('libdwarf %clang platform=test target=x86_32')
+ content = self.get_modulefile_content(spec)
+ print('\n'.join(content))
+ self.assertEqual(
+ len([x
+ for x in content
+ if x.startswith('prepend-path("CMAKE_PREFIX_PATH"')]), 0)
+ self.assertEqual(
+ len([x for x in content if 'setenv("FOO", "foo")' in x]), 0)
+ self.assertEqual(
+ len([x for x in content if 'unsetenv("BAR")' in x]), 0)
+
+ def test_blacklist(self):
+ spack.modules._module_config = self.configuration_blacklist
+ spec = spack.spec.Spec(mpileaks_spec_string)
+ content = self.get_modulefile_content(spec)
+ self.assertEqual(
+ len([x for x in content if 'if not isloaded(' in x]), 1)
+ self.assertEqual(len([x for x in content if 'load(' in x]), 1)
+
+ def test_no_hash(self):
+ # Make sure that virtual providers (in the hierarchy) always
+ # include a hash. Make sure that the module file for the spec
+ # does not include a hash if hash_length is 0.
+ spack.modules._module_config = self.configuration_no_hash
+ spec = spack.spec.Spec(mpileaks_spec_string)
+ spec.concretize()
+ module = spack.modules.LmodModule(spec)
+ path = module.file_name
+ mpiSpec = spec['mpi']
+ mpiElement = "{0}/{1}-{2}/".format(
+ mpiSpec.name, mpiSpec.version, mpiSpec.dag_hash(length=7))
+ self.assertTrue(mpiElement in path)
+ mpileaksSpec = spec
+ mpileaksElement = "{0}/{1}.lua".format(
+ mpileaksSpec.name, mpileaksSpec.version)
+ self.assertTrue(path.endswith(mpileaksElement))
class DotkitTests(MockPackagesTest):
+ configuration_dotkit = {
+ 'enable': ['dotkit'],
+ 'dotkit': {
+ 'all': {
+ 'prerequisites': 'direct'
+ }
+ }
+ }
+
def setUp(self):
super(DotkitTests, self).setUp()
- self.configuration_obj = spack.modules.CONFIGURATION
+ self.configuration_obj = spack.modules._module_config
spack.modules.open = mock_open
# Make sure that a non-mocked configuration will trigger an error
- spack.modules.CONFIGURATION = None
+ spack.modules._module_config = None
def tearDown(self):
del spack.modules.open
- spack.modules.CONFIGURATION = self.configuration_obj
+ spack.modules._module_config = self.configuration_obj
super(DotkitTests, self).tearDown()
def get_modulefile_content(self, spec):
@@ -343,7 +517,7 @@ class DotkitTests(MockPackagesTest):
return content
def test_dotkit(self):
- spack.modules.CONFIGURATION = configuration_dotkit
+ spack.modules._module_config = self.configuration_dotkit
spec = spack.spec.Spec('mpileaks arch=x86-linux')
content = self.get_modulefile_content(spec)
self.assertTrue('#c spack' in content)
diff --git a/lib/spack/spack/test/operating_system.py b/lib/spack/spack/test/operating_system.py
deleted file mode 100644
index 8723f7244d..0000000000
--- a/lib/spack/spack/test/operating_system.py
+++ /dev/null
@@ -1,75 +0,0 @@
-##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Created 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 Lesser General Public License (as
-# published by the Free Software Foundation) version 2.1, 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 Lesser 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 checks if the operating_system class is created correctly and that
-the functions are using the correct operating_system. Also checks whether
-the operating_system correctly uses the compiler_strategy
-"""
-import unittest
-from spack.platforms.cray_xc import CrayXc
-from spack.platforms.linux import Linux
-from spack.platforms.darwin import Darwin
-from spack.operating_system.linux_distro import LinuxDistro
-from spack.operating_system.cnl import ComputeNodeLinux
-
-
-class TestOperatingSystem(unittest.TestCase):
-
- def setUp(self):
- cray_xc = CrayXc()
- linux = Linux()
- darwin = Darwin()
- self.cray_operating_sys = cray_xc.operating_system('front_os')
- self.cray_default_os = cray_xc.operating_system('default_os')
- self.cray_back_os = cray_xc.operating_system('back_os')
- self.darwin_operating_sys = darwin.operating_system('default_os')
- self.linux_operating_sys = linux.operating_system('default_os')
-
- def test_cray_front_end_operating_system(self):
- self.assertIsInstance(self.cray_operating_sys, LinuxDistro)
-
- def test_cray_front_end_compiler_strategy(self):
- self.assertEquals(self.cray_operating_sys.compiler_strategy, "PATH")
-
- def test_cray_back_end_operating_system(self):
- self.assertIsInstance(self.cray_back_os, ComputeNodeLinux)
-
- def test_cray_back_end_compiler_strategy(self):
- self.assertEquals(self.cray_back_os.compiler_strategy, "MODULES")
-
- def test_linux_operating_system(self):
- self.assertIsInstance(self.linux_operating_sys, LinuxDistro)
-
- def test_linux_compiler_strategy(self):
- self.assertEquals(self.linux_operating_sys.compiler_strategy, "PATH")
-
- def test_cray_front_end_compiler_list(self):
- """ Operating systems will now be in charge of finding compilers.
- So, depending on which operating system you want to build for
- or which operating system you are on, then you could detect
- compilers in a certain way. Cray linux environment on the front
- end is just a regular linux distro whereas the Cray linux compute
- node is a stripped down version which modules are important
- """
- self.assertEquals(True, False)
diff --git a/lib/spack/spack/test/provider_index.py b/lib/spack/spack/test/provider_index.py
index 9847dd05a6..d785038899 100644
--- a/lib/spack/spack/test/provider_index.py
+++ b/lib/spack/spack/test/provider_index.py
@@ -24,7 +24,7 @@
##############################################################################
"""Tests for provider index cache files.
-Tests assume that mock packages provide this:
+Tests assume that mock packages provide this::
{'blas': {
blas: set([netlib-blas, openblas, openblas-with-lapack])},
diff --git a/lib/spack/spack/test/yaml.py b/lib/spack/spack/test/spack_yaml.py
index dedbd15d10..fbbb7b8e60 100644
--- a/lib/spack/spack/test/yaml.py
+++ b/lib/spack/spack/test/spack_yaml.py
@@ -56,7 +56,7 @@ test_data = {
])}
-class YamlTest(unittest.TestCase):
+class SpackYamlTest(unittest.TestCase):
def setUp(self):
self.data = syaml.load(test_file)
@@ -90,3 +90,19 @@ class YamlTest(unittest.TestCase):
check(self.data['config_file']['some_list'][2], 8, 8)
check(self.data['config_file']['another_list'], 10, 10)
check(self.data['config_file']['some_key'], 11, 11)
+
+ def test_yaml_aliases(self):
+ aliased_list_1 = ['foo']
+ aliased_list_2 = []
+ dict_with_aliases = {
+ 'a': aliased_list_1,
+ 'b': aliased_list_1,
+ 'c': aliased_list_1,
+ 'd': aliased_list_2,
+ 'e': aliased_list_2,
+ 'f': aliased_list_2,
+ }
+ string = syaml.dump(dict_with_aliases)
+
+ # ensure no YAML aliases appear in syaml dumps.
+ self.assertFalse('*id' in string)
diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py
index 8f61c7ac76..0bc63bcf0f 100644
--- a/lib/spack/spack/test/spec_dag.py
+++ b/lib/spack/spack/test/spec_dag.py
@@ -241,15 +241,15 @@ class SpecDagTest(MockPackagesTest):
def test_invalid_dep(self):
spec = Spec('libelf ^mpich')
- self.assertRaises(spack.spec.InvalidDependencyException,
+ self.assertRaises(spack.spec.InvalidDependencyError,
spec.normalize)
spec = Spec('libelf ^libdwarf')
- self.assertRaises(spack.spec.InvalidDependencyException,
+ self.assertRaises(spack.spec.InvalidDependencyError,
spec.normalize)
spec = Spec('mpich ^dyninst ^libelf')
- self.assertRaises(spack.spec.InvalidDependencyException,
+ self.assertRaises(spack.spec.InvalidDependencyError,
spec.normalize)
def test_equal(self):
@@ -495,3 +495,65 @@ class SpecDagTest(MockPackagesTest):
traversal = dag.traverse(deptype='run')
self.assertEqual([x.name for x in traversal], names)
+
+ def test_using_ordered_dict(self):
+ """ Checks that dicts are ordered
+
+ Necessary to make sure that dag_hash is stable across python
+ versions and processes.
+ """
+ def descend_and_check(iterable, level=0):
+ from spack.util.spack_yaml import syaml_dict
+ from collections import Iterable, Mapping
+ if isinstance(iterable, Mapping):
+ self.assertTrue(isinstance(iterable, syaml_dict))
+ return descend_and_check(iterable.values(), level=level + 1)
+ max_level = level
+ for value in iterable:
+ if isinstance(value, Iterable) and not isinstance(value, str):
+ nlevel = descend_and_check(value, level=level + 1)
+ if nlevel > max_level:
+ max_level = nlevel
+ return max_level
+
+ specs = ['mpileaks ^zmpi', 'dttop', 'dtuse']
+ for spec in specs:
+ dag = Spec(spec)
+ dag.normalize()
+ level = descend_and_check(dag.to_node_dict())
+ # level just makes sure we are doing something here
+ self.assertTrue(level >= 5)
+
+ def test_hash_bits(self):
+ """Ensure getting first n bits of a base32-encoded DAG hash works."""
+
+ # RFC 4648 base32 decode table
+ b32 = dict((j, i) for i, j in enumerate('abcdefghijklmnopqrstuvwxyz'))
+ b32.update(dict((j, i) for i, j in enumerate('234567', 26)))
+
+ # some package hashes
+ tests = [
+ '35orsd4cenv743hg4i5vxha2lzayycby',
+ '6kfqtj7dap3773rxog6kkmoweix5gpwo',
+ 'e6h6ff3uvmjbq3azik2ckr6ckwm3depv',
+ 'snz2juf4ij7sv77cq3vs467q6acftmur',
+ '4eg47oedi5bbkhpoxw26v3oe6vamkfd7',
+ 'vrwabwj6umeb5vjw6flx2rnft3j457rw']
+
+ for test_hash in tests:
+ # string containing raw bits of hash ('1' and '0')
+ expected = ''.join([format(b32[c], '#07b').replace('0b', '')
+ for c in test_hash])
+
+ for bits in (1, 2, 3, 4, 7, 8, 9, 16, 64, 117, 128, 160):
+ actual_int = spack.spec.base32_prefix_bits(test_hash, bits)
+ fmt = "#0%sb" % (bits + 2)
+ actual = format(actual_int, fmt).replace('0b', '')
+
+ self.assertEqual(expected[:bits], actual)
+
+ self.assertRaises(
+ ValueError, spack.spec.base32_prefix_bits, test_hash, 161)
+
+ self.assertRaises(
+ ValueError, spack.spec.base32_prefix_bits, test_hash, 256)
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
index 3079288c77..d4eb9e057f 100644
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -24,34 +24,34 @@
##############################################################################
import unittest
-import spack.spec
+import spack.spec as sp
from spack.parse import Token
from spack.spec import *
# Sample output for a complex lexing.
-complex_lex = [Token(ID, 'mvapich_foo'),
- Token(DEP),
- Token(ID, '_openmpi'),
- Token(AT),
- Token(ID, '1.2'),
- Token(COLON),
- Token(ID, '1.4'),
- Token(COMMA),
- Token(ID, '1.6'),
- Token(PCT),
- Token(ID, 'intel'),
- Token(AT),
- Token(ID, '12.1'),
- Token(COLON),
- Token(ID, '12.6'),
- Token(ON),
- Token(ID, 'debug'),
- Token(OFF),
- Token(ID, 'qt_4'),
- Token(DEP),
- Token(ID, 'stackwalker'),
- Token(AT),
- Token(ID, '8.1_1e')]
+complex_lex = [Token(sp.ID, 'mvapich_foo'),
+ Token(sp.DEP),
+ Token(sp.ID, '_openmpi'),
+ Token(sp.AT),
+ Token(sp.ID, '1.2'),
+ Token(sp.COLON),
+ Token(sp.ID, '1.4'),
+ Token(sp.COMMA),
+ Token(sp.ID, '1.6'),
+ Token(sp.PCT),
+ Token(sp.ID, 'intel'),
+ Token(sp.AT),
+ Token(sp.ID, '12.1'),
+ Token(sp.COLON),
+ Token(sp.ID, '12.6'),
+ Token(sp.ON),
+ Token(sp.ID, 'debug'),
+ Token(sp.OFF),
+ Token(sp.ID, 'qt_4'),
+ Token(sp.DEP),
+ Token(sp.ID, 'stackwalker'),
+ Token(sp.AT),
+ Token(sp.ID, '8.1_1e')]
class SpecSyntaxTest(unittest.TestCase):
@@ -74,16 +74,16 @@ class SpecSyntaxTest(unittest.TestCase):
"""
if spec is None:
spec = expected
- output = spack.spec.parse(spec)
+ output = sp.parse(spec)
parsed = (" ".join(str(spec) for spec in output))
self.assertEqual(expected, parsed)
def check_lex(self, tokens, spec):
"""Check that the provided spec parses to the provided token list."""
- lex_output = SpecLexer().lex(spec)
+ lex_output = sp.SpecLexer().lex(spec)
for tok, spec_tok in zip(tokens, lex_output):
- if tok.type == ID:
+ if tok.type == sp.ID:
self.assertEqual(tok, spec_tok)
else:
# Only check the type for non-identifiers.
diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py
index ec661bfe50..64cfa222db 100644
--- a/lib/spack/spack/test/stage.py
+++ b/lib/spack/spack/test/stage.py
@@ -27,64 +27,93 @@ Test that the Stage class works correctly.
"""
import os
import shutil
-import unittest
+import tempfile
from contextlib import *
import spack
+import spack.stage
from llnl.util.filesystem import *
from spack.stage import Stage
from spack.util.executable import which
+from spack.test.mock_packages_test import *
-test_files_dir = os.path.realpath(join_path(spack.stage_path, '.test'))
-test_tmp_path = os.path.realpath(join_path(test_files_dir, 'tmp'))
-
-archive_dir = 'test-files'
-archive_name = archive_dir + '.tar.gz'
-archive_dir_path = join_path(test_files_dir, archive_dir)
-archive_url = 'file://' + join_path(test_files_dir, archive_name)
-readme_name = 'README.txt'
-test_readme = join_path(archive_dir_path, readme_name)
-readme_text = "hello world!\n"
-
-stage_name = 'spack-test-stage'
+_test_tmp_path = None
@contextmanager
def use_tmp(use_tmp):
- """Allow some test code to be executed with spack.use_tmp_stage
- set to a certain value. Context manager makes sure it's reset
- on failure.
+ """Allow some test code to be executed such that spack will either use or
+ not use temporary space for stages.
"""
- old_tmp = spack.use_tmp_stage
- spack.use_tmp_stage = use_tmp
+ # mock up config
+ assert(_test_tmp_path is not None)
+
+ if use_tmp:
+ path = _test_tmp_path # use temporary stage
+ else:
+ path = spack.stage_path # Use Spack's stage dir (no links)
+
+ spack.config.update_config(
+ 'config', {'build_stage': [path]}, scope='user')
+
yield
- spack.use_tmp_stage = old_tmp
-class StageTest(unittest.TestCase):
+class StageTest(MockPackagesTest):
def setUp(self):
"""This sets up a mock archive to fetch, and a mock temp space for use
by the Stage class. It doesn't actually create the Stage -- that
is done by individual tests.
"""
- if os.path.exists(test_files_dir):
- shutil.rmtree(test_files_dir)
+ super(StageTest, self).setUp()
+
+ global _test_tmp_path
+
+ #
+ # Mock up a stage area that looks like this:
+ #
+ # TMPDIR/ test_files_dir
+ # tmp/ test_tmp_path (where stage should be)
+ # test-files/ archive_dir_path
+ # README.txt test_readme (contains "hello world!\n")
+ # test-files.tar.gz archive_url = file:///path/to/this
+ #
+ self.test_files_dir = tempfile.mkdtemp()
+ self.test_tmp_path = os.path.realpath(
+ os.path.join(self.test_files_dir, 'tmp'))
+ _test_tmp_path = self.test_tmp_path
+
+ # set _test_tmp_path as the default test directory to use for stages.
+ spack.config.update_config(
+ 'config', {'build_stage': [_test_tmp_path]}, scope='user')
+
+ self.archive_dir = 'test-files'
+ self.archive_name = self.archive_dir + '.tar.gz'
+ archive_dir_path = os.path.join(self.test_files_dir,
+ self.archive_dir)
+ self.archive_url = 'file://' + os.path.join(self.test_files_dir,
+ self.archive_name)
+ test_readme = join_path(archive_dir_path, 'README.txt')
+ self.readme_text = "hello world!\n"
+
+ self.stage_name = 'spack-test-stage'
- mkdirp(test_files_dir)
mkdirp(archive_dir_path)
- mkdirp(test_tmp_path)
+ mkdirp(self.test_tmp_path)
with open(test_readme, 'w') as readme:
- readme.write(readme_text)
+ readme.write(self.readme_text)
- with working_dir(test_files_dir):
- tar = which('tar')
- tar('czf', archive_name, archive_dir)
+ with working_dir(self.test_files_dir):
+ tar = which('tar', required=True)
+ tar('czf', self.archive_name, self.archive_dir)
# Make spack use the test environment for tmp stuff.
- self.old_tmp_dirs = spack.tmp_dirs
- spack.tmp_dirs = [test_tmp_path]
+ self._old_tmp_root = spack.stage._tmp_root
+ self._old_use_tmp_stage = spack.stage._use_tmp_stage
+ spack.stage._tmp_root = None
+ spack.stage._use_tmp_stage = True
# record this since this test changes to directories that will
# be removed.
@@ -92,13 +121,16 @@ class StageTest(unittest.TestCase):
def tearDown(self):
"""Blows away the test environment directory."""
- shutil.rmtree(test_files_dir)
+ super(StageTest, self).tearDown()
+
+ shutil.rmtree(self.test_files_dir, ignore_errors=True)
# chdir back to original working dir
os.chdir(self.working_dir)
# restore spack's original tmp environment
- spack.tmp_dirs = self.old_tmp_dirs
+ spack.stage._tmp_root = self._old_tmp_root
+ spack.stage._use_tmp_stage = self._old_use_tmp_stage
def get_stage_path(self, stage, stage_name):
"""Figure out where a stage should be living. This depends on
@@ -120,7 +152,7 @@ class StageTest(unittest.TestCase):
# Ensure stage was created in the spack stage directory
self.assertTrue(os.path.isdir(stage_path))
- if spack.use_tmp_stage:
+ if spack.stage.get_tmp_root():
# Check that the stage dir is really a symlink.
self.assertTrue(os.path.islink(stage_path))
@@ -130,8 +162,8 @@ class StageTest(unittest.TestCase):
self.assertFalse(os.path.islink(target))
# Make sure the directory is in the place we asked it to
- # be (see setUp and tearDown)
- self.assertTrue(target.startswith(test_tmp_path))
+ # be (see setUp, tearDown, and use_tmp)
+ self.assertTrue(target.startswith(self.test_tmp_path))
else:
# Make sure the stage path is NOT a link for a non-tmp stage
@@ -139,24 +171,24 @@ class StageTest(unittest.TestCase):
def check_fetch(self, stage, stage_name):
stage_path = self.get_stage_path(stage, stage_name)
- self.assertTrue(archive_name in os.listdir(stage_path))
- self.assertEqual(join_path(stage_path, archive_name),
+ self.assertTrue(self.archive_name in os.listdir(stage_path))
+ self.assertEqual(join_path(stage_path, self.archive_name),
stage.fetcher.archive_file)
def check_expand_archive(self, stage, stage_name):
stage_path = self.get_stage_path(stage, stage_name)
- self.assertTrue(archive_name in os.listdir(stage_path))
- self.assertTrue(archive_dir in os.listdir(stage_path))
+ self.assertTrue(self.archive_name in os.listdir(stage_path))
+ self.assertTrue(self.archive_dir in os.listdir(stage_path))
self.assertEqual(
- join_path(stage_path, archive_dir),
+ join_path(stage_path, self.archive_dir),
stage.source_path)
- readme = join_path(stage_path, archive_dir, readme_name)
+ readme = join_path(stage_path, self.archive_dir, 'README.txt')
self.assertTrue(os.path.isfile(readme))
with open(readme) as file:
- self.assertEqual(readme_text, file.read())
+ self.assertEqual(self.readme_text, file.read())
def check_chdir(self, stage, stage_name):
stage_path = self.get_stage_path(stage, stage_name)
@@ -165,7 +197,7 @@ class StageTest(unittest.TestCase):
def check_chdir_to_source(self, stage, stage_name):
stage_path = self.get_stage_path(stage, stage_name)
self.assertEqual(
- join_path(os.path.realpath(stage_path), archive_dir),
+ join_path(os.path.realpath(stage_path), self.archive_dir),
os.getcwd())
def check_destroy(self, stage, stage_name):
@@ -176,76 +208,76 @@ class StageTest(unittest.TestCase):
self.assertFalse(os.path.exists(stage_path))
# tmp stage needs to remove tmp dir too.
- if spack.use_tmp_stage:
+ if spack.stage._use_tmp_stage:
target = os.path.realpath(stage_path)
self.assertFalse(os.path.exists(target))
def test_setup_and_destroy_name_with_tmp(self):
with use_tmp(True):
- with Stage(archive_url, name=stage_name) as stage:
- self.check_setup(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ with Stage(self.archive_url, name=self.stage_name) as stage:
+ self.check_setup(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_setup_and_destroy_name_without_tmp(self):
with use_tmp(False):
- with Stage(archive_url, name=stage_name) as stage:
- self.check_setup(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ with Stage(self.archive_url, name=self.stage_name) as stage:
+ self.check_setup(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_setup_and_destroy_no_name_with_tmp(self):
with use_tmp(True):
- with Stage(archive_url) as stage:
+ with Stage(self.archive_url) as stage:
self.check_setup(stage, None)
self.check_destroy(stage, None)
def test_setup_and_destroy_no_name_without_tmp(self):
with use_tmp(False):
- with Stage(archive_url) as stage:
+ with Stage(self.archive_url) as stage:
self.check_setup(stage, None)
self.check_destroy(stage, None)
def test_chdir(self):
- with Stage(archive_url, name=stage_name) as stage:
+ with Stage(self.archive_url, name=self.stage_name) as stage:
stage.chdir()
- self.check_setup(stage, stage_name)
- self.check_chdir(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ self.check_setup(stage, self.stage_name)
+ self.check_chdir(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_fetch(self):
- with Stage(archive_url, name=stage_name) as stage:
+ with Stage(self.archive_url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, stage_name)
- self.check_chdir(stage, stage_name)
- self.check_fetch(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ self.check_setup(stage, self.stage_name)
+ self.check_chdir(stage, self.stage_name)
+ self.check_fetch(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_expand_archive(self):
- with Stage(archive_url, name=stage_name) as stage:
+ with Stage(self.archive_url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, stage_name)
- self.check_fetch(stage, stage_name)
+ self.check_setup(stage, self.stage_name)
+ self.check_fetch(stage, self.stage_name)
stage.expand_archive()
- self.check_expand_archive(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ self.check_expand_archive(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_expand_archive_with_chdir(self):
- with Stage(archive_url, name=stage_name) as stage:
+ with Stage(self.archive_url, name=self.stage_name) as stage:
stage.fetch()
- self.check_setup(stage, stage_name)
- self.check_fetch(stage, stage_name)
+ self.check_setup(stage, self.stage_name)
+ self.check_fetch(stage, self.stage_name)
stage.expand_archive()
stage.chdir_to_source()
- self.check_expand_archive(stage, stage_name)
- self.check_chdir_to_source(stage, stage_name)
- self.check_destroy(stage, stage_name)
+ self.check_expand_archive(stage, self.stage_name)
+ self.check_chdir_to_source(stage, self.stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_restage(self):
- with Stage(archive_url, name=stage_name) as stage:
+ with Stage(self.archive_url, name=self.stage_name) as stage:
stage.fetch()
stage.expand_archive()
stage.chdir_to_source()
- self.check_expand_archive(stage, stage_name)
- self.check_chdir_to_source(stage, stage_name)
+ self.check_expand_archive(stage, self.stage_name)
+ self.check_chdir_to_source(stage, self.stage_name)
# Try to make a file in the old archive dir
with open('foobar', 'w') as file:
@@ -255,40 +287,44 @@ class StageTest(unittest.TestCase):
# Make sure the file is not there after restage.
stage.restage()
- self.check_chdir(stage, stage_name)
- self.check_fetch(stage, stage_name)
+ self.check_chdir(stage, self.stage_name)
+ self.check_fetch(stage, self.stage_name)
stage.chdir_to_source()
- self.check_chdir_to_source(stage, stage_name)
+ self.check_chdir_to_source(stage, self.stage_name)
self.assertFalse('foobar' in os.listdir(stage.source_path))
- self.check_destroy(stage, stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_no_keep_without_exceptions(self):
- with Stage(archive_url, name=stage_name, keep=False) as stage:
+ with Stage(self.archive_url,
+ name=self.stage_name, keep=False) as stage:
pass
- self.check_destroy(stage, stage_name)
+ self.check_destroy(stage, self.stage_name)
def test_keep_without_exceptions(self):
- with Stage(archive_url, name=stage_name, keep=True) as stage:
+ with Stage(self.archive_url,
+ name=self.stage_name, keep=True) as stage:
pass
- path = self.get_stage_path(stage, stage_name)
+ path = self.get_stage_path(stage, self.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:
+ with Stage(self.archive_url,
+ name=self.stage_name, keep=False) as stage:
raise Exception()
- path = self.get_stage_path(stage, stage_name)
+ path = self.get_stage_path(stage, self.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:
+ with Stage(self.archive_url,
+ name=self.stage_name, keep=True) as stage:
raise Exception()
- path = self.get_stage_path(stage, stage_name)
+ path = self.get_stage_path(stage, self.stage_name)
self.assertTrue(os.path.isdir(path))
except:
pass # ignore here.
diff --git a/lib/spack/spack/test/svn_fetch.py b/lib/spack/spack/test/svn_fetch.py
index 9ef7593ed1..01ffc488a7 100644
--- a/lib/spack/spack/test/svn_fetch.py
+++ b/lib/spack/spack/test/svn_fetch.py
@@ -63,12 +63,13 @@ class SvnFetchTest(MockPackagesTest):
def try_fetch(self, rev, test_file, args):
"""Tries to:
- 1. Fetch the repo using a fetch strategy constructed with
- supplied args.
- 2. Check if the test_file is in the checked out repository.
- 3. Assert that the repository is at the revision supplied.
- 4. Add and remove some files, then reset the repo, and
- ensure it's all there again.
+
+ 1. Fetch the repo using a fetch strategy constructed with
+ supplied args.
+ 2. Check if the test_file is in the checked out repository.
+ 3. Assert that the repository is at the revision supplied.
+ 4. Add and remove some files, then reset the repo, and
+ ensure it's all there again.
"""
self.pkg.versions[ver('svn')] = args
diff --git a/lib/spack/spack/test/tally_plugin.py b/lib/spack/spack/test/tally_plugin.py
index 808694d186..d848f2cb9f 100644
--- a/lib/spack/spack/test/tally_plugin.py
+++ b/lib/spack/spack/test/tally_plugin.py
@@ -35,6 +35,8 @@ class Tally(Plugin):
self.successCount = 0
self.failCount = 0
self.errorCount = 0
+ self.error_list = []
+ self.fail_list = []
@property
def numberOfTestsRun(self):
@@ -52,9 +54,11 @@ class Tally(Plugin):
def addError(self, test, err):
self.errorCount += 1
+ self.error_list.append(test)
def addFailure(self, test, err):
self.failCount += 1
+ self.fail_list.append(test)
def finalize(self, result):
pass
diff --git a/lib/spack/spack/test/versions.py b/lib/spack/spack/test/versions.py
index 41d72e7c34..c1d427783c 100644
--- a/lib/spack/spack/test/versions.py
+++ b/lib/spack/spack/test/versions.py
@@ -392,6 +392,7 @@ class VersionsTest(unittest.TestCase):
self.assertEqual(v.dotted, '1.2.3')
self.assertEqual(v.dashed, '1-2-3')
self.assertEqual(v.underscored, '1_2_3')
+ self.assertEqual(v.joined, '123')
def test_repr_and_str(self):
@@ -428,3 +429,6 @@ class VersionsTest(unittest.TestCase):
self.assertEqual(str(b), '1_2-3')
# Raise TypeError on tuples
self.assertRaises(TypeError, b.__getitem__, 1, 2)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/lib/spack/spack/url.py b/lib/spack/spack/url.py
index 02c9c04380..8842495613 100644
--- a/lib/spack/spack/url.py
+++ b/lib/spack/spack/url.py
@@ -142,7 +142,7 @@ def split_url_extension(path):
return prefix, ext, suffix
-def downloaded_file_extension(path):
+def determine_url_file_extension(path):
"""This returns the type of archive a URL refers to. This is
sometimes confusing because of URLs like:
@@ -160,8 +160,6 @@ def downloaded_file_extension(path):
return 'tar.gz'
prefix, ext, suffix = split_url_extension(path)
- if not ext:
- raise UrlParseError("Cannot deduce archive type in %s" % path, path)
return ext
@@ -325,7 +323,7 @@ def parse_name_and_version(path):
def insensitize(string):
"""Change upper and lowercase letters to be case insensitive in
- the provided string. e.g., 'a' because '[Aa]', 'B' becomes
+ the provided string. e.g., 'a' becomes '[Aa]', 'B' becomes
'[bB]', etc. Use for building regexes."""
def to_ins(match):
char = match.group(1)
diff --git a/lib/spack/spack/util/compression.py b/lib/spack/spack/util/compression.py
index 64554ab2f7..806465dc4e 100644
--- a/lib/spack/spack/util/compression.py
+++ b/lib/spack/spack/util/compression.py
@@ -40,11 +40,15 @@ def allowed_archive(path):
return any(path.endswith(t) for t in ALLOWED_ARCHIVE_TYPES)
-def decompressor_for(path):
+def decompressor_for(path, extension=None):
"""Get the appropriate decompressor for a path."""
- if path.endswith(".zip"):
+ if ((extension and re.match(r'\.?zip$', extension)) or
+ path.endswith('.zip')):
unzip = which('unzip', required=True)
return unzip
+ if extension and re.match(r'gz', extension):
+ gunzip = which('gunzip', required=True)
+ return gunzip
tar = which('tar', required=True)
tar.add_default_arg('-xf')
return tar
diff --git a/lib/spack/spack/util/crypto.py b/lib/spack/spack/util/crypto.py
index 22777fdb68..d074716022 100644
--- a/lib/spack/spack/util/crypto.py
+++ b/lib/spack/spack/util/crypto.py
@@ -100,3 +100,24 @@ class Checker(object):
self.sum = checksum(
self.hash_fun, filename, block_size=self.block_size)
return self.sum == self.hexdigest
+
+
+def prefix_bits(byte_array, bits):
+ """Return the first <bits> bits of a byte array as an integer."""
+ result = 0
+ n = 0
+ for i, b in enumerate(byte_array):
+ n += 8
+ result = (result << 8) | ord(b)
+ if n >= bits:
+ break
+
+ result >>= (n - bits)
+ return result
+
+
+def bit_length(num):
+ """Number of bits required to represent an integer in binary."""
+ s = bin(num)
+ s = s.lstrip('-0b')
+ return len(s)
diff --git a/lib/spack/spack/util/environment.py b/lib/spack/spack/util/environment.py
index f23110d2d9..15eb52d8e2 100644
--- a/lib/spack/spack/util/environment.py
+++ b/lib/spack/spack/util/environment.py
@@ -24,6 +24,24 @@
##############################################################################
import os
+system_paths = ['/', '/usr/', '/usr/local']
+suffixes = ['lib', 'lib64', 'include']
+system_dirs = [os.path.join(p, s) for s in suffixes for p in system_paths]
+system_bins = [os.path.join(p, 'bin') for p in system_paths]
+
+
+def filter_system_paths(paths):
+ return [p for p in paths if p not in system_dirs]
+
+
+def filter_system_bin_paths(paths):
+ # Turn the iterable into a list. Assume it's a list from here on.
+ _paths = list(paths)
+ bins = [p for p in _paths if p in system_bins]
+ nobins = [p for p in _paths if p not in system_bins]
+ # put bins infront as PATH is set by: prepend_path('PATH', item)
+ return bins + nobins
+
def get_path(name):
path = os.environ.get(name, "").strip()
diff --git a/lib/spack/spack/util/executable.py b/lib/spack/spack/util/executable.py
index 5c27b92df5..63bbbb7c92 100644
--- a/lib/spack/spack/util/executable.py
+++ b/lib/spack/spack/util/executable.py
@@ -26,7 +26,6 @@
import os
import re
import subprocess
-import inspect
import llnl.util.tty as tty
import spack
@@ -40,6 +39,7 @@ class Executable(object):
def __init__(self, name):
self.exe = name.split(' ')
+ self.default_env = {}
self.returncode = None
if not self.exe:
@@ -48,6 +48,9 @@ class Executable(object):
def add_default_arg(self, arg):
self.exe.append(arg)
+ def add_default_env(self, key, value):
+ self.default_env[key] = value
+
@property
def command(self):
return ' '.join(self.exe)
@@ -103,7 +106,13 @@ class Executable(object):
fail_on_error = kwargs.pop("fail_on_error", True)
ignore_errors = kwargs.pop("ignore_errors", ())
+ # environment
env = kwargs.get('env', None)
+ if env is None:
+ env = os.environ.copy()
+ env.update(self.default_env)
+ else:
+ env = self.default_env.copy().update(env)
# TODO: This is deprecated. Remove in a future version.
return_output = kwargs.pop("return_output", False)
@@ -149,6 +158,7 @@ class Executable(object):
cmd_line = "'%s'" % "' '".join(
map(lambda arg: arg.replace("'", "'\"'\"'"), cmd))
+
tty.debug(cmd_line)
try:
@@ -173,11 +183,11 @@ class Executable(object):
result += err
return result
- except OSError, e:
+ except OSError as e:
raise ProcessError(
"%s: %s" % (self.exe[0], e.strerror), "Command: " + cmd_line)
- except subprocess.CalledProcessError, e:
+ except subprocess.CalledProcessError as e:
if fail_on_error:
raise ProcessError(
str(e), "\nExit status %d when invoking command: %s" %
@@ -185,11 +195,11 @@ class Executable(object):
finally:
if close_ostream:
- output.close()
+ ostream.close()
if close_estream:
- error.close()
+ estream.close()
if close_istream:
- input.close()
+ istream.close()
def __eq__(self, other):
return self.exe == other.exe
@@ -226,74 +236,4 @@ def which(name, **kwargs):
class ProcessError(spack.error.SpackError):
-
- def __init__(self, msg, long_message=None):
- # These are used for detailed debugging information for
- # package builds. They're built up gradually as the exception
- # propagates.
- self.package_context = _get_package_context()
- self.build_log = None
-
- super(ProcessError, self).__init__(msg, long_message)
-
- @property
- def long_message(self):
- msg = self._long_message
- if msg:
- msg += "\n\n"
-
- if self.build_log:
- msg += "See build log for details:\n"
- msg += " %s" % self.build_log
-
- if self.package_context:
- if msg:
- msg += "\n\n"
- msg += '\n'.join(self.package_context)
-
- return msg
-
-
-def _get_package_context():
- """Return some context for an error message when the build fails.
-
- This should be called within a ProcessError when the exception is
- thrown.
-
- Args:
- process_error -- A ProcessError raised during install()
-
- This function inspects the stack to find where we failed in the
- package file, and it adds detailed context to the long_message
- from there.
-
- """
- lines = []
-
- # Walk up the stack
- for f in inspect.stack():
- frame = f[0]
-
- # Find a frame with 'self' in the local variables.
- if 'self' not in frame.f_locals:
- continue
-
- # Look only at a frame in a subclass of spack.Package
- obj = frame.f_locals['self']
- if type(obj) != spack.Package and isinstance(obj, spack.Package):
- break
- else:
- # Didn't find anything
- return lines
-
- # Build a message showing where in install we failed.
- lines.append("%s:%d, in %s:" % (
- inspect.getfile(frame.f_code), frame.f_lineno, frame.f_code.co_name
- ))
-
- sourcelines, start = inspect.getsourcelines(frame)
- for i, line in enumerate(sourcelines):
- mark = ">> " if start + i == frame.f_lineno else " "
- lines.append(" %s%-5d%s" % (mark, start + i, line.rstrip()))
-
- return lines
+ """ProcessErrors are raised when Executables exit with an error code."""
diff --git a/lib/spack/spack/util/path.py b/lib/spack/spack/util/path.py
new file mode 100644
index 0000000000..7235f6b756
--- /dev/null
+++ b/lib/spack/spack/util/path.py
@@ -0,0 +1,72 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created 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 Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, 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 Lesser 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
+##############################################################################
+"""Utilities for managing paths in Spack.
+"""
+import os
+import re
+import spack
+import getpass
+import tempfile
+
+__all__ = [
+ 'substitute_config_variables',
+ 'canonicalize_path']
+
+# Substitutions to perform
+replacements = {
+ 'spack': spack.prefix,
+ 'user': getpass.getuser(),
+ 'tempdir': tempfile.gettempdir(),
+}
+
+
+def substitute_config_variables(path):
+ """Substitute placeholders into paths.
+
+ Spack allows paths in configs to have some placeholders, as follows:
+
+ - $spack The Spack instance's prefix
+ - $user The current user's username
+ - $tempdir Default temporary directory returned by tempfile.gettempdir()
+
+ These are substituted case-insensitively into the path, and users can
+ use either ``$var`` or ``${var}`` syntax for the variables.
+
+ """
+ # Look up replacements for re.sub in the replacements dict.
+ def repl(match):
+ m = match.group(0).strip('${}')
+ return replacements.get(m.lower(), match.group(0))
+
+ # Replace $var or ${var}.
+ return re.sub(r'(\$\w+\b|\$\{\w+\})', repl, path)
+
+
+def canonicalize_path(path):
+ """Substitute config vars, expand user home, take abspath."""
+ path = substitute_config_variables(path)
+ path = os.path.expanduser(path)
+ path = os.path.abspath(path)
+ return path
diff --git a/lib/spack/spack/util/pattern.py b/lib/spack/spack/util/pattern.py
index c36445193c..b5731ccf08 100644
--- a/lib/spack/spack/util/pattern.py
+++ b/lib/spack/spack/util/pattern.py
@@ -31,15 +31,15 @@ def composite(interface=None, method_list=None, container=list):
"""Returns a class decorator that patches a class adding all the methods
it needs to be a composite for a given interface.
- :param interface: class exposing the interface to which the composite
- object must conform. Only non-private and non-special methods will be
- taken into account
+ :param interface: class exposing the interface to which the composite \
+ object must conform. Only non-private and non-special methods will \
+ be taken into account
:param method_list: names of methods that should be part of the composite
- :param container: container for the composite object (default = list).
- Must fulfill the MutableSequence contract. The composite class will expose
- the container API to manage object composition
+ :param container: container for the composite object (default = list). \
+ Must fulfill the MutableSequence contract. The composite class will \
+ expose the container API to manage object composition
:return: class decorator
"""
@@ -140,3 +140,9 @@ class Bunch(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
+
+
+class Args(Bunch):
+ """Subclass of Bunch to write argparse args more naturally."""
+ def __init__(self, *flags, **kwargs):
+ super(Args, self).__init__(flags=tuple(flags), kwargs=kwargs)
diff --git a/lib/spack/spack/util/spack_yaml.py b/lib/spack/spack/util/spack_yaml.py
index 7bcdf2d61f..c27db52066 100644
--- a/lib/spack/spack/util/spack_yaml.py
+++ b/lib/spack/spack/util/spack_yaml.py
@@ -32,6 +32,10 @@
"""
import yaml
+try:
+ from yaml import CLoader as Loader, CDumper as Dumper
+except ImportError as e:
+ from yaml import Loader, Dumper
from yaml.nodes import *
from yaml.constructor import ConstructorError
from ordereddict_backport import OrderedDict
@@ -64,7 +68,7 @@ def mark(obj, node):
obj._end_mark = node.end_mark
-class OrderedLineLoader(yaml.Loader):
+class OrderedLineLoader(Loader):
"""YAML loader that preserves order and line numbers.
Mappings read in by this loader behave like an ordered dict.
@@ -87,7 +91,9 @@ class OrderedLineLoader(yaml.Loader):
value = value.encode('ascii')
except UnicodeEncodeError:
pass
+
value = syaml_str(value)
+
mark(value, node)
return value
@@ -156,7 +162,7 @@ OrderedLineLoader.add_constructor(
u'tag:yaml.org,2002:str', OrderedLineLoader.construct_yaml_str)
-class OrderedLineDumper(yaml.Dumper):
+class OrderedLineDumper(Dumper):
"""Dumper that preserves ordering and formats ``syaml_*`` objects.
This dumper preserves insertion ordering ``syaml_dict`` objects
@@ -196,6 +202,11 @@ class OrderedLineDumper(yaml.Dumper):
node.flow_style = best_style
return node
+ def ignore_aliases(self, _data):
+ """Make the dumper NEVER print YAML aliases."""
+ return True
+
+
# Make our special objects look like normal YAML ones.
OrderedLineDumper.add_representer(syaml_dict, OrderedLineDumper.represent_dict)
OrderedLineDumper.add_representer(syaml_list, OrderedLineDumper.represent_list)
diff --git a/lib/spack/spack/util/web.py b/lib/spack/spack/util/web.py
index 29ed6e0d32..935532266f 100644
--- a/lib/spack/spack/util/web.py
+++ b/lib/spack/spack/util/web.py
@@ -227,7 +227,16 @@ def find_versions_of_archive(*archive_urls, **kwargs):
# We'll be a bit more liberal and just look for the archive
# part, not the full path.
- regexes.append(os.path.basename(url_regex))
+ url_regex = os.path.basename(url_regex)
+
+ # We need to add a $ anchor to the end of the regex to prevent
+ # Spack from picking up signature files like:
+ # .asc
+ # .md5
+ # .sha256
+ # .sig
+ # However, SourceForge downloads still need to end in '/download'.
+ regexes.append(url_regex + '(\/download)?$')
# Build a dict version -> URL from any links that match the wildcards.
versions = {}
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index e1311eb0d9..0d68a709e8 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -49,6 +49,7 @@ from bisect import bisect_left
from functools import wraps
from functools_backport import total_ordering
+from spack.util.spack_yaml import syaml_dict
__all__ = ['Version', 'VersionRange', 'VersionList', 'ver']
@@ -106,6 +107,10 @@ def coerced(method):
return coercing_method
+def _numeric_lt(self0, other):
+ """Compares two versions, knowing they're both numeric"""
+
+
@total_ordering
class Version(object):
"""Class to represent versions"""
@@ -141,6 +146,10 @@ class Version(object):
def dashed(self):
return '-'.join(str(x) for x in self.version)
+ @property
+ def joined(self):
+ return ''.join(str(x) for x in self.version)
+
def up_to(self, index):
"""Return a version string up to the specified component, exclusive.
e.g., if this is 10.8.2, self.up_to(2) will return '10.8'.
@@ -153,13 +162,35 @@ class Version(object):
def highest(self):
return self
+ def isnumeric(self):
+ """Tells if this version is numeric (vs. a non-numeric version). A
+ version will be numeric as long as the first section of it is,
+ even if it contains non-numerica portions.
+
+ Some numeric versions:
+ 1
+ 1.1
+ 1.1a
+ 1.a.1b
+ Some non-numeric versions:
+ develop
+ system
+ myfavoritebranch
+ """
+ return isinstance(self.version[0], numbers.Integral)
+
+ def isdevelop(self):
+ """Triggers on the special case of the `@develop` version."""
+ return self.string == 'develop'
+
@coerced
def satisfies(self, other):
- """A Version 'satisfies' another if it is at least as specific and has a
- common prefix. e.g., we want gcc@4.7.3 to satisfy a request for
- gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
- a suitable compiler.
+ """A Version 'satisfies' another if it is at least as specific and has
+ a common prefix. e.g., we want gcc@4.7.3 to satisfy a request for
+ gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
+ a suitable compiler.
"""
+
nself = len(self.version)
nother = len(other.version)
return nother <= nself and self.version[:nother] == other.version
@@ -223,6 +254,27 @@ class Version(object):
def concrete(self):
return self
+ def _numeric_lt(self, other):
+ """Compares two versions, knowing they're both numeric"""
+ # Standard comparison of two numeric versions
+ for a, b in zip(self.version, other.version):
+ if a == b:
+ continue
+ else:
+ # Numbers are always "newer" than letters.
+ # This is for consistency with RPM. See patch
+ # #60884 (and details) from bugzilla #50977 in
+ # the RPM project at rpm.org. Or look at
+ # rpmvercmp.c if you want to see how this is
+ # implemented there.
+ if type(a) != type(b):
+ return type(b) == int
+ else:
+ return a < b
+ # If the common prefix is equal, the one
+ # with more segments is bigger.
+ return len(self.version) < len(other.version)
+
@coerced
def __lt__(self, other):
"""Version comparison is designed for consistency with the way RPM
@@ -238,30 +290,33 @@ class Version(object):
if self.version == other.version:
return False
- # dev is __gt__ than anything but itself.
- if other.string == 'develop':
- return True
-
- # If lhs is dev then it can't be < than anything
- if self.string == 'develop':
- return False
-
- for a, b in zip(self.version, other.version):
- if a == b:
- continue
- else:
- # Numbers are always "newer" than letters. This is for
- # consistency with RPM. See patch #60884 (and details)
- # from bugzilla #50977 in the RPM project at rpm.org.
- # Or look at rpmvercmp.c if you want to see how this is
- # implemented there.
- if type(a) != type(b):
- return type(b) == int
- else:
- return a < b
-
- # If the common prefix is equal, the one with more segments is bigger.
- return len(self.version) < len(other.version)
+ # First priority: anything < develop
+ sdev = self.isdevelop()
+ if sdev:
+ return False # source = develop, it can't be < anything
+
+ # Now we know !sdev
+ odev = other.isdevelop()
+ if odev:
+ return True # src < dst
+
+ # now we know neither self nor other isdevelop().
+
+ # Principle: Non-numeric is less than numeric
+ # (so numeric will always be preferred by default)
+ if self.isnumeric():
+ if other.isnumeric():
+ return self._numeric_lt(other)
+ else: # self = numeric; other = non-numeric
+ # Numeric > Non-numeric (always)
+ return False
+ else:
+ if other.isnumeric(): # self = non-numeric, other = numeric
+ # non-numeric < numeric (always)
+ return True
+ else: # Both non-numeric
+ # Maybe consider other ways to compare here...
+ return self.string < other.string
@coerced
def __eq__(self, other):
@@ -388,12 +443,12 @@ class VersionRange(object):
@coerced
def satisfies(self, other):
- """
- A VersionRange satisfies another if some version in this range
+ """A VersionRange satisfies another if some version in this range
would satisfy some version in the other range. To do this it must
either:
- a) Overlap with the other range
- b) The start of this range satisfies the end of the other range.
+
+ a) Overlap with the other range
+ b) The start of this range satisfies the end of the other range.
This is essentially the same as overlaps(), but overlaps assumes
that its arguments are specific. That is, 4.7 is interpreted as
@@ -401,6 +456,7 @@ class VersionRange(object):
by 4.7.3.5, etc.
Rationale:
+
If a user asks for gcc@4.5:4.7, and a package is only compatible with
gcc@4.7.3:4.8, then that package should be able to build under the
constraints. Just using overlaps() would not work here.
@@ -591,9 +647,13 @@ class VersionList(object):
def to_dict(self):
"""Generate human-readable dict for YAML."""
if self.concrete:
- return {'version': str(self[0])}
+ return syaml_dict([
+ ('version', str(self[0]))
+ ])
else:
- return {'versions': [str(v) for v in self]}
+ return syaml_dict([
+ ('versions', [str(v) for v in self])
+ ])
@staticmethod
def from_dict(dictionary):