summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/docs/conf.py6
-rw-r--r--lib/spack/llnl/util/lang.py5
-rw-r--r--lib/spack/llnl/util/tty/colify.py18
-rw-r--r--lib/spack/spack/__init__.py14
-rw-r--r--lib/spack/spack/architecture.py20
-rw-r--r--lib/spack/spack/build_environment.py7
-rw-r--r--lib/spack/spack/cmd/info.py16
-rw-r--r--lib/spack/spack/cmd/test-install.py211
-rw-r--r--lib/spack/spack/compiler.py24
-rw-r--r--lib/spack/spack/directives.py10
-rw-r--r--lib/spack/spack/directory_layout.py7
-rw-r--r--lib/spack/spack/package.py19
-rw-r--r--lib/spack/spack/stage.py3
-rw-r--r--lib/spack/spack/test/__init__.py1
-rw-r--r--lib/spack/spack/test/unit_install.py121
-rw-r--r--lib/spack/spack/url.py4
-rw-r--r--lib/spack/spack/virtual.py8
17 files changed, 427 insertions, 67 deletions
diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py
index 7303d7fef6..bce9ef0e94 100644
--- a/lib/spack/docs/conf.py
+++ b/lib/spack/docs/conf.py
@@ -149,7 +149,7 @@ html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
-#html_theme_options = [('show_copyright', False)]
+html_theme_options = { 'logo_only' : True }
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ["_themes"]
@@ -163,12 +163,12 @@ html_theme_path = ["_themes"]
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-#html_logo = None
+html_logo = '../../../share/spack/logo/spack-logo-white-text-48.png'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-#html_favicon = None
+html_favicon = '../../../share/spack/logo/favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
diff --git a/lib/spack/llnl/util/lang.py b/lib/spack/llnl/util/lang.py
index 9e1bef18ca..156ee34c9e 100644
--- a/lib/spack/llnl/util/lang.py
+++ b/lib/spack/llnl/util/lang.py
@@ -87,10 +87,7 @@ def index_by(objects, *funcs):
result = {}
for o in objects:
key = f(o)
- if key not in result:
- result[key] = [o]
- else:
- result[key].append(o)
+ result.setdefault(key, []).append(o)
for key, objects in result.items():
result[key] = index_by(objects, *funcs[1:])
diff --git a/lib/spack/llnl/util/tty/colify.py b/lib/spack/llnl/util/tty/colify.py
index 66c52c3968..db928444c7 100644
--- a/lib/spack/llnl/util/tty/colify.py
+++ b/lib/spack/llnl/util/tty/colify.py
@@ -33,8 +33,7 @@ import struct
from StringIO import StringIO
from llnl.util.tty import terminal_size
-from llnl.util.tty.color import clen
-
+from llnl.util.tty.color import clen, cextra
class ColumnConfig:
def __init__(self, cols):
@@ -42,7 +41,6 @@ class ColumnConfig:
self.line_length = 0
self.valid = True
self.widths = [0] * cols # does not include ansi colors
- self.cwidths = [0] * cols # includes ansi colors
def __repr__(self):
attrs = [(a,getattr(self, a)) for a in dir(self) if not a.startswith("__")]
@@ -66,8 +64,6 @@ def config_variable_cols(elts, console_width, padding, cols=0):
# Get a bound on the most columns we could possibly have.
# 'clen' ignores length of ansi color sequences.
lengths = [clen(e) for e in elts]
- clengths = [len(e) for e in elts]
-
max_cols = max(1, console_width / (min(lengths) + padding))
max_cols = min(len(elts), max_cols)
@@ -85,7 +81,6 @@ def config_variable_cols(elts, console_width, padding, cols=0):
if conf.widths[col] < (length + p):
conf.line_length += length + p - conf.widths[col]
conf.widths[col] = length + p
- conf.cwidths[col] = clengths[i] + p
conf.valid = (conf.line_length < console_width)
try:
@@ -118,7 +113,6 @@ def config_uniform_cols(elts, console_width, padding, cols=0):
config = ColumnConfig(cols)
config.widths = [max_len] * cols
- config.cwidths = [max_clen] * cols
return config
@@ -147,9 +141,6 @@ def colify(elts, **options):
method=<string> Method to use to fit columns. Options are variable or uniform.
Variable-width columns are tighter, uniform columns are all the
same width and fit less data on the screen.
-
- len=<func> Function to use for calculating string length.
- Useful for ignoring ansi color. Default is 'len'.
"""
# Get keyword arguments or set defaults
cols = options.pop("cols", 0)
@@ -199,9 +190,6 @@ def colify(elts, **options):
raise ValueError("method must be one of: " + allowed_methods)
cols = config.cols
- formats = ["%%-%ds" % width for width in config.cwidths[:-1]]
- formats.append("%s") # last column has no trailing space
-
rows = (len(elts) + cols - 1) / cols
rows_last_col = len(elts) % rows
@@ -209,7 +197,9 @@ def colify(elts, **options):
output.write(" " * indent)
for col in xrange(cols):
elt = col * rows + row
- output.write(formats[col] % elts[elt])
+ width = config.widths[col] + cextra(elts[elt])
+ fmt = '%%-%ds' % width
+ output.write(fmt % elts[elt])
output.write("\n")
row += 1
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 4d10bc2da6..1ecf662178 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -27,24 +27,26 @@ import tempfile
from llnl.util.filesystem import *
# This lives in $prefix/lib/spack/spack/__file__
-prefix = ancestor(__file__, 4)
+spack_root = ancestor(__file__, 4)
# The spack script itself
-spack_file = join_path(prefix, "bin", "spack")
+spack_file = join_path(spack_root, "bin", "spack")
# spack directory hierarchy
-etc_path = join_path(prefix, "etc")
-lib_path = join_path(prefix, "lib", "spack")
+lib_path = join_path(spack_root, "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")
hooks_path = join_path(module_path, "hooks")
-var_path = join_path(prefix, "var", "spack")
+var_path = join_path(spack_root, "var", "spack")
stage_path = join_path(var_path, "stage")
+share_path = join_path(spack_root, "share", "spack")
+
+prefix = spack_root
opt_path = join_path(prefix, "opt")
install_path = join_path(opt_path, "spack")
-share_path = join_path(prefix, "share", "spack")
+etc_path = join_path(prefix, "etc")
#
# Set up the packages database.
diff --git a/lib/spack/spack/architecture.py b/lib/spack/spack/architecture.py
index 0c4b605e91..05ac5d6f35 100644
--- a/lib/spack/spack/architecture.py
+++ b/lib/spack/spack/architecture.py
@@ -23,13 +23,12 @@
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import os
-import platform as py_platform
+import subprocess
from llnl.util.lang import memoized
import spack
import spack.error as serr
-from spack.version import Version
class InvalidSysTypeError(serr.SpackError):
@@ -59,22 +58,23 @@ def get_sys_type_from_environment():
return os.environ.get('SYS_TYPE')
-def get_mac_sys_type():
- """Return a Mac OS SYS_TYPE or None if this isn't a mac."""
- mac_ver = py_platform.mac_ver()[0]
- if not mac_ver:
+def get_sys_type_from_uname():
+ """Return the architecture from uname."""
+ try:
+ arch_proc = subprocess.Popen(['uname', '-i'],
+ stdout=subprocess.PIPE)
+ arch, _ = arch_proc.communicate()
+ return arch.strip()
+ except:
return None
- return "macosx_%s_%s" % (
- Version(mac_ver).up_to(2), py_platform.machine())
-
@memoized
def sys_type():
"""Returns a SysType for the current machine."""
methods = [get_sys_type_from_spack_globals,
get_sys_type_from_environment,
- get_mac_sys_type]
+ get_sys_type_from_uname]
# search for a method that doesn't return None
sys_type = None
diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py
index a133faa629..dac25d9940 100644
--- a/lib/spack/spack/build_environment.py
+++ b/lib/spack/spack/build_environment.py
@@ -296,4 +296,9 @@ def fork(pkg, function):
# message. Just make the parent exit with an error code.
pid, returncode = os.waitpid(pid, 0)
if returncode != 0:
- sys.exit(1)
+ raise InstallError("Installation process had nonzero exit code."
+ .format(str(returncode)))
+
+
+class InstallError(spack.error.SpackError):
+ """Raised when a package fails to install"""
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index c6209523f0..085e4db44d 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -65,11 +65,21 @@ def print_text_info(pkg):
print "None"
else:
pad = padder(pkg.variants, 4)
+
+ maxv = max(len(v) for v in sorted(pkg.variants))
+ fmt = "%%-%ss%%-10s%%s" % (maxv + 4)
+
+ print " " + fmt % ('Name', 'Default', 'Description')
+ print
for name in sorted(pkg.variants):
v = pkg.variants[name]
- print " %s%s" % (
- pad(('+' if v.default else '-') + name + ':'),
- "\n".join(textwrap.wrap(v.description)))
+ default = 'on' if v.default else 'off'
+
+ lines = textwrap.wrap(v.description)
+ lines[1:] = [" " + (" " * maxv) + l for l in lines[1:]]
+ desc = "\n".join(lines)
+
+ print " " + fmt % (name, default, desc)
print
print "Dependencies:"
diff --git a/lib/spack/spack/cmd/test-install.py b/lib/spack/spack/cmd/test-install.py
new file mode 100644
index 0000000000..68b761d5dc
--- /dev/null
+++ b/lib/spack/spack/cmd/test-install.py
@@ -0,0 +1,211 @@
+##############################################################################
+# 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 external import argparse
+import xml.etree.ElementTree as ET
+import itertools
+import re
+import os
+import codecs
+
+import llnl.util.tty as tty
+from llnl.util.filesystem import *
+
+import spack
+from spack.build_environment import InstallError
+from spack.fetch_strategy import FetchError
+import spack.cmd
+
+description = "Treat package installations as unit tests and output formatted test results"
+
+def setup_parser(subparser):
+ subparser.add_argument(
+ '-j', '--jobs', action='store', type=int,
+ help="Explicitly set number of make jobs. Default is #cpus.")
+
+ subparser.add_argument(
+ '-n', '--no-checksum', action='store_true', dest='no_checksum',
+ help="Do not check packages against checksum")
+
+ subparser.add_argument(
+ '-o', '--output', action='store', help="test output goes in this file")
+
+ subparser.add_argument(
+ 'package', nargs=argparse.REMAINDER, help="spec of package to install")
+
+
+class JunitResultFormat(object):
+ def __init__(self):
+ self.root = ET.Element('testsuite')
+ self.tests = []
+
+ def add_test(self, buildId, testResult, buildInfo=None):
+ self.tests.append((buildId, testResult, buildInfo))
+
+ def write_to(self, stream):
+ self.root.set('tests', '{0}'.format(len(self.tests)))
+ for buildId, testResult, buildInfo in self.tests:
+ testcase = ET.SubElement(self.root, 'testcase')
+ testcase.set('classname', buildId.name)
+ testcase.set('name', buildId.stringId())
+ if testResult == TestResult.FAILED:
+ failure = ET.SubElement(testcase, 'failure')
+ failure.set('type', "Build Error")
+ failure.text = buildInfo
+ elif testResult == TestResult.SKIPPED:
+ skipped = ET.SubElement(testcase, 'skipped')
+ skipped.set('type', "Skipped Build")
+ skipped.text = buildInfo
+ ET.ElementTree(self.root).write(stream)
+
+
+class TestResult(object):
+ PASSED = 0
+ FAILED = 1
+ SKIPPED = 2
+
+
+class BuildId(object):
+ def __init__(self, spec):
+ self.name = spec.name
+ self.version = spec.version
+ self.hashId = spec.dag_hash()
+
+ def stringId(self):
+ return "-".join(str(x) for x in (self.name, self.version, self.hashId))
+
+ def __hash__(self):
+ return hash((self.name, self.version, self.hashId))
+
+ def __eq__(self, other):
+ if not isinstance(other, BuildId):
+ return False
+
+ return ((self.name, self.version, self.hashId) ==
+ (other.name, other.version, other.hashId))
+
+
+def fetch_log(path):
+ if not os.path.exists(path):
+ return list()
+ with codecs.open(path, 'rb', 'utf-8') as F:
+ return list(line.strip() for line in F.readlines())
+
+
+def failed_dependencies(spec):
+ return set(childSpec for childSpec in spec.dependencies.itervalues() if not
+ spack.db.get(childSpec).installed)
+
+
+def create_test_output(topSpec, newInstalls, output, getLogFunc=fetch_log):
+ # Post-order traversal is not strictly required but it makes sense to output
+ # tests for dependencies first.
+ for spec in topSpec.traverse(order='post'):
+ if spec not in newInstalls:
+ continue
+
+ failedDeps = failed_dependencies(spec)
+ package = spack.db.get(spec)
+ if failedDeps:
+ result = TestResult.SKIPPED
+ dep = iter(failedDeps).next()
+ depBID = BuildId(dep)
+ errOutput = "Skipped due to failed dependency: {0}".format(
+ depBID.stringId())
+ elif (not package.installed) and (not package.stage.source_path):
+ result = TestResult.FAILED
+ errOutput = "Failure to fetch package resources."
+ elif not package.installed:
+ result = TestResult.FAILED
+ lines = getLogFunc(package.build_log_path)
+ errMessages = list(line for line in lines if
+ re.search('error:', line, re.IGNORECASE))
+ errOutput = errMessages if errMessages else lines[-10:]
+ errOutput = '\n'.join(itertools.chain(
+ [spec.to_yaml(), "Errors:"], errOutput,
+ ["Build Log:", package.build_log_path]))
+ else:
+ result = TestResult.PASSED
+ errOutput = None
+
+ bId = BuildId(spec)
+ output.add_test(bId, result, errOutput)
+
+
+def test_install(parser, args):
+ if not args.package:
+ tty.die("install requires a package argument")
+
+ if args.jobs is not None:
+ if args.jobs <= 0:
+ tty.die("The -j option must be a positive integer!")
+
+ if args.no_checksum:
+ spack.do_checksum = False # TODO: remove this global.
+
+ specs = spack.cmd.parse_specs(args.package, concretize=True)
+ if len(specs) > 1:
+ tty.die("Only 1 top-level package can be specified")
+ topSpec = iter(specs).next()
+
+ newInstalls = set()
+ for spec in topSpec.traverse():
+ package = spack.db.get(spec)
+ if not package.installed:
+ newInstalls.add(spec)
+
+ if not args.output:
+ bId = BuildId(topSpec)
+ outputDir = join_path(os.getcwd(), "test-output")
+ if not os.path.exists(outputDir):
+ os.mkdir(outputDir)
+ outputFpath = join_path(outputDir, "test-{0}.xml".format(bId.stringId()))
+ else:
+ outputFpath = args.output
+
+ for spec in topSpec.traverse(order='post'):
+ # Calling do_install for the top-level package would be sufficient but
+ # this attempts to keep going if any package fails (other packages which
+ # are not dependents may succeed)
+ package = spack.db.get(spec)
+ if (not failed_dependencies(spec)) and (not package.installed):
+ try:
+ package.do_install(
+ keep_prefix=False,
+ keep_stage=True,
+ ignore_deps=False,
+ make_jobs=args.jobs,
+ verbose=True,
+ fake=False)
+ except InstallError:
+ pass
+ except FetchError:
+ pass
+
+ jrf = JunitResultFormat()
+ handled = {}
+ create_test_output(topSpec, newInstalls, jrf)
+
+ with open(outputFpath, 'wb') as F:
+ jrf.write_to(F)
diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py
index 646050d267..1e800a8979 100644
--- a/lib/spack/spack/compiler.py
+++ b/lib/spack/spack/compiler.py
@@ -227,14 +227,32 @@ class Compiler(object):
for d in dicts:
all_keys.update(d)
- compilers = []
+ compilers = {}
for k in all_keys:
ver, pre, suf = k
+
+ # Skip compilers with unknown version.
+ if ver == 'unknown':
+ continue
+
paths = tuple(pn[k] if k in pn else None for pn in dicts)
spec = spack.spec.CompilerSpec(cls.name, ver)
- compilers.append(cls(spec, *paths))
- return compilers
+ if ver in compilers:
+ prev = compilers[ver]
+
+ # prefer the one with more compilers.
+ prev_paths = [prev.cc, prev.cxx, prev.f77, prev.fc]
+ newcount = len([p for p in paths if p is not None])
+ prevcount = len([p for p in prev_paths if p is not None])
+
+ # Don't add if it's not an improvement over prev compiler.
+ if newcount <= prevcount:
+ continue
+
+ compilers[ver] = cls(spec, *paths)
+
+ return list(compilers.values())
def __repr__(self):
diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py
index 9297d6dac3..78039ac6f9 100644
--- a/lib/spack/spack/directives.py
+++ b/lib/spack/spack/directives.py
@@ -239,12 +239,10 @@ def patch(pkg, url_or_filename, level=1, when=None):
when = pkg.name
when_spec = parse_anonymous_spec(when, pkg.name)
- if when_spec not in pkg.patches:
- pkg.patches[when_spec] = [Patch(pkg.name, url_or_filename, level)]
- else:
- # if this spec is identical to some other, then append this
- # patch to the existing list.
- pkg.patches[when_spec].append(Patch(pkg.name, url_or_filename, level))
+ cur_patches = pkg.patches.setdefault(when_spec, [])
+ # if this spec is identical to some other, then append this
+ # patch to the existing list.
+ cur_patches.append(Patch(pkg.name, url_or_filename, level))
@directive('variants')
diff --git a/lib/spack/spack/directory_layout.py b/lib/spack/spack/directory_layout.py
index 758ec209db..da8f4187cc 100644
--- a/lib/spack/spack/directory_layout.py
+++ b/lib/spack/spack/directory_layout.py
@@ -186,14 +186,9 @@ class YamlDirectoryLayout(DirectoryLayout):
def relative_path_for_spec(self, spec):
_check_concrete(spec)
- enabled_variants = (
- '-' + v.name for v in spec.variants.values()
- if v.enabled)
-
- dir_name = "%s-%s%s-%s" % (
+ dir_name = "%s-%s-%s" % (
spec.name,
spec.version,
- ''.join(enabled_variants),
spec.dag_hash(self.hash_len))
path = join_path(
diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py
index b87baf403e..b15d4b2040 100644
--- a/lib/spack/spack/package.py
+++ b/lib/spack/spack/package.py
@@ -438,9 +438,16 @@ class Package(object):
raise ValueError("Can only get a stage for a concrete package.")
if self._stage is None:
+ # Construct a mirror path (TODO: get this out of package.py)
mp = spack.mirror.mirror_archive_path(self.spec)
- self._stage = Stage(
- self.fetcher, mirror_path=mp, name=self.spec.short_spec)
+
+ # Construct a path where the stage should build..
+ s = self.spec
+ stage_name = "%s-%s-%s" % (s.name, s.version, s.dag_hash())
+
+ # Build the stage
+ self._stage = Stage(self.fetcher, mirror_path=mp, name=stage_name)
+
return self._stage
@@ -866,6 +873,14 @@ class Package(object):
@property
+ def build_log_path(self):
+ if self.installed:
+ return spack.install_layout.build_log_path(self.spec)
+ else:
+ return join_path(self.stage.source_path, 'spack-build.out')
+
+
+ @property
def module(self):
"""Use this to add variables to the class's module's scope.
This lets us use custom syntax in the install method.
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
index 008c5f0429..78930ecb5b 100644
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -261,7 +261,8 @@ class Stage(object):
tty.debug(e)
continue
else:
- tty.die("All fetchers failed for %s" % self.name)
+ errMessage = "All fetchers failed for %s" % self.name
+ raise fs.FetchError(errMessage, None)
def check(self):
diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py
index 84419781e2..0f776bfea4 100644
--- a/lib/spack/spack/test/__init__.py
+++ b/lib/spack/spack/test/__init__.py
@@ -57,6 +57,7 @@ test_names = ['versions',
'optional_deps',
'make_executable',
'configure_guess',
+ 'unit_install',
'lock',
'database']
diff --git a/lib/spack/spack/test/unit_install.py b/lib/spack/spack/test/unit_install.py
new file mode 100644
index 0000000000..c4b9092f05
--- /dev/null
+++ b/lib/spack/spack/test/unit_install.py
@@ -0,0 +1,121 @@
+##############################################################################
+# 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 itertools
+
+import spack
+test_install = __import__("spack.cmd.test-install",
+ fromlist=["BuildId", "create_test_output", "TestResult"])
+
+class MockOutput(object):
+ def __init__(self):
+ self.results = {}
+
+ def add_test(self, buildId, passed=True, buildInfo=None):
+ self.results[buildId] = passed
+
+ def write_to(self, stream):
+ pass
+
+class MockSpec(object):
+ def __init__(self, name, version, hashStr=None):
+ self.dependencies = {}
+ self.name = name
+ self.version = version
+ self.hash = hashStr if hashStr else hash((name, version))
+
+ def traverse(self, order=None):
+ allDeps = itertools.chain.from_iterable(i.traverse() for i in
+ self.dependencies.itervalues())
+ return set(itertools.chain([self], allDeps))
+
+ def dag_hash(self):
+ return self.hash
+
+ def to_yaml(self):
+ return "<<<MOCK YAML {0}>>>".format(test_install.BuildId(self).stringId())
+
+class MockPackage(object):
+ def __init__(self, buildLogPath):
+ self.installed = False
+ self.build_log_path = buildLogPath
+
+specX = MockSpec("X", "1.2.0")
+specY = MockSpec("Y", "2.3.8")
+specX.dependencies['Y'] = specY
+pkgX = MockPackage('logX')
+pkgY = MockPackage('logY')
+bIdX = test_install.BuildId(specX)
+bIdY = test_install.BuildId(specY)
+
+class UnitInstallTest(unittest.TestCase):
+ """Tests test-install where X->Y"""
+
+ def setUp(self):
+ super(UnitInstallTest, self).setUp()
+
+ pkgX.installed = False
+ pkgY.installed = False
+
+ pkgDb = MockPackageDb({specX:pkgX, specY:pkgY})
+ spack.db = pkgDb
+
+ def tearDown(self):
+ super(UnitInstallTest, self).tearDown()
+
+ def test_installing_both(self):
+ mo = MockOutput()
+
+ pkgX.installed = True
+ pkgY.installed = True
+ test_install.create_test_output(specX, [specX, specY], mo, getLogFunc=test_fetch_log)
+
+ self.assertEqual(mo.results,
+ {bIdX:test_install.TestResult.PASSED,
+ bIdY:test_install.TestResult.PASSED})
+
+ def test_dependency_already_installed(self):
+ mo = MockOutput()
+
+ pkgX.installed = True
+ pkgY.installed = True
+ test_install.create_test_output(specX, [specX], mo, getLogFunc=test_fetch_log)
+
+ self.assertEqual(mo.results, {bIdX:test_install.TestResult.PASSED})
+
+ #TODO: add test(s) where Y fails to install
+
+class MockPackageDb(object):
+ def __init__(self, init=None):
+ self.specToPkg = {}
+ if init:
+ self.specToPkg.update(init)
+
+ def get(self, spec):
+ return self.specToPkg[spec]
+
+def test_fetch_log(path):
+ return []
+
diff --git a/lib/spack/spack/url.py b/lib/spack/spack/url.py
index 58838306af..6adbfe156d 100644
--- a/lib/spack/spack/url.py
+++ b/lib/spack/spack/url.py
@@ -209,8 +209,8 @@ def parse_version_offset(path):
# e.g. foobar-4.5.1
(r'-((\d+\.)*\d+)$', stem),
- # e.g. foobar-4.5.1b
- (r'-((\d+\.)*\d+\-?([a-z]|rc|RC|tp|TP)\d*)$', stem),
+ # e.g. foobar-4.5.1b, foobar4.5RC, foobar.v4.5.1b
+ (r'[-._]?v?((\d+\.)*\d+[-._]?([a-z]|rc|RC|tp|TP?)\d*)$', stem),
# e.g. foobar-4.5.0-beta1, or foobar-4.50-beta
(r'-((\d+\.)*\d+-beta(\d+)?)$', stem),
diff --git a/lib/spack/spack/virtual.py b/lib/spack/spack/virtual.py
index fa070e6bd5..c77b259d61 100644
--- a/lib/spack/spack/virtual.py
+++ b/lib/spack/spack/virtual.py
@@ -73,10 +73,8 @@ class ProviderIndex(object):
for provided_spec, provider_spec in pkg.provided.iteritems():
if provider_spec.satisfies(spec, deps=False):
provided_name = provided_spec.name
- if provided_name not in self.providers:
- self.providers[provided_name] = {}
- provider_map = self.providers[provided_name]
+ provider_map = self.providers.setdefault(provided_name, {})
if not provided_spec in provider_map:
provider_map[provided_spec] = set()
@@ -133,9 +131,7 @@ class ProviderIndex(object):
if lp_spec.name == rp_spec.name:
try:
const = lp_spec.copy().constrain(rp_spec,deps=False)
- if constrained not in result:
- result[constrained] = set()
- result[constrained].add(const)
+ result.setdefault(constrained, set()).add(const)
except spack.spec.UnsatisfiableSpecError:
continue
return result