summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Todd Gamblin <gamblin2@llnl.gov>2014-06-22 13:00:17 -0700
committerGeorge Todd Gamblin <gamblin2@llnl.gov>2014-06-22 13:00:17 -0700
commitf7fe65102f082c743949708ed14767d22831b728 (patch)
tree8f2d9d9a93f5f57fdd7fc121e9d320ab9b187158
parentc66a6a16f9224ee340ba3f602115dc638f5807c4 (diff)
parent33a11f32fdaea48192ecb13a85af11f85aa6b8bf (diff)
downloadspack-f7fe65102f082c743949708ed14767d22831b728.tar.gz
spack-f7fe65102f082c743949708ed14767d22831b728.tar.bz2
spack-f7fe65102f082c743949708ed14767d22831b728.tar.xz
spack-f7fe65102f082c743949708ed14767d22831b728.zip
Merge pull request #14 in SCALE/spack from features/compilers to develop
# By Todd Gamblin # Via Todd Gamblin * commit '33a11f32fdaea48192ecb13a85af11f85aa6b8bf': (21 commits) Multi-compiler support feature-complete. Fix SPACK-3, SPACK-4, SPACK-12. Executables can optionally ignore error output. Enable allow_no_value for config parser. Make tests use mock compiler configuration. Set default editor to vi if EDITOR is not set. Add working_dir, Version back into package build namespace. Compiler support now uses configuration files. Make debug and verbose output work properly. Minor cleanup and bug fixes. Add support for configuration files. Fix SPACK-24. Proper exiting for forked process in do_install() Move globals to spack's __init__.py Move globals to spack's __init__.py Adding per-compiler python files Initial ability to swap compilers. Better satisfies: e.g., v4.7.3 now satisfies v4.7 Add CompilerSpec class and loading capability. Implemented compiler concretization policy. Sorted out spack.compilers vs var/spack/compilers Make Compiler constructor behave like Spec constructor. ...
-rw-r--r--.gitignore1
-rwxr-xr-xbin/spack7
-rw-r--r--lib/spack/docs/packaging_guide.rst4
-rwxr-xr-xlib/spack/env/cc56
-rw-r--r--lib/spack/llnl/util/filesystem.py3
-rw-r--r--lib/spack/llnl/util/tty/__init__.py22
-rw-r--r--lib/spack/spack/__init__.py146
-rw-r--r--lib/spack/spack/build_environment.py37
-rw-r--r--lib/spack/spack/cmd/bootstrap.py2
-rw-r--r--lib/spack/spack/cmd/compiler.py81
-rw-r--r--lib/spack/spack/cmd/compilers.py8
-rw-r--r--lib/spack/spack/cmd/config.py84
-rw-r--r--lib/spack/spack/cmd/find.py17
-rw-r--r--lib/spack/spack/cmd/install.py6
-rw-r--r--lib/spack/spack/cmd/uninstall.py22
-rw-r--r--lib/spack/spack/compiler.py292
-rw-r--r--lib/spack/spack/compilers/__init__.py217
-rw-r--r--lib/spack/spack/compilers/clang.py52
-rw-r--r--lib/spack/spack/compilers/gcc.py38
-rw-r--r--lib/spack/spack/compilers/intel.py42
-rw-r--r--lib/spack/spack/compilers/pgi.py51
-rw-r--r--lib/spack/spack/concretize.py67
-rw-r--r--lib/spack/spack/config.py492
-rw-r--r--lib/spack/spack/globals.py135
-rw-r--r--lib/spack/spack/multimethod.py2
-rw-r--r--lib/spack/spack/package.py39
-rw-r--r--lib/spack/spack/packages.py53
-rw-r--r--lib/spack/spack/spec.py167
-rw-r--r--lib/spack/spack/stage.py2
-rw-r--r--lib/spack/spack/test/__init__.py3
-rw-r--r--lib/spack/spack/test/concretize.py14
-rw-r--r--lib/spack/spack/test/config.py69
-rw-r--r--lib/spack/spack/test/install.py6
-rw-r--r--lib/spack/spack/test/mock_packages_test.py9
-rw-r--r--lib/spack/spack/test/package_sanity.py7
-rw-r--r--lib/spack/spack/test/packages.py11
-rw-r--r--lib/spack/spack/test/spec_semantics.py36
-rw-r--r--lib/spack/spack/test/spec_syntax.py12
-rw-r--r--lib/spack/spack/test/versions.py73
-rw-r--r--lib/spack/spack/util/executable.py49
-rw-r--r--lib/spack/spack/util/multiproc.py45
-rw-r--r--lib/spack/spack/util/naming.py61
-rw-r--r--lib/spack/spack/version.py69
-rw-r--r--var/spack/mock_configs/site_spackconfig12
-rw-r--r--var/spack/mock_configs/user_spackconfig0
-rw-r--r--var/spack/packages/libdwarf/package.py2
46 files changed, 2208 insertions, 415 deletions
diff --git a/.gitignore b/.gitignore
index 7010bf7ede..ed2012d208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
*~
.DS_Store
.idea
+/etc/spackconfig
diff --git a/bin/spack b/bin/spack
index fb9f045f35..df517c1f1d 100755
--- a/bin/spack
+++ b/bin/spack
@@ -41,7 +41,7 @@ sys.path.insert(0, SPACK_LIB_PATH)
# If there is no working directory, use the spack prefix.
try:
- os.getcwd()
+ working_dir = os.getcwd()
except OSError:
os.chdir(SPACK_PREFIX)
@@ -77,8 +77,11 @@ for cmd in spack.cmd.commands:
args = parser.parse_args()
# Set up environment based on args.
-spack.verbose = args.verbose
+tty.set_verbose(args.verbose)
+tty.set_debug(args.debug)
spack.debug = args.debug
+
+spack.spack_working_dir = working_dir
if args.mock:
from spack.packages import PackageDB
spack.db = PackageDB(spack.mock_packages_path)
diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst
index 4e076aa991..da944b33c8 100644
--- a/lib/spack/docs/packaging_guide.rst
+++ b/lib/spack/docs/packaging_guide.rst
@@ -107,7 +107,7 @@ Package class names
The **class name** (``Libelf`` in our example) is formed by converting
words separated by `-` or ``_`` in the file name to camel case. If
the name starts with a number, we prefix the class name with
-``Num_``. Here are some examples:
+``_``. Here are some examples:
================= =================
Module Name Class Name
@@ -115,7 +115,7 @@ the name starts with a number, we prefix the class name with
``foo_bar`` ``FooBar``
``docbook-xml`` ``DocbookXml``
``FooBar`` ``Foobar``
- ``3proxy`` ``Num_3proxy``
+ ``3proxy`` ``_3proxy``
================= =================
The class name is needed by Spack to properly import a package, but
diff --git a/lib/spack/env/cc b/lib/spack/env/cc
index e5dbf21beb..4478747844 100755
--- a/lib/spack/env/cc
+++ b/lib/spack/env/cc
@@ -10,7 +10,7 @@ import argparse
from contextlib import closing
# Import spack parameters through the build environment.
-spack_lib = os.environ.get("SPACK_LIB")
+spack_lib = os.environ.get("SPACK_LIB")
if not spack_lib:
print "Spack compiler must be run from spack!"
sys.exit(1)
@@ -20,24 +20,24 @@ sys.path.append(spack_lib)
from spack.compilation import *
import llnl.util.tty as tty
-spack_prefix = get_env_var("SPACK_PREFIX")
-spack_build_root = get_env_var("SPACK_BUILD_ROOT")
-spack_debug = get_env_flag("SPACK_DEBUG")
-spack_deps = get_path("SPACK_DEPENDENCIES")
-spack_env_path = get_path("SPACK_ENV_PATH")
+spack_prefix = get_env_var("SPACK_PREFIX")
+spack_debug = get_env_flag("SPACK_DEBUG")
+spack_deps = get_path("SPACK_DEPENDENCIES")
+spack_env_path = get_path("SPACK_ENV_PATH")
+spack_debug_log_dir = get_env_var("SPACK_DEBUG_LOG_DIR")
+spack_spec = get_env_var("SPACK_SPEC")
+
+compiler_spec = get_env_var("SPACK_COMPILER_SPEC")
+spack_cc = get_env_var("SPACK_CC", required=False)
+spack_cxx = get_env_var("SPACK_CXX", required=False)
+spack_f77 = get_env_var("SPACK_F77", required=False)
+spack_fc = get_env_var("SPACK_FC", required=False)
# Figure out what type of operation we're doing
command = os.path.basename(sys.argv[0])
cpp, cc, ccld, ld, version_check = range(5)
-########################################################################
-# TODO: this can to be removed once JIRA issue SPACK-16 is resolved
-#
-if command == 'CC':
- command = 'c++'
-########################################################################
-
if command == 'cpp':
mode = cpp
elif command == 'ld':
@@ -49,7 +49,31 @@ elif '-c' in sys.argv:
else:
mode = ccld
-if '-V' in sys.argv or '-v' in sys.argv or '--version' in sys.argv:
+
+if command in ('cc', 'gcc', 'c89', 'c99', 'clang'):
+ command = spack_cc
+ language = "C"
+elif command in ('c++', 'CC', 'g++', 'clang++'):
+ command = spack_cxx
+ language = "C++"
+elif command in ('f77'):
+ command = spack_f77
+ language = "Fortran 77"
+elif command in ('fc'):
+ command = spack_fc
+ language = "Fortran 90"
+elif command in ('ld', 'cpp'):
+ pass # leave it the same. TODO: what's the right thing?
+else:
+ raise Exception("Unknown compiler: %s" % command)
+
+if command is None:
+ print "ERROR: Compiler '%s' does not support compiling %s programs." % (
+ compiler_spec, language)
+ sys.exit(1)
+
+version_args = ['-V', '-v', '--version', '-dumpversion']
+if any(arg in sys.argv for arg in version_args):
mode = version_check
# Parse out the includes, libs, etc. so we can adjust them if need be.
@@ -104,8 +128,8 @@ os.environ["PATH"] = ":".join(path)
full_command = [command] + arguments
if spack_debug:
- input_log = os.path.join(spack_build_root, 'spack_cc_in.log')
- output_log = os.path.join(spack_build_root, 'spack_cc_out.log')
+ input_log = os.path.join(spack_debug_log_dir, 'spack-cc-%s.in.log' % spack_spec)
+ output_log = os.path.join(spack_debug_log_dir, 'spack-cc-%s.out.log' % spack_spec)
with closing(open(input_log, 'a')) as log:
args = [os.path.basename(sys.argv[0])] + sys.argv[1:]
log.write("%s\n" % " ".join(arg.replace(' ', r'\ ') for arg in args))
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 2ec8c61cfa..80341f3ddc 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -22,6 +22,9 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+__all__ = ['install', 'expand_user', 'working_dir', 'touch', 'mkdirp',
+ 'join_path', 'ancestor', 'can_access']
+
import os
import re
import shutil
diff --git a/lib/spack/llnl/util/tty/__init__.py b/lib/spack/llnl/util/tty/__init__.py
index fcbafa82a7..3f413cd46e 100644
--- a/lib/spack/llnl/util/tty/__init__.py
+++ b/lib/spack/llnl/util/tty/__init__.py
@@ -29,10 +29,20 @@ from StringIO import StringIO
from llnl.util.tty.color import *
-debug = False
-verbose = False
+_debug = False
+_verbose = False
indent = " "
+def set_debug(flag):
+ global _debug
+ _debug = flag
+
+
+def set_verbose(flag):
+ global _verbose
+ _verbose = flag
+
+
def msg(message, *args):
cprint("@*b{==>} %s" % cescape(message))
for arg in args:
@@ -50,13 +60,13 @@ def info(message, *args, **kwargs):
def verbose(message, *args):
- if verbose:
+ if _verbose:
info(message, *args, format='c')
-def debug(*args):
- if debug:
- info("Debug: " + message, *args, format='*g')
+def debug(message, *args):
+ if _debug:
+ info(message, *args, format='g')
def error(message, *args):
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 77aad98524..287f2f862c 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -22,10 +22,144 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-from globals import *
-from util import *
-from error import *
-from package import Package
-from relations import depends_on, provides, patch
-from multimethod import when
+#
+# When packages call 'from spack import *', this is what is brought in.
+#
+# Spack internal code calls 'import spack' and accesses other
+# variables (spack.db, paths, etc.) directly.
+#
+# TODO: maybe this should be separated out and should go in build_environment.py?
+# TODO: it's not clear where all the stuff that needs to be included in packages
+# should live. This file is overloaded for spack core vs. for packages.
+__all__ = ['Package', 'when', 'provides', 'depends_on',
+ 'patch', 'Version', 'working_dir']
+
+import os
+import tempfile
+from llnl.util.filesystem import *
+
+# This lives in $prefix/lib/spac/spack/__file__
+prefix = ancestor(__file__, 4)
+
+# The spack script itself
+spack_file = join_path(prefix, "bin", "spack")
+
+# spack directory hierarchy
+etc_path = join_path(prefix, "etc")
+lib_path = join_path(prefix, "lib", "spack")
+build_env_path = join_path(lib_path, "env")
+module_path = join_path(lib_path, "spack")
+compilers_path = join_path(module_path, "compilers")
+test_path = join_path(module_path, "test")
+var_path = join_path(prefix, "var", "spack")
+stage_path = join_path(var_path, "stage")
+install_path = join_path(prefix, "opt")
+
+#
+# Set up the packages database.
+#
+from spack.packages import PackageDB
+packages_path = join_path(var_path, "packages")
+db = PackageDB(packages_path)
+
+#
+# Paths to mock files for testing.
+#
+mock_packages_path = join_path(var_path, "mock_packages")
+
+mock_config_path = join_path(var_path, "mock_configs")
+mock_site_config = join_path(mock_config_path, "site_spackconfig")
+mock_user_config = join_path(mock_config_path, "user_spackconfig")
+
+#
+# This controls how spack lays out install prefixes and
+# stage directories.
+#
+from spack.directory_layout import SpecHashDirectoryLayout
+install_layout = SpecHashDirectoryLayout(install_path, prefix_size=6)
+
+#
+# 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("1.0")
+
+#
+# Executables used by Spack
+#
+from spack.util.executable import Executable, which
+
+# User's editor from the environment
+editor = Executable(os.environ.get("EDITOR", "vi"))
+
+# 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()
+if _default_tmp != os.getcwd():
+ tmp_dirs.append(os.path.join(_default_tmp, 'spack-stage'))
+tmp_dirs.append('/nfs/tmp2/%u/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
+
+#
+# Places to download tarballs from.
+#
+# TODO: move to configuration.
+#
+# Examples:
+#
+# For a local directory:
+# mirrors = ['file:///Users/gamblin2/spack-mirror']
+#
+# For a website:
+# mirrors = ['http://spackports.org/spack-mirror/']
+#
+# For no mirrors:
+# mirrors = []
+#
+mirrors = []
+
+#
+# Extra imports that should be generally usable from package.py files.
+#
+from llnl.util.filesystem import working_dir
+from spack.package import Package
+from spack.relations import depends_on, provides, patch
+from spack.multimethod import when
+from spack.version import Version
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index d6becb77db..b9d0c16353 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -34,6 +34,7 @@ import platform
from llnl.util.filesystem import *
import spack
+import spack.compilers as compilers
from spack.util.executable import Executable, which
from spack.util.environment import *
@@ -51,7 +52,9 @@ SPACK_LIB = 'SPACK_LIB'
SPACK_ENV_PATH = 'SPACK_ENV_PATH'
SPACK_DEPENDENCIES = 'SPACK_DEPENDENCIES'
SPACK_PREFIX = 'SPACK_PREFIX'
-SPACK_BUILD_ROOT = 'SPACK_BUILD_ROOT'
+SPACK_DEBUG = 'SPACK_DEBUG'
+SPACK_SPEC = 'SPACK_SPEC'
+SPACK_DEBUG_LOG_DIR = 'SPACK_DEBUG_LOG_DIR'
class MakeExecutable(Executable):
@@ -79,6 +82,29 @@ class MakeExecutable(Executable):
super(MakeExecutable, self).__call__(*args, **kwargs)
+def set_compiler_environment_variables(pkg):
+ assert(pkg.spec.concrete)
+ compiler = compilers.compiler_for_spec(pkg.spec.compiler)
+
+ # Set compiler variables used by CMake and autotools
+ os.environ['CC'] = 'cc'
+ os.environ['CXX'] = 'c++'
+ os.environ['F77'] = 'f77'
+ os.environ['FC'] = 'fc'
+
+ # Set SPACK compiler variables so that our wrapper knows what to call
+ if compiler.cc:
+ os.environ['SPACK_CC'] = compiler.cc
+ if compiler.cxx:
+ os.environ['SPACK_CXX'] = compiler.cxx
+ if compiler.f77:
+ os.environ['SPACK_F77'] = compiler.f77
+ if compiler.fc:
+ os.environ['SPACK_FC'] = compiler.fc
+
+ os.environ['SPACK_COMPILER_SPEC'] = str(pkg.spec.compiler)
+
+
def set_build_environment_variables(pkg):
"""This ensures a clean install environment when we build packages.
"""
@@ -102,9 +128,6 @@ def set_build_environment_variables(pkg):
# Install prefix
os.environ[SPACK_PREFIX] = pkg.prefix
- # Build root for logging.
- os.environ[SPACK_BUILD_ROOT] = pkg.stage.expanded_archive_path
-
# Remove these vars from the environment during build becaus they
# can affect how some packages find libraries. We want to make
# sure that builds never pull in unintended external dependencies.
@@ -114,6 +137,12 @@ def set_build_environment_variables(pkg):
bin_dirs = ['%s/bin' % prefix for prefix in dep_prefixes]
path_put_first('PATH', [bin for bin in bin_dirs if os.path.isdir(bin)])
+ # Working directory for the spack command itself, for debug logs.
+ if spack.debug:
+ os.environ[SPACK_DEBUG] = "TRUE"
+ os.environ[SPACK_SPEC] = str(pkg.spec)
+ os.environ[SPACK_DEBUG_LOG_DIR] = spack.spack_working_dir
+
def set_module_variables_for_package(pkg):
"""Populate the module scope of install() with some useful functions.
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index 11fc67e519..31c908d42b 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -26,9 +26,9 @@ import os
from subprocess import check_call, check_output
import llnl.util.tty as tty
+from llnl.util.filesystem import join_path
import spack
-from spack import join_path
description = "Create a new installation of spack in another prefix"
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
new file mode 100644
index 0000000000..af69897ce9
--- /dev/null
+++ b/lib/spack/spack/cmd/compiler.py
@@ -0,0 +1,81 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import argparse
+
+from pprint import pprint
+
+import llnl.util.tty as tty
+from llnl.util.tty.colify import colify
+from llnl.util.lang import index_by
+
+import spack.compilers
+import spack.spec
+import spack.config
+from spack.compilation import get_path
+
+description = "Manage compilers"
+
+def setup_parser(subparser):
+ sp = subparser.add_subparsers(
+ metavar='SUBCOMMAND', dest='compiler_command')
+
+ update_parser = sp.add_parser(
+ 'add', help='Add compilers to the Spack configuration.')
+ update_parser.add_argument('add_paths', nargs=argparse.REMAINDER)
+
+ remove_parser = sp.add_parser('remove', help='remove compiler')
+ remove_parser.add_argument('path')
+
+ list_parser = sp.add_parser('list', help='list available compilers')
+
+
+def compiler_add(args):
+ paths = args.add_paths
+ if not paths:
+ paths = get_path('PATH')
+
+ compilers = spack.compilers.find_compilers(*args.add_paths)
+ spack.compilers.add_compilers_to_config('user', *compilers)
+
+
+def compiler_remove(args):
+ pass
+
+
+def compiler_list(args):
+ tty.msg("Available compilers")
+
+ index = index_by(spack.compilers.all_compilers(), 'name')
+ for name, compilers in index.items():
+ tty.hline(name, char='-', color=spack.spec.compiler_color)
+ colify(reversed(sorted(compilers)), indent=4)
+
+
+def compiler(parser, args):
+ action = { 'add' : compiler_add,
+ 'remove' : compiler_remove,
+ 'list' : compiler_list }
+ action[args.compiler_command](args)
+
diff --git a/lib/spack/spack/cmd/compilers.py b/lib/spack/spack/cmd/compilers.py
index c3f204e3d3..8d046bfd7c 100644
--- a/lib/spack/spack/cmd/compilers.py
+++ b/lib/spack/spack/cmd/compilers.py
@@ -24,11 +24,11 @@
##############################################################################
import llnl.util.tty as tty
from llnl.util.tty.colify import colify
+from llnl.util.lang import index_by
-import spack.compilers
+from spack.cmd.compiler import compiler_list
-description = "List available compilers"
+description = "List available compilers. Same as 'spack compiler list'."
def compilers(parser, args):
- tty.msg("Supported compilers")
- colify(spack.compilers.supported_compilers(), indent=4)
+ compiler_list(args)
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
new file mode 100644
index 0000000000..85f9642019
--- /dev/null
+++ b/lib/spack/spack/cmd/config.py
@@ -0,0 +1,84 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import sys
+import argparse
+
+import llnl.util.tty as tty
+
+import spack.config
+
+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.")
+
+ sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='config_command')
+
+ set_parser = sp.add_parser('set', help='Set configuration values.')
+ set_parser.add_argument('key', help="Key to set value for.")
+ set_parser.add_argument('value', nargs='?', default=None,
+ help="Value to associate with key")
+
+ get_parser = sp.add_parser('get', help='Get configuration values.')
+ get_parser.add_argument('key', help="Key to get value for.")
+
+ edit_parser = sp.add_parser('edit', help='Edit configuration file.')
+
+
+def config_set(args):
+ # default scope for writing is 'user'
+ if not args.scope:
+ args.scope = 'user'
+
+ config = spack.config.get_config(args.scope)
+ config.set_value(args.key, args.value)
+ config.write()
+
+
+def config_get(args):
+ config = spack.config.get_config(args.scope)
+ print config.get_value(args.key)
+
+
+def config_edit(args):
+ if not args.scope:
+ args.scope = 'user'
+ config_file = spack.config.get_filename(args.scope)
+ spack.editor(config_file)
+
+
+def config(parser, args):
+ action = { 'set' : config_set,
+ 'get' : config_get,
+ 'edit' : config_edit }
+ action[args.config_command](args)
+
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index b6ec123c92..08cfb5af96 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -29,7 +29,7 @@ from StringIO import StringIO
import llnl.util.tty as tty
from llnl.util.tty.colify import colify
from llnl.util.tty.color import *
-from llnl.util.lang import partition_list
+from llnl.util.lang import partition_list, index_by
import spack
import spack.spec
@@ -49,9 +49,6 @@ def setup_parser(subparser):
def find(parser, args):
- def hasher():
- return collections.defaultdict(hasher)
-
# Filter out specs that don't exist.
query_specs = spack.cmd.parse_specs(args.query_specs)
query_specs, nonexisting = partition_list(
@@ -64,15 +61,9 @@ def find(parser, args):
return
# Make a dict with specs keyed by architecture and compiler.
- index = hasher()
- for spec in spack.db.installed_package_specs():
- # Check whether this installed package matches any query.
- if query_specs and not any(spec.satisfies(q) for q in query_specs):
- continue
-
- if spec.compiler not in index[spec.architecture]:
- index[spec.architecture][spec.compiler] = []
- index[spec.architecture][spec.compiler].append(spec)
+ specs = [s for s in spack.db.installed_package_specs()
+ if not query_specs or any(s.satisfies(q) for q in query_specs)]
+ index = index_by(specs, 'architecture', 'compiler')
# Traverse the index and print out each package
for architecture in index:
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index ea11cb89a9..4570d6c40f 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -36,7 +36,10 @@ def setup_parser(subparser):
help="Do not try to install dependencies of requested packages.")
subparser.add_argument(
'--keep-prefix', action='store_true', dest='keep_prefix',
- help="Don't clean up staging area when install completes.")
+ help="Don't remove the install prefix if installation fails.")
+ subparser.add_argument(
+ '--keep-stage', action='store_true', dest='keep_stage',
+ help="Don't remove the build stage if installation succeeds.")
subparser.add_argument(
'-n', '--no-checksum', action='store_true', dest='no_checksum',
help="Do not check packages against checksum")
@@ -55,4 +58,5 @@ def install(parser, args):
for spec in specs:
package = spack.db.get(spec)
package.do_install(keep_prefix=args.keep_prefix,
+ keep_stage=args.keep_stage,
ignore_deps=args.ignore_deps)
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index df208b3a6a..28a2f659ae 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -36,6 +36,11 @@ def setup_parser(subparser):
'-f', '--force', action='store_true', dest='force',
help="Remove regardless of whether other packages depend on this one.")
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.")
+ subparser.add_argument(
'packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall")
@@ -50,15 +55,20 @@ def uninstall(parser, args):
pkgs = []
for spec in specs:
matching_specs = spack.db.get_installed(spec)
- if len(matching_specs) > 1:
- tty.die("%s matches multiple packages. Which one did you mean?"
- % spec, *matching_specs)
+ if not args.all and len(matching_specs) > 1:
+ args = ["%s matches multiple packages." % spec,
+ "Matching packages:"]
+ args += [" " + str(s) for s in matching_specs]
+ args += ["You can either:",
+ " a) Use spack uninstall -a to uninstall ALL matching specs, or",
+ " b) use a more specific spec."]
+ tty.die(*args)
+
- elif len(matching_specs) == 0:
+ if len(matching_specs) == 0:
tty.die("%s does not match any installed packages." % spec)
- installed_spec = matching_specs[0]
- pkgs.append(spack.db.get(installed_spec))
+ pkgs.extend(spack.db.get(s) for s in matching_specs)
# Sort packages to be uninstalled by the number of installed dependents
# This ensures we do things in the right order
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
new file mode 100644
index 0000000000..b04034bbdd
--- /dev/null
+++ b/lib/spack/spack/compiler.py
@@ -0,0 +1,292 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import os
+import re
+import itertools
+from datetime import datetime
+
+import llnl.util.tty as tty
+from llnl.util.lang import memoized
+from llnl.util.filesystem import join_path
+
+import spack.error
+import spack.spec
+from spack.util.multiproc import parmap
+from spack.util.executable import *
+from spack.version import Version
+from spack.compilation import get_path
+
+__all__ = ['Compiler', 'get_compiler_version']
+
+def _verify_executables(*paths):
+ for path in paths:
+ if not os.path.isfile(path) and os.access(path, os.X_OK):
+ raise CompilerAccessError(path)
+
+
+_version_cache = {}
+
+def get_compiler_version(compiler_path, version_arg, regex='(.*)'):
+ if not compiler_path in _version_cache:
+ compiler = Executable(compiler_path)
+ output = compiler(version_arg, return_output=True, error=None)
+
+ match = re.search(regex, output)
+ _version_cache[compiler_path] = match.group(1) if match else None
+
+ return _version_cache[compiler_path]
+
+
+def dumpversion(compiler_path):
+ """Simple default dumpversion method -- this is what gcc does."""
+ return get_compiler_version(compiler_path, '-dumpversion')
+
+
+class Compiler(object):
+ """This class encapsulates a Spack "compiler", which includes C,
+ C++, and Fortran compilers. Subclasses should implement
+ support for specific compilers, their possible names, arguments,
+ and how to identify the particular type of compiler."""
+
+ # Subclasses use possible names of C compiler
+ cc_names = []
+
+ # Subclasses use possible names of C++ compiler
+ cxx_names = []
+
+ # Subclasses use possible names of Fortran 77 compiler
+ f77_names = []
+
+ # Subclasses use possible names of Fortran 90 compiler
+ fc_names = []
+
+ # Optional prefix regexes for searching for this type of compiler.
+ # Prefixes are sometimes used for toolchains, e.g. 'powerpc-bgq-linux-'
+ prefixes = []
+
+ # Optional suffix regexes for searching for this type of compiler.
+ # Suffixes are used by some frameworks, e.g. macports uses an '-mp-X.Y'
+ # version suffix for gcc.
+ suffixes = [r'-.*']
+
+ # Names of generic arguments used by this compiler
+ arg_rpath = '-Wl,-rpath,%s'
+
+
+ def __init__(self, cc, cxx, f77, fc, version=None):
+ def check(exe):
+ if exe is None:
+ return None
+ _verify_executables(exe)
+ return exe
+
+ self.cc = check(cc)
+ self.cxx = check(cxx)
+ self.f77 = check(f77)
+ self.fc = check(fc)
+
+ # Allow versions to be memoized so we don't have to run
+ # compilers many times. Record them in the version cache if
+ # we get them in a constructor
+ #
+ # TODO: what to do if compilers have different versions?
+ #
+ self._version = version
+ self._cache_version()
+
+
+ @property
+ def version(self):
+ if not self._version:
+ v = self.cc_version(self.cc)
+ if v is not None:
+ self._version = v
+ return Version(v)
+
+ v = self.cxx_version(self.cxx)
+ if v is not None:
+ self._version = v
+ return Version(v)
+
+ v = self.f77_version(self.f77)
+ if v is not None:
+ self._version = v
+ return Version(v)
+
+ v = self.fc_version(self.fc)
+ if v is not None:
+ self._version = v
+ return Version(v)
+
+ raise InvalidCompilerError()
+
+ return Version(self._version)
+
+
+ def _cache_version(self):
+ _version_cache[self.cc] = self._version
+ _version_cache[self.cxx] = self._version
+ _version_cache[self.f77] = self._version
+ _version_cache[self.fc] = self._version
+
+
+ @property
+ def spec(self):
+ return spack.spec.CompilerSpec(self.name, self.version)
+
+
+ @classmethod
+ def default_version(cls, cc):
+ """Override just this to override all compiler version functions."""
+ return dumpversion(cc)
+
+ @classmethod
+ def cc_version(cls, cc):
+ return cls.default_version(cc)
+
+ @classmethod
+ def cxx_version(cls, cxx):
+ return cls.default_version(cxx)
+
+ @classmethod
+ def f77_version(cls, f77):
+ return cls.default_version(f77)
+
+ @classmethod
+ def fc_version(cls, fc):
+ return cls.default_version(fc)
+
+
+ @classmethod
+ def _find_matches_in_path(cls, compiler_names, detect_version, *path):
+ """Finds compilers in the paths supplied.
+
+ Looks for all combinations of ``compiler_names`` with the
+ ``prefixes`` and ``suffixes`` defined for this compiler
+ class. If any compilers match the compiler_names,
+ prefixes, or suffixes, uses ``detect_version`` to figure
+ out what version the compiler is.
+
+ This returns a dict with compilers grouped by (prefix,
+ suffix, version) tuples. This can be further organized by
+ find().
+ """
+ if not path:
+ path = get_path('PATH')
+
+ prefixes = [''] + cls.prefixes
+ suffixes = [''] + cls.suffixes
+
+ checks = []
+ for directory in path:
+ files = os.listdir(directory)
+ for exe in files:
+ full_path = join_path(directory, exe)
+
+ prod = itertools.product(prefixes, compiler_names, suffixes)
+ for pre, name, suf in prod:
+ regex = r'^(%s)%s(%s)$' % (pre, re.escape(name), suf)
+
+ match = re.match(regex, exe)
+ if match:
+ key = (full_path,) + match.groups()
+ checks.append(key)
+
+ def check(key):
+ try:
+ full_path, prefix, suffix = key
+ version = detect_version(full_path)
+ return (version, prefix, suffix, full_path)
+ except ProcessError, e:
+ tty.debug("Couldn't get version for compiler %s" % full_path, e)
+ return None
+
+ successful = [key for key in parmap(check, checks) if key is not None]
+ return { (v, p, s) : path for v, p, s, path in successful }
+
+ @classmethod
+ def find(cls, *path):
+ """Try to find this type of compiler in the user's
+ environment. For each set of compilers found, this returns
+ compiler objects with the cc, cxx, f77, fc paths and the
+ version filled in.
+
+ This will search for compilers with the names in cc_names,
+ cxx_names, etc. and it will group them if they have common
+ prefixes, suffixes, and versions. e.g., gcc-mp-4.7 would
+ be grouped with g++-mp-4.7 and gfortran-mp-4.7.
+
+ Example return values::
+
+ [ gcc('/usr/bin/gcc', '/usr/bin/g++',
+ '/usr/bin/gfortran', '/usr/bin/gfortran',
+ Version('4.4.5')),
+ gcc('/usr/bin/gcc-mp-4.5', '/usr/bin/g++-mp-4.5',
+ '/usr/bin/gfortran-mp-4.5', '/usr/bin/gfortran-mp-4.5',
+ Version('4.7.2')) ]
+
+ """
+ dicts = parmap(
+ lambda t: cls._find_matches_in_path(*t),
+ [(cls.cc_names, cls.cc_version) + tuple(path),
+ (cls.cxx_names, cls.cxx_version) + tuple(path),
+ (cls.f77_names, cls.f77_version) + tuple(path),
+ (cls.fc_names, cls.fc_version) + tuple(path)])
+
+ all_keys = set()
+ for d in dicts:
+ all_keys.update(d)
+
+ compilers = []
+ for k in all_keys:
+ ver, pre, suf = k
+ paths = tuple(pn[k] if k in pn else None for pn in dicts)
+ args = paths + (ver,)
+ compilers.append(cls(*args))
+
+ return compilers
+
+
+ def __repr__(self):
+ """Return a string represntation of the compiler toolchain."""
+ return self.__str__()
+
+
+ def __str__(self):
+ """Return a string represntation of the compiler toolchain."""
+ return "%s(%s)" % (
+ self.name, '\n '.join((str(s) for s in (self.cc, self.cxx, self.f77, self.fc))))
+
+
+class CompilerAccessError(spack.error.SpackError):
+ def __init__(self, path):
+ super(CompilerAccessError, self).__init__(
+ "'%s' is not a valid compiler." % path)
+
+
+class InvalidCompilerError(spack.error.SpackError):
+ def __init__(self):
+ super(InvalidCompilerError, self).__init__(
+ "Compiler has no executables.")
diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py
index 5448e709c4..36d995b6bd 100644
--- a/lib/spack/spack/compilers/__init__.py
+++ b/lib/spack/spack/compilers/__init__.py
@@ -22,23 +22,218 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
-#
-# This needs to be expanded for full compiler support.
-#
+"""This module contains functions related to finding compilers on the
+system and configuring Spack to use multiple compilers.
+"""
+import imp
+import os
+
from llnl.util.lang import memoized, list_modules
+from llnl.util.filesystem import join_path
+
import spack
-import spack.compilers.gcc
+import spack.error
+import spack.spec
+import spack.config
-@memoized
-def supported_compilers():
- return [c for c in list_modules(spack.compilers_path)]
+from spack.util.multiproc import parmap
+from spack.compiler import Compiler
+from spack.util.executable import which
+from spack.util.naming import mod_to_class
+from spack.compilation import get_path
+
+_imported_compilers_module = 'spack.compilers'
+_required_instance_vars = ['cc', 'cxx', 'f77', 'fc']
+_default_order = ['gcc', 'intel', 'pgi', 'clang']
-def supported(compiler):
- return compiler in supported_compilers()
+def _auto_compiler_spec(function):
+ def converter(cspec_like):
+ if not isinstance(cspec_like, spack.spec.CompilerSpec):
+ cspec_like = spack.spec.CompilerSpec(cspec_like)
+ return function(cspec_like)
+ return converter
+
+
+def _get_config():
+ """Get a Spack config, but make sure it has compiler configuration
+ first."""
+ # If any configuration file has compilers, just stick with the
+ # ones already configured.
+ config = spack.config.get_config()
+ existing = [spack.spec.CompilerSpec(s)
+ for s in config.get_section_names('compiler')]
+ if existing:
+ return config
+
+ compilers = find_compilers(*get_path('PATH'))
+ new_compilers = [
+ c for c in compilers if c.spec not in existing]
+ add_compilers_to_config('user', *new_compilers)
+
+ # After writing compilers to the user config, return a full config
+ # from all files.
+ return spack.config.get_config(refresh=True)
@memoized
def default_compiler():
- from spack.spec import Compiler
- return Compiler('gcc', gcc.get_version())
+ versions = []
+ for name in _default_order: # TODO: customize order.
+ versions = find(name)
+ if versions: break
+
+ if not versions:
+ raise NoCompilersError()
+
+ return sorted(versions)[-1]
+
+
+def find_compilers(*path):
+ """Return a list of compilers found in the suppied paths.
+ This invokes the find() method for each Compiler class,
+ and appends the compilers detected to a list.
+ """
+ # Make sure path elements exist, and include /bin directories
+ # under prefixes.
+ filtered_path = []
+ for p in path:
+ # Eliminate symlinks and just take the real directories.
+ p = os.path.realpath(p)
+ if not os.path.isdir(p):
+ continue
+ filtered_path.append(p)
+
+ # Check for a bin directory, add it if it exists
+ bin = join_path(p, 'bin')
+ if os.path.isdir(bin):
+ filtered_path.append(os.path.realpath(bin))
+
+ # 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.
+ types = all_compiler_types()
+ compiler_lists = parmap(lambda cls: cls.find(*filtered_path), types)
+
+ # ensure all the version calls we made are cached in the parent
+ # process, as well. This speeds up Spack a lot.
+ clist = reduce(lambda x,y: x+y, compiler_lists)
+ for c in clist: c._cache_version()
+ return clist
+
+
+def add_compilers_to_config(scope, *compilers):
+ config = spack.config.get_config(scope)
+ for compiler in compilers:
+ add_compiler(config, compiler)
+ config.write()
+
+
+def add_compiler(config, compiler):
+ def setup_field(cspec, name, exe):
+ path = exe if exe else "None"
+ config.set_value('compiler', cspec, name, path)
+
+ for c in _required_instance_vars:
+ setup_field(compiler.spec, c, getattr(compiler, c))
+
+
+def supported_compilers():
+ """Return a set of names of compilers supported by Spack.
+
+ See available_compilers() to get a list of all the available
+ versions of supported compilers.
+ """
+ return sorted(name for name in list_modules(spack.compilers_path))
+
+
+@_auto_compiler_spec
+def supported(compiler_spec):
+ """Test if a particular compiler is supported."""
+ return compiler_spec.name in supported_compilers()
+
+
+def all_compilers():
+ """Return a set of specs for all the compiler versions currently
+ available to build with. These are instances of CompilerSpec.
+ """
+ configuration = _get_config()
+ return [spack.spec.CompilerSpec(s)
+ for s in configuration.get_section_names('compiler')]
+
+
+@_auto_compiler_spec
+def find(compiler_spec):
+ """Return specs of available compilers that match the supplied
+ compiler spec. Return an list if nothing found."""
+ return [c for c in all_compilers() if c.satisfies(compiler_spec)]
+
+
+@_auto_compiler_spec
+def compilers_for_spec(compiler_spec):
+ """This gets all compilers that satisfy the supplied CompilerSpec.
+ Returns an empty list if none are found.
+ """
+ config = _get_config()
+
+ def get_compiler(cspec):
+ items = { k:v for k,v in config.items('compiler "%s"' % cspec) }
+
+ if not all(n in items for n in _required_instance_vars):
+ raise InvalidCompilerConfigurationError(cspec)
+
+ cls = class_for_compiler_name(cspec.name)
+ compiler_paths = []
+ for c in _required_instance_vars:
+ compiler_path = items[c]
+ if compiler_path != "None":
+ compiler_paths.append(compiler_path)
+ else:
+ compiler_paths.append(None)
+
+ args = tuple(compiler_paths) + (compiler_spec.version,)
+ return cls(*args)
+
+ matches = find(compiler_spec)
+ return [get_compiler(cspec) for cspec in matches]
+
+
+@_auto_compiler_spec
+def compiler_for_spec(compiler_spec):
+ """Get the compiler that satisfies compiler_spec. compiler_spec must
+ be concrete."""
+ assert(compiler_spec.concrete)
+ compilers = compilers_for_spec(compiler_spec)
+ assert(len(compilers) == 1)
+ return compilers[0]
+
+
+def class_for_compiler_name(compiler_name):
+ """Given a compiler module name, get the corresponding Compiler class."""
+ assert(supported(compiler_name))
+
+ file_path = join_path(spack.compilers_path, compiler_name + ".py")
+ compiler_mod = imp.load_source(_imported_compilers_module, file_path)
+ cls = getattr(compiler_mod, mod_to_class(compiler_name))
+
+ # make a note of the name in the module so we can get to it easily.
+ cls.name = compiler_name
+
+ return cls
+
+
+def all_compiler_types():
+ return [class_for_compiler_name(c) for c in supported_compilers()]
+
+
+class InvalidCompilerConfigurationError(spack.error.SpackError):
+ def __init__(self, compiler_spec):
+ super(InvalidCompilerConfigurationError, self).__init__(
+ "Invalid configuration for [compiler \"%s\"]: " % compiler_spec,
+ "Compiler configuration must contain entries for all compilers: %s"
+ % _required_instance_vars)
+
+
+class NoCompilersError(spack.error.SpackError):
+ def __init__(self):
+ super(NoCompilersError, self).__init__("Spack could not find any compilers!")
diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py
new file mode 100644
index 0000000000..73c462b49f
--- /dev/null
+++ b/lib/spack/spack/compilers/clang.py
@@ -0,0 +1,52 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack.compiler import Compiler
+
+class Clang(Compiler):
+ # Subclasses use possible names of C compiler
+ cc_names = ['clang']
+
+ # Subclasses use possible names of C++ compiler
+ cxx_names = ['clang++']
+
+ # Subclasses use possible names of Fortran 77 compiler
+ f77_names = []
+
+ # Subclasses use possible names of Fortran 90 compiler
+ fc_names = []
+
+
+ @classmethod
+ def default_version(self, comp):
+ """The '--version' option works for clang compilers.
+ Output looks like this::
+
+ clang version 3.1 (trunk 149096)
+ Target: x86_64-unknown-linux-gnu
+ Thread model: posix
+ """
+ return get_compiler_version(
+ comp, '--version', r'clang version ([^ ]+)')
+
diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py
index 073219dc20..cc3c52ca61 100644
--- a/lib/spack/spack/compilers/gcc.py
+++ b/lib/spack/spack/compilers/gcc.py
@@ -22,18 +22,32 @@
# 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 stub module. It should be expanded when we implement full
-# compiler support.
-#
+from spack.compiler import *
+
+class Gcc(Compiler):
+ # Subclasses use possible names of C compiler
+ cc_names = ['gcc']
+
+ # Subclasses use possible names of C++ compiler
+ cxx_names = ['g++']
+
+ # Subclasses use possible names of Fortran 77 compiler
+ f77_names = ['gfortran']
+
+ # Subclasses use possible names of Fortran 90 compiler
+ fc_names = ['gfortran']
+
+ # MacPorts builds gcc versions with prefixes and -mp-X.Y suffixes.
+ suffixes = [r'-mp-\d\.\d']
-import subprocess
-from spack.version import Version
+ @classmethod
+ def fc_version(cls, fc):
+ return get_compiler_version(
+ fc, '-dumpversion',
+ # older gfortran versions don't have simple dumpversion output.
+ r'(?:GNU Fortran \(GCC\))?(\d+\.\d+\.\d+)')
-cc = 'gcc'
-cxx = 'g++'
-fortran = 'gfortran'
-def get_version():
- v = subprocess.check_output([cc, '-dumpversion'])
- return Version(v)
+ @classmethod
+ def f77_version(cls, f77):
+ return cls.fc_version(f77)
diff --git a/lib/spack/spack/compilers/intel.py b/lib/spack/spack/compilers/intel.py
index c27fee1719..02e3b96b19 100644
--- a/lib/spack/spack/compilers/intel.py
+++ b/lib/spack/spack/compilers/intel.py
@@ -22,18 +22,36 @@
# 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 stub module. It should be expanded when we implement full
-# compiler support.
-#
+from spack.compiler import *
+
+class Intel(Compiler):
+ # Subclasses use possible names of C compiler
+ cc_names = ['icc']
+
+ # Subclasses use possible names of C++ compiler
+ cxx_names = ['icpc']
+
+ # Subclasses use possible names of Fortran 77 compiler
+ f77_names = ['ifort']
+
+ # Subclasses use possible names of Fortran 90 compiler
+ fc_names = ['ifort']
+
+
+ @classmethod
+ def default_version(cls, comp):
+ """The '--version' option seems to be the most consistent one
+ for intel compilers. Output looks like this::
+
+ icpc (ICC) 12.1.5 20120612
+ Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
+
+ or::
-import subprocess
-from spack.version import Version
+ ifort (IFORT) 12.1.5 20120612
+ Copyright (C) 1985-2012 Intel Corporation. All rights reserved.
+ """
+ return get_compiler_version(
+ comp, '--version', r'\((?:IFORT|ICC)\) ([^ ]+)')
-cc = 'icc'
-cxx = 'icc'
-fortran = 'ifort'
-def get_version():
- v = subprocess.check_output([cc, '-dumpversion'])
- return Version(v)
diff --git a/lib/spack/spack/compilers/pgi.py b/lib/spack/spack/compilers/pgi.py
new file mode 100644
index 0000000000..d97f24c12e
--- /dev/null
+++ b/lib/spack/spack/compilers/pgi.py
@@ -0,0 +1,51 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+from spack.compiler import *
+
+class Pgi(Compiler):
+ # Subclasses use possible names of C compiler
+ cc_names = ['pgcc']
+
+ # Subclasses use possible names of C++ compiler
+ cxx_names = ['pgCC']
+
+ # Subclasses use possible names of Fortran 77 compiler
+ f77_names = ['pgf77']
+
+ # Subclasses use possible names of Fortran 90 compiler
+ fc_names = ['pgf95', 'pgf90']
+
+ @classmethod
+ def default_version(cls, comp):
+ """The '-V' option works for all the PGI compilers.
+ Output looks like this::
+
+ pgf95 10.2-0 64-bit target on x86-64 Linux -tp nehalem-64
+ Copyright 1989-2000, The Portland Group, Inc. All Rights Reserved.
+ Copyright 2000-2010, STMicroelectronics, Inc. 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 fc360d59ba..f5775ef1bf 100644
--- a/lib/spack/spack/concretize.py
+++ b/lib/spack/spack/concretize.py
@@ -33,9 +33,10 @@ or user preferences.
TODO: make this customizable and allow users to configure
concretization policies.
"""
-import spack.architecture
-import spack.compilers
import spack.spec
+import spack.compilers
+import spack.architecture
+import spack.error
from spack.version import *
@@ -50,6 +51,15 @@ class DefaultConcretizer(object):
"""If the spec is already concrete, return. Otherwise take
the most recent available version, and default to the package's
version if there are no avaialble versions.
+
+ TODO: In many cases we probably want to look for installed
+ versions of each package and use an installed version
+ if we can link to it. The policy implemented here will
+ tend to rebuild a lot of stuff becasue it will prefer
+ a compiler in the spec to any compiler already-
+ installed things were built with. There is likely
+ some better policy that finds some middle ground
+ between these two extremes.
"""
# return if already concrete.
if spec.versions.concrete:
@@ -89,26 +99,42 @@ class DefaultConcretizer(object):
def concretize_compiler(self, spec):
- """Currently just sets the compiler to gcc or throws an exception
- if the compiler is set to something else.
-
- TODO: implement below description.
-
- If the spec already has a compiler, we're done. If not, then
- take the compiler used for the nearest ancestor with a concrete
- compiler, or use the system default if there is no ancestor
- with a compiler.
+ """If the spec already has a compiler, we're done. If not, then take
+ the compiler used for the nearest ancestor with a compiler
+ spec and use that. If the ancestor's compiler is not
+ concrete, then give it a valid version. If there is no
+ ancestor with a compiler, use the system default compiler.
Intuition: Use the system default if no package that depends on
this one has a strict compiler requirement. Otherwise, try to
build with the compiler that will be used by libraries that
link to this one, to maximize compatibility.
"""
- if spec.compiler and spec.compiler.concrete:
- if spec.compiler != spack.compilers.default_compiler():
- raise spack.spec.UnknownCompilerError(str(spec.compiler))
- else:
- spec.compiler = spack.compilers.default_compiler()
+ all_compilers = spack.compilers.all_compilers()
+
+ if (spec.compiler and
+ spec.compiler.concrete and
+ spec.compiler in all_compilers):
+ return
+
+ try:
+ nearest = next(p for p in spec.preorder_traversal(direction='parents')
+ if p.compiler is not None).compiler
+
+ if not nearest in all_compilers:
+ # Take the newest compiler that saisfies the spec
+ matches = sorted(spack.compilers.find(nearest))
+ if not matches:
+ raise UnavailableCompilerVersionError(nearest)
+
+ # copy concrete version into nearest spec
+ nearest.versions = matches[-1].versions.copy()
+ assert(nearest.concrete)
+
+ spec.compiler = nearest.copy()
+
+ except StopIteration:
+ spec.compiler = spack.compilers.default_compiler().copy()
def choose_provider(self, spec, providers):
@@ -123,3 +149,12 @@ class DefaultConcretizer(object):
first_key = sorted(index.keys())[0]
latest_version = sorted(index[first_key])[-1]
return latest_version
+
+
+class UnavailableCompilerVersionError(spack.error.SpackError):
+ """Raised when there is no available compiler that satisfies a
+ compiler spec."""
+ def __init__(self, compiler_spec):
+ super(UnavailableCompilerVersionError, self).__init__(
+ "No available compiler version matches '%s'" % compiler_spec,
+ "Run 'spack compilers' to see available compiler Options.")
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
new file mode 100644
index 0000000000..00ff4313a2
--- /dev/null
+++ b/lib/spack/spack/config.py
@@ -0,0 +1,492 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""This module implements Spack's configuration file handling.
+
+Configuration file scopes
+===============================
+
+When Spack runs, it pulls configuration data from several config
+files, much like bash shells. In Spack, there are two configuration
+scopes:
+
+ 1. ``site``: Spack loads site-wide configuration options from
+ ``$(prefix)/etc/spackconfig``.
+
+ 2. ``user``: Spack next loads per-user configuration options from
+ ~/.spackconfig.
+
+If user options have the same names as site options, the user options
+take precedence.
+
+
+Configuration file format
+===============================
+
+Configuration files are formatted using .gitconfig syntax, which is
+much like Windows .INI format. This format is implemented by Python's
+ConfigParser class, and it's easy to read and versatile.
+
+The file is divided into sections, like this ``compiler`` section::
+
+ [compiler]
+ cc = /usr/bin/gcc
+
+In each section there are options (cc), and each option has a value
+(/usr/bin/gcc).
+
+Borrowing from git, we also allow named sections, e.g.:
+
+ [compiler "gcc@4.7.3"]
+ cc = /usr/bin/gcc
+
+This is a compiler section, but it's for the specific compiler,
+``gcc@4.7.3``. ``gcc@4.7.3`` is the name.
+
+
+Keys
+===============================
+
+Together, the section, name, and option, separated by periods, are
+called a ``key``. Keys can be used on the command line to set
+configuration options explicitly (this is also borrowed from git).
+
+For example, to change the C compiler used by gcc@4.7.3, you could do
+this:
+
+ spack config compiler.gcc@4.7.3.cc /usr/local/bin/gcc
+
+That will create a named compiler section in the user's .spackconfig
+like the one shown above.
+"""
+import os
+import re
+import inspect
+import ConfigParser as cp
+from collections import OrderedDict
+
+from llnl.util.lang import memoized
+
+import spack.error
+
+__all__ = [
+ 'SpackConfigParser', 'get_config', 'SpackConfigurationError',
+ 'InvalidConfigurationScopeError', 'InvalidSectionNameError',
+ 'ReadOnlySpackConfigError', 'ConfigParserError', 'NoOptionError',
+ 'NoSectionError']
+
+_named_section_re = r'([^ ]+) "([^"]+)"'
+
+"""Names of scopes and their corresponding configuration files."""
+_scopes = OrderedDict({
+ 'site' : os.path.join(spack.etc_path, 'spackconfig'),
+ 'user' : os.path.expanduser('~/.spackconfig')
+})
+
+_field_regex = r'^([\w-]*)' \
+ r'(?:\.(.*(?=.)))?' \
+ r'(?:\.([\w-]+))?$'
+
+_section_regex = r'^([\w-]*)\s*' \
+ r'\"([^"]*\)\"$'
+
+
+# Cache of configs -- we memoize this for performance.
+_config = {}
+
+def get_config(scope=None, **kwargs):
+ """Get a Spack configuration object, which can be used to set options.
+
+ With no arguments, this returns a SpackConfigParser with config
+ options loaded from all config files. This is how client code
+ should read Spack configuration options.
+
+ Optionally, a scope parameter can be provided. Valid scopes
+ are ``site`` and ``user``. If a scope is provided, only the
+ options from that scope's configuration file are loaded. The
+ caller can set or unset options, then call ``write()`` on the
+ config object to write it back out to the original config file.
+
+ By default, this will cache configurations and return the last
+ read version of the config file. If the config file is
+ modified and you need to refresh, call get_config with the
+ refresh=True keyword argument. This will force all files to be
+ re-read.
+ """
+ refresh = kwargs.get('refresh', False)
+ if refresh:
+ _config.clear()
+
+ if scope not in _config:
+ if scope is None:
+ _config[scope] = SpackConfigParser([path for path in _scopes.values()])
+ elif scope not in _scopes:
+ raise UnknownConfigurationScopeError(scope)
+ else:
+ _config[scope] = SpackConfigParser(_scopes[scope])
+
+ return _config[scope]
+
+
+def get_filename(scope):
+ """Get the filename for a particular config scope."""
+ if not scope in _scopes:
+ raise UnknownConfigurationScopeError(scope)
+ return _scopes[scope]
+
+
+def _parse_key(key):
+ """Return the section, name, and option the field describes.
+ Values are returned in a 3-tuple.
+
+ e.g.:
+ The field name ``compiler.gcc@4.7.3.cc`` refers to the 'cc' key
+ in a section that looks like this:
+
+ [compiler "gcc@4.7.3"]
+ cc = /usr/local/bin/gcc
+
+ * The section is ``compiler``
+ * The name is ``gcc@4.7.3``
+ * The key is ``cc``
+ """
+ match = re.search(_field_regex, key)
+ if match:
+ return match.groups()
+ else:
+ raise InvalidSectionNameError(key)
+
+
+def _make_section_name(section, name):
+ if not name:
+ return section
+ return '%s "%s"' % (section, name)
+
+
+def _autokey(fun):
+ """Allow a function to be called with a string key like
+ 'compiler.gcc.cc', or with the section, name, and option
+ separated. Function should take at least three args, e.g.:
+
+ fun(self, section, name, option, [...])
+
+ This will allow the function above to be called normally or
+ with a string key, e.g.:
+
+ fun(self, key, [...])
+ """
+ argspec = inspect.getargspec(fun)
+ fun_nargs = len(argspec[0])
+
+ def string_key_func(*args):
+ nargs = len(args)
+ if nargs == fun_nargs - 2:
+ section, name, option = _parse_key(args[1])
+ return fun(args[0], section, name, option, *args[2:])
+
+ elif nargs == fun_nargs:
+ return fun(*args)
+
+ else:
+ raise TypeError(
+ "%s takes %d or %d args (found %d)."
+ % (fun.__name__, fun_nargs - 2, fun_nargs, len(args)))
+ return string_key_func
+
+
+
+class SpackConfigParser(cp.RawConfigParser):
+ """Slightly modified from Python's raw config file parser to accept
+ leading whitespace and preserve comments.
+ """
+ # Slightly modify Python option expressions to allow leading whitespace
+ OPTCRE = re.compile(r'\s*' + cp.RawConfigParser.OPTCRE.pattern)
+ OPTCRE_NV = re.compile(r'\s*' + cp.RawConfigParser.OPTCRE_NV.pattern)
+
+ def __init__(self, file_or_files):
+ cp.RawConfigParser.__init__(
+ self, dict_type=OrderedDict, allow_no_value=True)
+
+ if isinstance(file_or_files, basestring):
+ self.read([file_or_files])
+ self.filename = file_or_files
+
+ else:
+ self.read(file_or_files)
+ self.filename = None
+
+
+ @_autokey
+ def set_value(self, section, name, option, value):
+ """Set the value for a key. If the key is in a section or named
+ section that does not yet exist, add that section.
+ """
+ sn = _make_section_name(section, name)
+ if not self.has_section(sn):
+ self.add_section(sn)
+
+ # Allow valueless config options to be set like this:
+ # spack config set mirror https://foo.bar.com
+ #
+ # Instead of this, which parses incorrectly:
+ # spack config set mirror.https://foo.bar.com
+ #
+ if option is None:
+ option = value
+ value = None
+
+ self.set(sn, option, value)
+
+
+ @_autokey
+ def get_value(self, section, name, option):
+ """Get the value for a key. Raises NoOptionError or NoSectionError if
+ the key is not present."""
+ sn = _make_section_name(section, name)
+
+ try:
+ if not option:
+ # TODO: format this better
+ return self.items(sn)
+
+ return self.get(sn, option)
+
+ # Wrap ConfigParser exceptions in SpackExceptions
+ except cp.NoOptionError, e: raise NoOptionError(e)
+ except cp.NoSectionError, e: raise NoSectionError(e)
+ except cp.Error, e: raise ConfigParserError(e)
+
+
+ @_autokey
+ def has_value(self, section, name, option):
+ """Return whether the configuration file has a value for a
+ particular key."""
+ sn = _make_section_name(section, name)
+ return self.has_option(sn, option)
+
+
+ def get_section_names(self, sectype):
+ """Get all named sections with the specified type.
+ A named section looks like this:
+
+ [compiler "gcc@4.7"]
+
+ Names of sections are returned as a list, e.g.:
+
+ ['gcc@4.7', 'intel@12.3', 'pgi@4.2']
+
+ You can get items in the sections like this:
+ """
+ sections = []
+ for secname in self.sections():
+ match = re.match(_named_section_re, secname)
+ if match:
+ t, name = match.groups()
+ if t == sectype:
+ sections.append(name)
+ return sections
+
+
+ def write(self, path_or_fp=None):
+ """Write this configuration out to a file.
+
+ If called with no arguments, this will write the
+ configuration out to the file from which it was read. If
+ this config was read from multiple files, e.g. site
+ configuration and then user configuration, write will
+ simply raise an error.
+
+ If called with a path or file object, this will write the
+ configuration out to the supplied path or file object.
+ """
+ if path_or_fp is None:
+ if not self.filename:
+ raise ReadOnlySpackConfigError()
+ path_or_fp = self.filename
+
+ if isinstance(path_or_fp, basestring):
+ path_or_fp = open(path_or_fp, 'w')
+
+ self._write(path_or_fp)
+
+
+ def _read(self, fp, fpname):
+ """This is a copy of Python 2.7's _read() method, with support for
+ continuation lines removed.
+ """
+ cursect = None # None, or a dictionary
+ optname = None
+ lineno = 0
+ comment = 0
+ e = None # None, or an exception
+ while True:
+ line = fp.readline()
+ if not line:
+ break
+ lineno = lineno + 1
+ # comment or blank line?
+ if ((line.strip() == '' or line[0] in '#;') or
+ (line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR")):
+ self._sections["comment-%d" % comment] = line
+ comment += 1
+ continue
+ # a section header or option header?
+ else:
+ # is it a section header?
+ mo = self.SECTCRE.match(line)
+ if mo:
+ sectname = mo.group('header')
+ if sectname in self._sections:
+ cursect = self._sections[sectname]
+ elif sectname == cp.DEFAULTSECT:
+ cursect = self._defaults
+ else:
+ cursect = self._dict()
+ cursect['__name__'] = sectname
+ self._sections[sectname] = cursect
+ # So sections can't start with a continuation line
+ optname = None
+ # no section header in the file?
+ elif cursect is None:
+ raise cp.MissingSectionHeaderError(fpname, lineno, line)
+ # an option line?
+ else:
+ mo = self._optcre.match(line)
+ if mo:
+ optname, vi, optval = mo.group('option', 'vi', 'value')
+ optname = self.optionxform(optname.rstrip())
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ if vi in ('=', ':') and ';' in optval:
+ # ';' is a comment delimiter only if it follows
+ # a spacing character
+ pos = optval.find(';')
+ if pos != -1 and optval[pos-1].isspace():
+ optval = optval[:pos]
+ optval = optval.strip()
+ # allow empty values
+ if optval == '""':
+ optval = ''
+ cursect[optname] = [optval]
+ else:
+ # valueless option handling
+ cursect[optname] = optval
+ else:
+ # a non-fatal parsing error occurred. set up the
+ # exception but keep going. the exception will be
+ # raised at the end of the file and will contain a
+ # list of all bogus lines
+ if not e:
+ e = cp.ParsingError(fpname)
+ e.append(lineno, repr(line))
+ # if any parsing errors occurred, raise an exception
+ if e:
+ raise e
+
+ # join the multi-line values collected while reading
+ all_sections = [self._defaults]
+ all_sections.extend(self._sections.values())
+ for options in all_sections:
+ # skip comments
+ if isinstance(options, basestring):
+ continue
+
+ for name, val in options.items():
+ if isinstance(val, list):
+ options[name] = '\n'.join(val)
+
+
+ def _write(self, fp):
+ """Write an .ini-format representation of the configuration state.
+
+ This is taken from the default Python 2.7 source. It writes 4
+ spaces at the beginning of lines instead of no leading space.
+ """
+ if self._defaults:
+ fp.write("[%s]\n" % cp.DEFAULTSECT)
+ for (key, value) in self._defaults.items():
+ fp.write(" %s = %s\n" % (key, str(value).replace('\n', '\n\t')))
+ fp.write("\n")
+
+ for section in self._sections:
+ # Handles comments and blank lines.
+ if isinstance(self._sections[section], basestring):
+ fp.write(self._sections[section])
+ continue
+
+ else:
+ # Allow leading whitespace
+ fp.write("[%s]\n" % section)
+ for (key, value) in self._sections[section].items():
+ if key == "__name__":
+ continue
+ if (value is not None) or (self._optcre == self.OPTCRE):
+ key = " = ".join((key, str(value).replace('\n', '\n\t')))
+ fp.write(" %s\n" % (key))
+
+
+class SpackConfigurationError(spack.error.SpackError):
+ def __init__(self, *args):
+ super(SpackConfigurationError, self).__init__(*args)
+
+
+class InvalidConfigurationScopeError(SpackConfigurationError):
+ def __init__(self, scope):
+ super(InvalidConfigurationScopeError, self).__init__(
+ "Invalid configuration scope: '%s'" % scope,
+ "Options are: %s" % ", ".join(*_scopes.values()))
+
+
+class InvalidSectionNameError(SpackConfigurationError):
+ """Raised when the name for a section is invalid."""
+ def __init__(self, name):
+ super(InvalidSectionNameError, self).__init__(
+ "Invalid section specifier: '%s'" % name)
+
+
+class ReadOnlySpackConfigError(SpackConfigurationError):
+ """Raised when user attempts to write to a config read from multiple files."""
+ def __init__(self):
+ super(ReadOnlySpackConfigError, self).__init__(
+ "Can only write to a single-file SpackConfigParser")
+
+
+class ConfigParserError(SpackConfigurationError):
+ """Wrapper for the Python ConfigParser's errors"""
+ def __init__(self, error):
+ super(ConfigParserError, self).__init__(str(error))
+ self.error = error
+
+
+class NoOptionError(ConfigParserError):
+ """Wrapper for ConfigParser NoOptionError"""
+ def __init__(self, error):
+ super(NoOptionError, self).__init__(error)
+
+
+class NoSectionError(ConfigParserError):
+ """Wrapper for ConfigParser NoOptionError"""
+ def __init__(self, error):
+ super(NoSectionError, self).__init__(error)
diff --git a/lib/spack/spack/globals.py b/lib/spack/spack/globals.py
deleted file mode 100644
index 9fc40845b0..0000000000
--- a/lib/spack/spack/globals.py
+++ /dev/null
@@ -1,135 +0,0 @@
-##############################################################################
-# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
-# Produced at the Lawrence Livermore National Laboratory.
-#
-# This file is part of Spack.
-# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
-# LLNL-CODE-647188
-#
-# For details, see https://scalability-llnl.github.io/spack
-# Please also see the LICENSE file for our notice and the LGPL.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License (as published by
-# the Free Software Foundation) version 2.1 dated February 1999.
-#
-# This program is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
-# conditions of the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program; if not, write to the Free Software Foundation,
-# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##############################################################################
-import os
-import tempfile
-
-from llnl.util.filesystem import *
-
-from spack.version import Version
-from spack.util.executable import *
-from spack.directory_layout import SpecHashDirectoryLayout
-from spack.concretize import DefaultConcretizer
-from spack.packages import PackageDB
-
-# This lives in $prefix/lib/spac/spack/__file__
-prefix = ancestor(__file__, 4)
-
-# The spack script itself
-spack_file = join_path(prefix, "bin", "spack")
-
-# spack directory hierarchy
-lib_path = join_path(prefix, "lib", "spack")
-build_env_path = join_path(lib_path, "env")
-module_path = join_path(lib_path, "spack")
-compilers_path = join_path(module_path, "compilers")
-test_path = join_path(module_path, "test")
-
-var_path = join_path(prefix, "var", "spack")
-stage_path = join_path(var_path, "stage")
-
-install_path = join_path(prefix, "opt")
-
-#
-# Set up the packages database.
-#
-packages_path = join_path(var_path, "packages")
-db = PackageDB(packages_path)
-
-#
-# This is the path to mock packages used by spack for testing.
-#
-mock_packages_path = join_path(var_path, "mock_packages")
-
-#
-# This controls how spack lays out install prefixes and
-# stage directories.
-#
-install_layout = SpecHashDirectoryLayout(install_path, prefix_size=6)
-
-#
-# This controls how things are concretized in spack.
-# Replace it with a subclass if you want different
-# policies.
-#
-concretizer = DefaultConcretizer()
-
-# Version information
-spack_version = Version("1.0")
-
-# User's editor from the environment
-editor = Executable(os.environ.get("EDITOR", ""))
-
-# 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()
-if _default_tmp != os.getcwd():
- tmp_dirs.append(os.path.join(_default_tmp, 'spack-stage'))
-tmp_dirs.append('/nfs/tmp2/%u/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
-
-#
-# Places to download tarballs from. Examples:
-#
-# For a local directory:
-# mirrors = ['file:///Users/gamblin2/spack-mirror']
-#
-# For a website:
-# mirrors = ['http://spackports.org/spack-mirror/']
-#
-# For no mirrors:
-# mirrors = []
-#
-mirrors = []
-
diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py
index 8d91e4f86d..974401e1aa 100644
--- a/lib/spack/spack/multimethod.py
+++ b/lib/spack/spack/multimethod.py
@@ -117,7 +117,7 @@ class SpecMultiMethod(object):
or if there is none, then raise a NoSuchMethodError.
"""
for spec, method in self.method_list:
- if spec.satisfies(package_self.spec):
+ if package_self.spec.satisfies(spec):
return method(package_self, *args, **kwargs)
if self.default:
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index 59d7ce7907..ef8b639adf 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -626,6 +626,7 @@ class Package(object):
"""
# whether to keep the prefix on failure. Default is to destroy it.
keep_prefix = kwargs.get('keep_prefix', False)
+ keep_stage = kwargs.get('keep_stage', False)
ignore_deps = kwargs.get('ignore_deps', False)
if not self.spec.concrete:
@@ -650,29 +651,31 @@ class Package(object):
raise InstallError("Unable to fork build process: %s" % e)
if pid == 0:
- tty.msg("Building %s." % self.name)
+ try:
+ tty.msg("Building %s." % self.name)
- # create the install directory (allow the layout to handle
- # this in case it needs to add extra files)
- spack.install_layout.make_path_for_spec(self.spec)
+ # create the install directory. The install layout
+ # handles this in case so that it can use whatever
+ # package naming scheme it likes.
+ spack.install_layout.make_path_for_spec(self.spec)
- # Set up process's build environment before running install.
- build_env.set_build_environment_variables(self)
- build_env.set_module_variables_for_package(self)
+ # Set up process's build environment before running install.
+ build_env.set_compiler_environment_variables(self)
+ build_env.set_build_environment_variables(self)
+ build_env.set_module_variables_for_package(self)
- try:
- # Subclasses implement install() to do the build &
- # install work.
+ # Subclasses implement install() to do the real work.
self.install(self.spec, self.prefix)
+ # Ensure that something was actually installed.
if not os.listdir(self.prefix):
raise InstallError(
"Install failed for %s. Nothing was installed!"
% self.name)
# On successful install, remove the stage.
- # Leave if there is an error
- self.stage.destroy()
+ if not keep_stage:
+ self.stage.destroy()
tty.msg("Successfully installed %s" % self.name)
print_pkg(self.prefix)
@@ -690,7 +693,11 @@ class Package(object):
"Spack will think this package is installed." +
"Manually remove this directory to fix:",
self.prefix)
- raise
+
+ # 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)
# Parent process just waits for the child to complete. If the
# child exited badly, assume it already printed an appropriate
@@ -724,16 +731,16 @@ class Package(object):
force = kwargs.get('force', False)
if not self.installed:
- raise InstallError(self.name + " is not installed.")
+ raise InstallError(str(self.spec) + " is not installed.")
if not force:
deps = self.installed_dependents
if deps: raise InstallError(
"Cannot uninstall %s. The following installed packages depend on it: %s"
- % (self.name, deps))
+ % (self.spec, deps))
self.remove_prefix()
- tty.msg("Successfully uninstalled %s." % self.name)
+ tty.msg("Successfully uninstalled %s." % self.spec)
def do_clean(self):
diff --git a/lib/spack/spack/packages.py b/lib/spack/spack/packages.py
index 08ded5cdbb..00834c95d5 100644
--- a/lib/spack/spack/packages.py
+++ b/lib/spack/spack/packages.py
@@ -22,10 +22,8 @@
# 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 string
import inspect
import glob
import imp
@@ -34,10 +32,10 @@ import llnl.util.tty as tty
from llnl.util.filesystem import join_path
from llnl.util.lang import memoized
-import spack
import spack.error
import spack.spec
from spack.virtual import ProviderIndex
+from spack.util.naming import mod_to_class, validate_module_name
# Name of module under which packages are imported
_imported_packages_module = 'spack.packages'
@@ -45,42 +43,6 @@ _imported_packages_module = 'spack.packages'
# Name of the package file inside a package directory
_package_file_name = 'package.py'
-# Valid package names can contain '-' but can't start with it.
-valid_package_re = r'^\w[\w-]*$'
-
-# Don't allow consecutive [_-] in package names
-invalid_package_re = r'[_-][_-]+'
-
-
-def valid_package_name(pkg_name):
- """Return whether the pkg_name is valid for use in Spack."""
- return (re.match(valid_package_re, pkg_name) and
- not re.search(invalid_package_re, pkg_name))
-
-
-def validate_package_name(pkg_name):
- """Raise an exception if pkg_name is not valid."""
- if not valid_package_name(pkg_name):
- raise InvalidPackageNameError(pkg_name)
-
-
-def class_name_for_package_name(pkg_name):
- """Get a name for the class the package file should contain. Note that
- conflicts don't matter because the classes are in different modules.
- """
- validate_package_name(pkg_name)
-
- class_name = pkg_name.replace('_', '-')
- class_name = string.capwords(class_name, '-')
- class_name = class_name.replace('-', '')
-
- # If a class starts with a number, prefix it with Number_ to make it a valid
- # Python class name.
- if re.match(r'^[0-9]', class_name):
- class_name = "Num_%s" % class_name
-
- return class_name
-
def _autospec(function):
"""Decorator that automatically converts the argument of a single-arg
@@ -114,6 +76,7 @@ class PackageDB(object):
@_autospec
def get_installed(self, spec):
+ """Get all the installed specs that satisfy the provided spec constraint."""
return [s for s in self.installed_package_specs() if s.satisfies(spec)]
@@ -143,7 +106,7 @@ class PackageDB(object):
package doesn't exist yet, so callers will need to ensure
the package exists before importing.
"""
- validate_package_name(pkg_name)
+ validate_module_name(pkg_name)
pkg_dir = self.dirname_for_package_name(pkg_name)
return join_path(pkg_dir, _package_file_name)
@@ -200,7 +163,7 @@ class PackageDB(object):
else:
raise UnknownPackageError(pkg_name)
- class_name = class_name_for_package_name(pkg_name)
+ class_name = mod_to_class(pkg_name)
try:
module_name = _imported_packages_module + '.' + pkg_name
module = imp.load_source(module_name, file_path)
@@ -259,14 +222,6 @@ class PackageDB(object):
out.write('}\n')
-class InvalidPackageNameError(spack.error.SpackError):
- """Raised when we encounter a bad package name."""
- def __init__(self, name):
- super(InvalidPackageNameError, self).__init__(
- "Invalid package name: " + name)
- self.name = name
-
-
class UnknownPackageError(spack.error.SpackError):
"""Raised when we encounter a package spack doesn't have."""
def __init__(self, name):
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index 6416ff9487..1e2da10dcc 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -102,8 +102,7 @@ from llnl.util.tty.color import *
import spack
import spack.parse
import spack.error
-import spack.compilers
-import spack.compilers.gcc
+import spack.compilers as compilers
from spack.version import *
from spack.util.string import *
@@ -169,36 +168,71 @@ def colorize_spec(spec):
@key_ordering
-class Compiler(object):
- """The Compiler field represents the compiler or range of compiler
- versions that a package should be built with. Compilers have a
+class CompilerSpec(object):
+ """The CompilerSpec field represents the compiler or range of compiler
+ versions that a package should be built with. CompilerSpecs have a
name and a version list. """
- def __init__(self, name, version=None):
- self.name = name
- self.versions = VersionList()
- if version:
- self.versions.add(version)
+ def __init__(self, *args):
+ nargs = len(args)
+ if nargs == 1:
+ arg = args[0]
+ # If there is one argument, it's either another CompilerSpec
+ # to copy or a string to parse
+ if isinstance(arg, basestring):
+ c = SpecParser().parse_compiler(arg)
+ self.name = c.name
+ self.versions = c.versions
+
+ elif isinstance(arg, CompilerSpec):
+ self.name = arg.name
+ self.versions = arg.versions.copy()
+
+ else:
+ raise TypeError(
+ "Can only build CompilerSpec from string or CompilerSpec." +
+ " Found %s" % type(arg))
+
+ elif nargs == 2:
+ name, version = args
+ self.name = name
+ self.versions = VersionList()
+ self.versions.add(ver(version))
+
+ else:
+ raise TypeError(
+ "__init__ takes 1 or 2 arguments. (%d given)" % nargs)
def _add_version(self, version):
self.versions.add(version)
+ def _autospec(self, compiler_spec_like):
+ if isinstance(compiler_spec_like, CompilerSpec):
+ return compiler_spec_like
+ return CompilerSpec(compiler_spec_like)
+
+
def satisfies(self, other):
+ other = self._autospec(other)
return (self.name == other.name and
- self.versions.overlaps(other.versions))
+ self.versions.satisfies(other.versions))
def constrain(self, other):
- if not self.satisfies(other):
- raise UnsatisfiableCompilerSpecError(self, other)
+ other = self._autospec(other)
+
+ # ensure that other will actually constrain this spec.
+ if not other.satisfies(self):
+ raise UnsatisfiableCompilerSpecError(other, self)
self.versions.intersect(other.versions)
@property
def concrete(self):
- """A Compiler spec is concrete if its versions are concrete."""
+ """A CompilerSpec is concrete if its versions are concrete and there
+ is an available compiler with the right version."""
return self.versions.concrete
@@ -210,7 +244,8 @@ class Compiler(object):
def copy(self):
- clone = Compiler(self.name)
+ clone = CompilerSpec.__new__(CompilerSpec)
+ clone.name = self.name
clone.versions = self.versions.copy()
return clone
@@ -226,6 +261,9 @@ class Compiler(object):
out += "@%s" % vlist
return out
+ def __repr__(self):
+ return str(self)
+
@key_ordering
class Variant(object):
@@ -332,7 +370,7 @@ class Spec(object):
def _set_compiler(self, compiler):
"""Called by the parser to set the compiler."""
- if self.compiler: raise DuplicateCompilerError(
+ if self.compiler: raise DuplicateCompilerSpecError(
"Spec for '%s' cannot have two compilers." % self.name)
self.compiler = compiler
@@ -361,14 +399,14 @@ class Spec(object):
"""
if not self.dependents:
return self
- else:
- # If the spec has multiple dependents, ensure that they all
- # lead to the same place. Spack shouldn't deal with any DAGs
- # with multiple roots, so something's wrong if we find one.
- depiter = iter(self.dependents.values())
- first_root = next(depiter).root
- assert(all(first_root is d.root for d in depiter))
- return first_root
+
+ # If the spec has multiple dependents, ensure that they all
+ # lead to the same place. Spack shouldn't deal with any DAGs
+ # with multiple roots, so something's wrong if we find one.
+ depiter = iter(self.dependents.values())
+ first_root = next(depiter).root
+ assert(all(first_root is d.root for d in depiter))
+ return first_root
@property
@@ -428,17 +466,28 @@ class Spec(object):
root [=True]
If false, this won't yield the root node, just its descendents.
+
+ direction [=children|parents]
+ If 'children', does a traversal of this spec's children. If
+ 'parents', traverses upwards in the DAG towards the root.
+
"""
depth = kwargs.get('depth', False)
key_fun = kwargs.get('key', id)
yield_root = kwargs.get('root', True)
cover = kwargs.get('cover', 'nodes')
+ direction = kwargs.get('direction', 'children')
cover_values = ('nodes', 'edges', 'paths')
if cover not in cover_values:
raise ValueError("Invalid value for cover: %s. Choices are %s"
% (cover, ",".join(cover_values)))
+ direction_values = ('children', 'parents')
+ if direction not in direction_values:
+ raise ValueError("Invalid value for direction: %s. Choices are %s"
+ % (direction, ",".join(direction_values)))
+
if visited is None:
visited = set()
@@ -452,9 +501,13 @@ class Spec(object):
else:
if yield_root or d > 0: yield result
+ successors = self.dependencies
+ if direction == 'parents':
+ successors = self.dependents
+
visited.add(key)
- for name in sorted(self.dependencies):
- child = self.dependencies[name]
+ for name in sorted(successors):
+ child = successors[name]
for elt in child.preorder_traversal(visited, d+1, **kwargs):
yield elt
@@ -763,22 +816,22 @@ class Spec(object):
def validate_names(self):
"""This checks that names of packages and compilers in this spec are real.
If they're not, it will raise either UnknownPackageError or
- UnknownCompilerError.
+ UnsupportedCompilerError.
"""
for spec in self.preorder_traversal():
# Don't get a package for a virtual name.
if not spec.virtual:
spack.db.get(spec.name)
- # validate compiler name in addition to the package name.
+ # validate compiler in addition to the package name.
if spec.compiler:
- compiler_name = spec.compiler.name
- if not spack.compilers.supported(compiler_name):
- raise UnknownCompilerError(compiler_name)
+ if not compilers.supported(spec.compiler):
+ raise UnsupportedCompilerError(spec.compiler.name)
def constrain(self, other, **kwargs):
other = self._autospec(other)
+ constrain_deps = kwargs.get('deps', True)
if not self.name == other.name:
raise UnsatisfiableSpecNameError(self.name, other.name)
@@ -806,7 +859,7 @@ class Spec(object):
self.variants.update(other.variants)
self.architecture = self.architecture or other.architecture
- if kwargs.get('deps', True):
+ if constrain_deps:
self._constrain_dependencies(other)
@@ -818,8 +871,8 @@ class Spec(object):
# TODO: might want more detail than this, e.g. specific deps
# in violation. if this becomes a priority get rid of this
# check and be more specici about what's wrong.
- if not self.satisfies_dependencies(other):
- raise UnsatisfiableDependencySpecError(self, other)
+ if not other.satisfies_dependencies(self):
+ raise UnsatisfiableDependencySpecError(other, self)
# Handle common first-order constraints directly
for name in self.common_dependencies(other):
@@ -863,28 +916,28 @@ class Spec(object):
def satisfies(self, other, **kwargs):
other = self._autospec(other)
+ satisfy_deps = kwargs.get('deps', True)
# First thing we care about is whether the name matches
if self.name != other.name:
return False
- # This function simplifies null checking below
- def check(attribute, op):
- s = getattr(self, attribute)
- o = getattr(other, attribute)
- return not s or not o or op(s,o)
-
- # All these attrs have satisfies criteria of their own
- for attr in ('versions', 'variants', 'compiler'):
- if not check(attr, lambda s, o: s.satisfies(o)):
+ # All these attrs have satisfies criteria of their own,
+ # but can be None to indicate no constraints.
+ for s, o in ((self.versions, other.versions),
+ (self.variants, other.variants),
+ (self.compiler, other.compiler)):
+ if s and o and not s.satisfies(o):
return False
- # Architecture is just a string
- # TODO: inviestigate making an Architecture class for symmetry
- if not check('architecture', lambda s,o: s == o):
+ # Architecture satisfaction is currently just string equality.
+ # Can be None for unconstrained, though.
+ if (self.architecture and other.architecture and
+ self.architecture != other.architecture):
return False
- if kwargs.get('deps', True):
+ # If we need to descend into dependencies, do it, otherwise we're done.
+ if satisfy_deps:
return self.satisfies_dependencies(other)
else:
return True
@@ -1188,6 +1241,11 @@ class SpecParser(spack.parse.Parser):
return specs
+ def parse_compiler(self, text):
+ self.setup(text)
+ return self.compiler()
+
+
def spec(self):
"""Parse a spec out of the input. If a spec is supplied, then initialize
and return it instead of creating a new one."""
@@ -1279,7 +1337,10 @@ class SpecParser(spack.parse.Parser):
def compiler(self):
self.expect(ID)
self.check_identifier()
- compiler = Compiler(self.token.value)
+
+ compiler = CompilerSpec.__new__(CompilerSpec)
+ compiler.name = self.token.value
+ compiler.versions = VersionList()
if self.accept(AT):
vlist = self.version_list()
for version in vlist:
@@ -1359,17 +1420,17 @@ class DuplicateVariantError(SpecError):
super(DuplicateVariantError, self).__init__(message)
-class DuplicateCompilerError(SpecError):
+class DuplicateCompilerSpecError(SpecError):
"""Raised when the same compiler occurs in a spec twice."""
def __init__(self, message):
- super(DuplicateCompilerError, self).__init__(message)
+ super(DuplicateCompilerSpecError, self).__init__(message)
-class UnknownCompilerError(SpecError):
+class UnsupportedCompilerError(SpecError):
"""Raised when the user asks for a compiler spack doesn't know about."""
def __init__(self, compiler_name):
- super(UnknownCompilerError, self).__init__(
- "Unknown compiler: %s" % compiler_name)
+ super(UnsupportedCompilerError, self).__init__(
+ "The '%s' compiler is not yet supported." % compiler_name)
class DuplicateArchitectureError(SpecError):
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index df2fe83dac..8574df71e9 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -145,7 +145,7 @@ class Stage(object):
back to making the stage inside spack.stage_path.
"""
# Create the top-level stage directory
- spack.mkdirp(spack.stage_path)
+ mkdirp(spack.stage_path)
self._cleanup_dead_links()
# If this is a named stage, then construct a named path.
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index 5aac710119..5442189c2e 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -44,7 +44,8 @@ test_names = ['versions',
'concretize',
'multimethod',
'install',
- 'package_sanity']
+ 'package_sanity',
+ 'config']
def list_tests():
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 62e2732749..6ad2ef29d8 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -25,7 +25,7 @@
import unittest
import spack
-from spack.spec import Spec
+from spack.spec import Spec, CompilerSpec
from spack.test.mock_packages_test import *
class ConcretizeTest(MockPackagesTest):
@@ -163,3 +163,15 @@ class ConcretizeTest(MockPackagesTest):
spec = Spec('indirect_mpich')
spec.normalize()
spec.concretize()
+
+
+ def test_compiler_inheritance(self):
+ spec = Spec('mpileaks')
+ spec.normalize()
+
+ spec['dyninst'].compiler = CompilerSpec('clang')
+ spec.concretize()
+
+ # TODO: not exactly the syntax I would like.
+ self.assertTrue(spec['libdwarf'].compiler.satisfies('clang'))
+ self.assertTrue(spec['libelf'].compiler.satisfies('clang'))
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
new file mode 100644
index 0000000000..c676e9a35b
--- /dev/null
+++ b/lib/spack/spack/test/config.py
@@ -0,0 +1,69 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import unittest
+import shutil
+import os
+from tempfile import mkdtemp
+
+from spack.config import *
+
+
+class ConfigTest(unittest.TestCase):
+
+ @classmethod
+ def setUp(cls):
+ cls.tmp_dir = mkdtemp('.tmp', 'spack-config-test-')
+
+
+ @classmethod
+ def tearDown(cls):
+ shutil.rmtree(cls.tmp_dir, True)
+
+
+ def get_path(self):
+ return os.path.join(ConfigTest.tmp_dir, "spackconfig")
+
+
+ def test_write_key(self):
+ config = SpackConfigParser(self.get_path())
+ config.set_value('compiler.cc', 'a')
+ config.set_value('compiler.cxx', 'b')
+ config.set_value('compiler', 'gcc@4.7.3', 'cc', 'c')
+ config.set_value('compiler', 'gcc@4.7.3', 'cxx', 'd')
+ config.write()
+
+ config = SpackConfigParser(self.get_path())
+
+ self.assertEqual(config.get_value('compiler.cc'), 'a')
+ self.assertEqual(config.get_value('compiler.cxx'), 'b')
+ self.assertEqual(config.get_value('compiler', 'gcc@4.7.3', 'cc'), 'c')
+ self.assertEqual(config.get_value('compiler', 'gcc@4.7.3', 'cxx'), 'd')
+
+ self.assertEqual(config.get_value('compiler', None, 'cc'), 'a')
+ self.assertEqual(config.get_value('compiler', None, 'cxx'), 'b')
+ self.assertEqual(config.get_value('compiler.gcc@4.7.3.cc'), 'c')
+ self.assertEqual(config.get_value('compiler.gcc@4.7.3.cxx'), 'd')
+
+ self.assertRaises(NoOptionError, config.get_value, 'compiler', None, 'fc')
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
index ac3753c948..a92bd92289 100644
--- a/lib/spack/spack/test/install.py
+++ b/lib/spack/spack/test/install.py
@@ -22,6 +22,7 @@
# 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 unittest
import shutil
from contextlib import closing
@@ -82,11 +83,12 @@ class InstallTest(MockPackagesTest):
# Get a basic concrete spec for the trivial install package.
spec = Spec(install_test_package)
spec.concretize()
+ self.assertTrue(spec.concrete)
# Get the package
pkg = spack.db.get(spec)
- # Fake some values
+ # Fake the URL for the package so it downloads from a file.
archive_path = join_path(self.stage.path, archive_name)
pkg.url = 'file://' + archive_path
@@ -94,5 +96,5 @@ class InstallTest(MockPackagesTest):
pkg.do_install()
pkg.do_uninstall()
except Exception, e:
- if pkg: pkg.remove_prefix()
+ pkg.remove_prefix()
raise
diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py
index e9a8113c09..adde70ff6c 100644
--- a/lib/spack/spack/test/mock_packages_test.py
+++ b/lib/spack/spack/test/mock_packages_test.py
@@ -25,9 +25,11 @@
import unittest
import spack
+import spack.config
from spack.packages import PackageDB
from spack.spec import Spec
+
def set_pkg_dep(pkg, spec):
"""Alters dependence information for a pacakge.
Use this to mock up constraints.
@@ -45,9 +47,14 @@ class MockPackagesTest(unittest.TestCase):
self.real_db = spack.db
spack.db = PackageDB(spack.mock_packages_path)
+ self.real_scopes = spack.config._scopes
+ spack.config._scopes = {
+ 'site' : spack.mock_site_config,
+ 'user' : spack.mock_user_config }
@classmethod
def tearDown(self):
"""Restore the real packages path after any test."""
- #restore_dependencies()
spack.db = self.real_db
+ spack.config._scopes = self.real_scopes
+
diff --git a/lib/spack/spack/test/package_sanity.py b/lib/spack/spack/test/package_sanity.py
index a0bf4ceb4a..1a7bc5dc5e 100644
--- a/lib/spack/spack/test/package_sanity.py
+++ b/lib/spack/spack/test/package_sanity.py
@@ -31,6 +31,13 @@ import spack
import spack.url as url
class PackageSanityTest(unittest.TestCase):
+
+ def test_get_all_packages(self):
+ """Get all packages once and make sure that works."""
+ for name in spack.db.all_package_names():
+ spack.db.get(name)
+
+
def test_url_versions(self):
"""Ensure that url_for_version does the right thing for at least the
default version of each package.
diff --git a/lib/spack/spack/test/packages.py b/lib/spack/spack/test/packages.py
index 1b2e0ab07a..a8183cf6a6 100644
--- a/lib/spack/spack/test/packages.py
+++ b/lib/spack/spack/test/packages.py
@@ -28,6 +28,7 @@ from llnl.util.filesystem import join_path
import spack
import spack.packages as packages
+from spack.util.naming import mod_to_class
from spack.test.mock_packages_test import *
@@ -58,8 +59,8 @@ class PackagesTest(MockPackagesTest):
def test_package_class_names(self):
- self.assertEqual('Mpich', packages.class_name_for_package_name('mpich'))
- self.assertEqual('PmgrCollective', packages.class_name_for_package_name('pmgr_collective'))
- self.assertEqual('PmgrCollective', packages.class_name_for_package_name('pmgr-collective'))
- self.assertEqual('Pmgrcollective', packages.class_name_for_package_name('PmgrCollective'))
- self.assertEqual('Num_3db', packages.class_name_for_package_name('3db'))
+ self.assertEqual('Mpich', mod_to_class('mpich'))
+ self.assertEqual('PmgrCollective', mod_to_class('pmgr_collective'))
+ self.assertEqual('PmgrCollective', mod_to_class('pmgr-collective'))
+ self.assertEqual('Pmgrcollective', mod_to_class('PmgrCollective'))
+ self.assertEqual('_3db', mod_to_class('3db'))
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
index dbd04f9449..5fb09e68af 100644
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -37,16 +37,13 @@ class SpecSematicsTest(MockPackagesTest):
left = Spec(spec)
right = parse_anonymous_spec(anon_spec, left.name)
+ # Satisfies is one-directional.
self.assertTrue(left.satisfies(right))
self.assertTrue(left.satisfies(anon_spec))
- self.assertTrue(right.satisfies(left))
- try:
- left.copy().constrain(right)
- left.copy().constrain(anon_spec)
- right.copy().constrain(left)
- except SpecError, e:
- self.fail("Got a SpecError in constrain! " + e.message)
+ # if left satisfies right, then we should be able to consrain
+ # right by left. Reverse is not always true.
+ right.copy().constrain(left)
def check_unsatisfiable(self, spec, anon_spec):
@@ -56,25 +53,21 @@ class SpecSematicsTest(MockPackagesTest):
self.assertFalse(left.satisfies(right))
self.assertFalse(left.satisfies(anon_spec))
- self.assertFalse(right.satisfies(left))
+ self.assertRaises(UnsatisfiableSpecError, right.copy().constrain, left)
- self.assertRaises(UnsatisfiableSpecError, left.constrain, right)
- self.assertRaises(UnsatisfiableSpecError, left.constrain, anon_spec)
- self.assertRaises(UnsatisfiableSpecError, right.constrain, left)
-
- def check_constrain(self, expected, constrained, constraint):
+ def check_constrain(self, expected, spec, constraint):
exp = Spec(expected)
- constrained = Spec(constrained)
+ spec = Spec(spec)
constraint = Spec(constraint)
- constrained.constrain(constraint)
- self.assertEqual(exp, constrained)
+ spec.constrain(constraint)
+ self.assertEqual(exp, spec)
- def check_invalid_constraint(self, constrained, constraint):
- constrained = Spec(constrained)
+ def check_invalid_constraint(self, spec, constraint):
+ spec = Spec(spec)
constraint = Spec(constraint)
- self.assertRaises(UnsatisfiableSpecError, constrained.constrain, constraint)
+ self.assertRaises(UnsatisfiableSpecError, spec.constrain, constraint)
# ================================================================================
@@ -177,3 +170,8 @@ class SpecSematicsTest(MockPackagesTest):
self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo')
self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54')
+
+
+ def test_compiler_satisfies(self):
+ self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7')
+ self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3')
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py
index 20468be1b1..404f38906e 100644
--- a/lib/spack/spack/test/spec_syntax.py
+++ b/lib/spack/spack/test/spec_syntax.py
@@ -133,12 +133,12 @@ class SpecSyntaxTest(unittest.TestCase):
self.assertRaises(DuplicateDependencyError, self.check_parse, "x ^y ^y")
def test_duplicate_compiler(self):
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x%intel%intel")
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x%intel%gcc")
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x%gcc%intel")
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x ^y%intel%intel")
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x ^y%intel%gcc")
- self.assertRaises(DuplicateCompilerError, self.check_parse, "x ^y%gcc%intel")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x%intel%intel")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x%intel%gcc")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x%gcc%intel")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x ^y%intel%intel")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x ^y%intel%gcc")
+ self.assertRaises(DuplicateCompilerSpecError, self.check_parse, "x ^y%gcc%intel")
# ================================================================================
diff --git a/lib/spack/spack/test/versions.py b/lib/spack/spack/test/versions.py
index 37fd28a8e7..454ab36b8a 100644
--- a/lib/spack/spack/test/versions.py
+++ b/lib/spack/spack/test/versions.py
@@ -83,6 +83,14 @@ class VersionsTest(unittest.TestCase):
self.assertFalse(ver(v1).overlaps(ver(v2)))
+ def assert_satisfies(self, v1, v2):
+ self.assertTrue(ver(v1).satisfies(ver(v2)))
+
+
+ def assert_does_not_satisfy(self, v1, v2):
+ self.assertFalse(ver(v1).satisfies(ver(v2)))
+
+
def check_intersection(self, expected, a, b):
self.assertEqual(ver(expected), ver(a).intersection(ver(b)))
@@ -301,3 +309,68 @@ class VersionsTest(unittest.TestCase):
self.check_intersection(['2.5:2.7'],
['1.1:2.7'], ['2.5:3.0','1.0'])
self.check_intersection(['0:1'], [':'], ['0:1'])
+
+
+ def test_basic_version_satisfaction(self):
+ self.assert_satisfies('4.7.3', '4.7.3')
+
+ self.assert_satisfies('4.7.3', '4.7')
+ self.assert_satisfies('4.7.3b2', '4.7')
+ self.assert_satisfies('4.7b6', '4.7')
+
+ self.assert_satisfies('4.7.3', '4')
+ self.assert_satisfies('4.7.3b2', '4')
+ self.assert_satisfies('4.7b6', '4')
+
+ self.assert_does_not_satisfy('4.8.0', '4.9')
+ self.assert_does_not_satisfy('4.8', '4.9')
+ self.assert_does_not_satisfy('4', '4.9')
+
+ def test_basic_version_satisfaction_in_lists(self):
+ self.assert_satisfies(['4.7.3'], ['4.7.3'])
+
+ self.assert_satisfies(['4.7.3'], ['4.7'])
+ self.assert_satisfies(['4.7.3b2'], ['4.7'])
+ self.assert_satisfies(['4.7b6'], ['4.7'])
+
+ self.assert_satisfies(['4.7.3'], ['4'])
+ self.assert_satisfies(['4.7.3b2'], ['4'])
+ self.assert_satisfies(['4.7b6'], ['4'])
+
+ self.assert_does_not_satisfy(['4.8.0'], ['4.9'])
+ self.assert_does_not_satisfy(['4.8'], ['4.9'])
+ self.assert_does_not_satisfy(['4'], ['4.9'])
+
+ def test_version_range_satisfaction(self):
+ self.assert_satisfies('4.7b6', '4.3:4.7')
+ self.assert_satisfies('4.3.0', '4.3:4.7')
+ self.assert_satisfies('4.3.2', '4.3:4.7')
+
+ self.assert_does_not_satisfy('4.8.0', '4.3:4.7')
+ self.assert_does_not_satisfy('4.3', '4.4:4.7')
+
+ self.assert_satisfies('4.7b6', '4.3:4.7')
+ self.assert_does_not_satisfy('4.8.0', '4.3:4.7')
+
+ def test_version_range_satisfaction_in_lists(self):
+ self.assert_satisfies(['4.7b6'], ['4.3:4.7'])
+ self.assert_satisfies(['4.3.0'], ['4.3:4.7'])
+ self.assert_satisfies(['4.3.2'], ['4.3:4.7'])
+
+ self.assert_does_not_satisfy(['4.8.0'], ['4.3:4.7'])
+ self.assert_does_not_satisfy(['4.3'], ['4.4:4.7'])
+
+ self.assert_satisfies(['4.7b6'], ['4.3:4.7'])
+ self.assert_does_not_satisfy(['4.8.0'], ['4.3:4.7'])
+
+ def test_satisfaction_with_lists(self):
+ self.assert_satisfies('4.7', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.7.3', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.6.5', '4.3, 4.6, 4.7')
+ self.assert_satisfies('4.6.5.2', '4.3, 4.6, 4.7')
+
+ self.assert_does_not_satisfy('4', '4.3, 4.6, 4.7')
+ self.assert_does_not_satisfy('4.8.0', '4.2, 4.3:4.7')
+
+ self.assert_satisfies('4.8.0', '4.2, 4.3:4.8')
+ self.assert_satisfies('4.8.2', '4.2, 4.3:4.8')
diff --git a/lib/spack/spack/util/executable.py b/lib/spack/spack/util/executable.py
index 845d1470e7..bc27b25889 100644
--- a/lib/spack/spack/util/executable.py
+++ b/lib/spack/spack/util/executable.py
@@ -22,14 +22,15 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+__all__ = ['Executable', 'which', 'ProcessError']
+
import os
import sys
import re
import subprocess
import llnl.util.tty as tty
-from spack.error import SpackError
-
+import spack.error
class Executable(object):
"""Class representing a program that can be run on the command line."""
@@ -37,19 +38,21 @@ class Executable(object):
self.exe = name.split(' ')
self.returncode = None
+
def add_default_arg(self, arg):
self.exe.append(arg)
@property
def command(self):
- return self.exe[0]
+ return ' '.join(self.exe)
def __call__(self, *args, **kwargs):
"""Run the executable with subprocess.check_output, return output."""
return_output = kwargs.get("return_output", False)
fail_on_error = kwargs.get("fail_on_error", True)
+ error = kwargs.get("error", sys.stderr)
quoted_args = [arg for arg in args if re.search(r'^"|^\'|"$|\'$', arg)]
if quoted_args:
@@ -60,24 +63,49 @@ class Executable(object):
"Consider removing them")
cmd = self.exe + list(args)
- tty.verbose(" ".join(cmd))
+ tty.debug(" ".join(cmd))
+ close_error = False
try:
+ if error is None:
+ error = open(os.devnull, 'w')
+ close_error = True
+
proc = subprocess.Popen(
cmd,
- stderr=sys.stderr,
+ stderr=error,
stdout=subprocess.PIPE if return_output else sys.stdout)
out, err = proc.communicate()
self.returncode = proc.returncode
if fail_on_error and proc.returncode != 0:
- raise SpackError("command '%s' returned error code %d"
- % (" ".join(cmd), proc.returncode))
+ raise ProcessError("command '%s' returned error code %d"
+ % (" ".join(cmd), proc.returncode))
if return_output:
return out
except subprocess.CalledProcessError, e:
- if fail_on_error: raise
+ if fail_on_error:
+ raise ProcessError(
+ "command '%s' failed to run." % (
+ " ".join(cmd), proc.returncode), str(e))
+
+ finally:
+ if close_error:
+ error.close()
+
+
+ def __eq__(self, other):
+ return self.exe == other.exe
+
+
+ def __neq__(self, other):
+ return not (self == other)
+
+
+ def __hash__(self):
+ return hash((type(self),) + tuple(self.exe))
+
def __repr__(self):
return "<exe: %s>" % self.exe
@@ -99,3 +127,8 @@ def which(name, **kwargs):
if required:
tty.die("spack requires %s. Make sure it is in your path." % name)
return None
+
+
+class ProcessError(spack.error.SpackError):
+ def __init__(self, msg, *long_msg):
+ super(ProcessError, self).__init__(msg, *long_msg)
diff --git a/lib/spack/spack/util/multiproc.py b/lib/spack/spack/util/multiproc.py
new file mode 100644
index 0000000000..9e045a090f
--- /dev/null
+++ b/lib/spack/spack/util/multiproc.py
@@ -0,0 +1,45 @@
+##############################################################################
+# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://scalability-llnl.github.io/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License (as published by
+# the Free Software Foundation) version 2.1 dated February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""
+This implements a parallel map operation but it can accept more values
+than multiprocessing.Pool.apply() can. For example, apply() will fail
+to pickle functions if they're passed indirectly as parameters.
+"""
+from multiprocessing import Process, Pipe
+from itertools import izip
+
+def spawn(f):
+ def fun(pipe,x):
+ pipe.send(f(x))
+ pipe.close()
+ return fun
+
+def parmap(f,X):
+ pipe=[Pipe() for x in X]
+ proc=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]
+ [p.start() for p in proc]
+ [p.join() for p in proc]
+ return [p.recv() for (p,c) in pipe]
+
diff --git a/lib/spack/spack/util/naming.py b/lib/spack/spack/util/naming.py
new file mode 100644
index 0000000000..782afbd4bb
--- /dev/null
+++ b/lib/spack/spack/util/naming.py
@@ -0,0 +1,61 @@
+# Need this because of spack.util.string
+from __future__ import absolute_import
+import string
+import re
+
+import spack
+
+# Valid module names can contain '-' but can't start with it.
+_valid_module_re = r'^\w[\w-]*$'
+
+
+def mod_to_class(mod_name):
+ """Convert a name from module style to class name style. Spack mostly
+ follows `PEP-8 <http://legacy.python.org/dev/peps/pep-0008/>`_:
+
+ * Module and package names use lowercase_with_underscores.
+ * Class names use the CapWords convention.
+
+ Regular source code follows these convetions. Spack is a bit
+ more liberal with its Package names nad Compiler names:
+
+ * They can contain '-' as well as '_', but cannot start with '-'.
+ * They can start with numbers, e.g. "3proxy".
+
+ This function converts from the module convention to the class
+ convention by removing _ and - and converting surrounding
+ lowercase text to CapWords. If mod_name starts with a number,
+ the class name returned will be prepended with '_' to make a
+ valid Python identifier.
+ """
+ validate_module_name(mod_name)
+
+ class_name = re.sub(r'[-_]+', '-', mod_name)
+ class_name = string.capwords(class_name, '-')
+ class_name = class_name.replace('-', '')
+
+ # If a class starts with a number, prefix it with Number_ to make it a valid
+ # Python class name.
+ if re.match(r'^[0-9]', class_name):
+ class_name = "_%s" % class_name
+
+ return class_name
+
+
+def valid_module_name(mod_name):
+ """Return whether the mod_name is valid for use in Spack."""
+ return bool(re.match(_valid_module_re, mod_name))
+
+
+def validate_module_name(mod_name):
+ """Raise an exception if mod_name is not valid."""
+ if not valid_module_name(mod_name):
+ raise InvalidModuleNameError(mod_name)
+
+
+class InvalidModuleNameError(spack.error.SpackError):
+ """Raised when we encounter a bad module name."""
+ def __init__(self, name):
+ super(InvalidModuleNameError, self).__init__(
+ "Invalid module name: " + name)
+ self.name = name
diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py
index 0b5125fdf0..ce94303a9c 100644
--- a/lib/spack/spack/version.py
+++ b/lib/spack/spack/version.py
@@ -143,6 +143,18 @@ class Version(object):
return self
+ @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.
+ """
+ nself = len(self.version)
+ nother = len(other.version)
+ return nother <= nself and self.version[:nother] == other.version
+
+
def wildcard(self):
"""Create a regex that will match variants of this version string."""
def a_or_n(seg):
@@ -327,6 +339,37 @@ class VersionRange(object):
@coerced
+ def satisfies(self, other):
+ """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.
+
+ This is essentially the same as overlaps(), but overlaps assumes
+ that its arguments are specific. That is, 4.7 is interpreted as
+ 4.7.0.0.0.0... . This funciton assumes that 4.7 woudl be satisfied
+ 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.
+
+ Note that we don't need to check whether the end of this range
+ would satisfy the start of the other range, because overlaps()
+ already covers that case.
+
+ Note further that overlaps() is a symmetric operation, while
+ satisfies() is not.
+ """
+ return (self.overlaps(other) or
+ # if either self.start or other.end are None, then this can't
+ # satisfy, or overlaps() would've taken care of it.
+ self.start and other.end and self.start.satisfies(other.end))
+
+
+ @coerced
def overlaps(self, other):
return (other in self or self in other or
((self.start == None or other.end is None or
@@ -444,11 +487,6 @@ class VersionList(object):
return self[-1].highest()
- def satisfies(self, other):
- """Synonym for overlaps."""
- return self.overlaps(other)
-
-
@coerced
def overlaps(self, other):
if not other or not self:
@@ -466,6 +504,27 @@ class VersionList(object):
@coerced
+ def satisfies(self, other):
+ """A VersionList satisfies another if some version in the list would
+ would satisfy some version in the other list. This uses essentially
+ the same algorithm as overlaps() does for VersionList, but it calls
+ satisfies() on member Versions and VersionRanges.
+ """
+ if not other or not self:
+ return False
+
+ s = o = 0
+ while s < len(self) and o < len(other):
+ if self[s].satisfies(other[o]):
+ return True
+ elif self[s] < other[o]:
+ s += 1
+ else:
+ o += 1
+ return False
+
+
+ @coerced
def update(self, other):
for v in other.versions:
self.add(v)
diff --git a/var/spack/mock_configs/site_spackconfig b/var/spack/mock_configs/site_spackconfig
new file mode 100644
index 0000000000..1358720362
--- /dev/null
+++ b/var/spack/mock_configs/site_spackconfig
@@ -0,0 +1,12 @@
+[compiler "gcc@4.5.0"]
+ cc = /path/to/gcc
+ cxx = /path/to/g++
+ f77 = /path/to/gfortran
+ fc = /path/to/gfortran
+
+[compiler "clang@3.3"]
+ cc = /path/to/clang
+ cxx = /path/to/clang++
+ f77 = None
+ fc = None
+
diff --git a/var/spack/mock_configs/user_spackconfig b/var/spack/mock_configs/user_spackconfig
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/var/spack/mock_configs/user_spackconfig
diff --git a/var/spack/packages/libdwarf/package.py b/var/spack/packages/libdwarf/package.py
index 657e84705a..000187736c 100644
--- a/var/spack/packages/libdwarf/package.py
+++ b/var/spack/packages/libdwarf/package.py
@@ -39,6 +39,8 @@ class Libdwarf(Package):
depends_on("libelf")
+ parallel = False
+
def clean(self):
for dir in dwarf_dirs: