diff options
40 files changed, 679 insertions, 85 deletions
diff --git a/lib/spack/docs/.gitignore b/lib/spack/docs/.gitignore index 4d5300fbb9..7701dd9f12 100644 --- a/lib/spack/docs/.gitignore +++ b/lib/spack/docs/.gitignore @@ -1,2 +1,3 @@ +package_list.rst spack*.rst _build diff --git a/lib/spack/docs/Makefile b/lib/spack/docs/Makefile index 4baba720ef..e3068ea10c 100644 --- a/lib/spack/docs/Makefile +++ b/lib/spack/docs/Makefile @@ -22,6 +22,12 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . all: html # +# This autogenerates a package list. +# +package_list: + spack info -r > package_list.rst + +# # This creates a git repository and commits generated html docs. # It them pushes the new branch into THIS repository as gh-pages. # @@ -36,12 +42,14 @@ gh-pages: _build/html touch .nojekyll && \ git init && \ git add . && \ - git commit -m "Initial commit" && \ + git commit -m "Spack Documentation" && \ git push -f $$root master:gh-pages && \ rm -rf .git upload: rsync -avz --rsh=ssh --delete _build/html/ cab:/usr/global/web-pages/lc/www/adept/docs/spack + git push -f origin gh-pages + git push -f github gh-pages apidoc: sphinx-apidoc -T -o . $(PYTHONPATH)/spack @@ -69,9 +77,10 @@ help: @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: + -rm -f package_list.rst -rm -rf $(BUILDDIR)/* $(APIDOC_FILES) -html: apidoc +html: apidoc package_list $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." diff --git a/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html b/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html index 0eccd11178..5ec315e58f 100644 --- a/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html +++ b/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html @@ -13,7 +13,7 @@ <hr/> <p> - © Copyright 2013, + © Copyright 2013-2014, <a href="https://scalability.llnl.gov/">Lawrence Livermore National Laboratory</a>. <br/> Written by Todd Gamblin, <a href="mailto:tgamblin@llnl.gov">tgamblin@llnl.gov</a>, LLNL-CODE-647188 diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py index 700cb00299..b4d49c594d 100644 --- a/lib/spack/docs/conf.py +++ b/lib/spack/docs/conf.py @@ -90,7 +90,7 @@ master_doc = 'index' # General information about the project. project = u'Spack' -copyright = u'2013, Lawrence Livermore National Laboratory' +copyright = u'2013-2014, Lawrence Livermore National Laboratory' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/lib/spack/docs/features.rst b/lib/spack/docs/features.rst index 22dc594d7f..b39dcd3390 100644 --- a/lib/spack/docs/features.rst +++ b/lib/spack/docs/features.rst @@ -93,7 +93,7 @@ creates a simple python file: homepage = "http://www.example.com/" url = "http://www.mr511.de/software/libelf-0.8.13.tar.gz" - versions = { '0.8.13' : '4136d7b4c04df68b686570afa26988ac' } + version('0.8.13', '4136d7b4c04df68b686570afa26988ac') def install(self, prefix): configure("--prefix=%s" % prefix) diff --git a/lib/spack/docs/index.rst b/lib/spack/docs/index.rst index ac0eac93f3..73eff43ab7 100644 --- a/lib/spack/docs/index.rst +++ b/lib/spack/docs/index.rst @@ -48,6 +48,7 @@ Table of Contents packaging_guide site_configuration developer_guide + package_list API Docs <spack> Indices and tables diff --git a/lib/spack/external/argparse.py b/lib/spack/external/argparse.py index c8dfdd3bed..42b64ee7be 100644 --- a/lib/spack/external/argparse.py +++ b/lib/spack/external/argparse.py @@ -108,6 +108,8 @@ import re as _re import sys as _sys import textwrap as _textwrap +from llnl.util.tty.colify import colified + from gettext import gettext as _ try: @@ -2285,8 +2287,8 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def _check_value(self, action, value): # converted value must be one of the choices (if specified) if action.choices is not None and value not in action.choices: - tup = value, ', '.join(map(repr, action.choices)) - msg = _('invalid choice: %r (choose from %s)') % tup + cols = colified(sorted(action.choices), indent=4, tty=True) + msg = _('invalid choice: %r choose from:\n%s') % (value, cols) raise ArgumentError(action, msg) # ======================= diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index 3782aefcce..a70111b915 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -38,7 +38,7 @@ import llnl.util.tty as tty from spack.util.compression import ALLOWED_ARCHIVE_TYPES -def filter_file(regex, repl, *filenames): +def filter_file(regex, repl, *filenames, **kwargs): """Like sed, but uses python regular expressions. Filters every line of file through regex and replaces the file @@ -49,16 +49,31 @@ def filter_file(regex, repl, *filenames): return a suitable replacement string. If it is a string, it can contain ``\1``, ``\2``, etc. to represent back-substitution as sed would allow. + + Keyword Options: + string[=False] If True, treat regex as a plain string. + backup[=True] Make a backup files suffixed with ~ + ignore_absent[=False] Ignore any files that don't exist. """ - # Keep callables intact - if not hasattr(repl, '__call__'): - # Allow strings to use \1, \2, etc. for replacement, like sed + string = kwargs.get('string', False) + backup = kwargs.get('backup', True) + ignore_absent = kwargs.get('ignore_absent', False) + + # Allow strings to use \1, \2, etc. for replacement, like sed + if not callable(repl): unescaped = repl.replace(r'\\', '\\') repl = lambda m: re.sub( r'\\([0-9])', lambda x: m.group(int(x.group(1))), unescaped) + if string: + regex = re.escape(regex) + for filename in filenames: backup = filename + "~" + + if ignore_absent and not os.path.exists(filename): + continue + shutil.copy(filename, backup) try: with closing(open(backup)) as infile: @@ -71,6 +86,10 @@ def filter_file(regex, repl, *filenames): shutil.move(backup, filename) raise + finally: + if not backup: + shutil.rmtree(backup, ignore_errors=True) + def change_sed_delimiter(old_delim, new_delim, *filenames): """Find all sed search/replace commands and change the delimiter. diff --git a/lib/spack/llnl/util/tty/colify.py b/lib/spack/llnl/util/tty/colify.py index 1b04f1012f..ff06241937 100644 --- a/lib/spack/llnl/util/tty/colify.py +++ b/lib/spack/llnl/util/tty/colify.py @@ -37,9 +37,11 @@ import sys import fcntl import termios import struct +from StringIO import StringIO from llnl.util.tty import terminal_size + class ColumnConfig: def __init__(self, cols): self.cols = cols @@ -102,16 +104,20 @@ def colify(elts, **options): output = options.get("output", sys.stdout) indent = options.get("indent", 0) padding = options.get("padding", 2) + tty = options.get('tty', None) # elts needs to be an array of strings so we can count the elements elts = [str(elt) for elt in elts] if not elts: - return + return (0, ()) + + if not tty: + if tty is False or not isatty(output): + for elt in elts: + output.write("%s\n" % elt) - if not isatty(output): - for elt in elts: - output.write("%s\n" % elt) - return + maxlen = max(len(str(s)) for s in elts) + return (1, (maxlen,)) console_cols = options.get("cols", None) if not console_cols: @@ -146,6 +152,17 @@ def colify(elts, **options): if row == rows_last_col: cols -= 1 + return (config.cols, tuple(config.widths)) + + +def colified(elts, **options): + """Invokes the colify() function but returns the result as a string + instead of writing it to an output string.""" + sio = StringIO() + options['output'] = sio + colify(elts, **options) + return sio.getvalue() + if __name__ == "__main__": import optparse diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index bf91a885ca..da7088640f 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -137,9 +137,9 @@ sys_type = None # 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', 'Version', 'when'] +__all__ = ['Package', 'Version', 'when', 'ver'] from spack.package import Package -from spack.version import Version +from spack.version import Version, ver from spack.multimethod import when import llnl.util.filesystem diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py index 5a8109b70f..f9218b9df1 100644 --- a/lib/spack/spack/cmd/checksum.py +++ b/lib/spack/spack/cmd/checksum.py @@ -96,7 +96,7 @@ def checksum(parser, args): if not versions: tty.die("Could not fetch any available versions for %s." % pkg.name) - versions = list(reversed(versions)) + versions = list(reversed(sorted(versions))) urls = [pkg.url_for_version(v) for v in versions] @@ -117,7 +117,5 @@ def checksum(parser, args): if not version_hashes: tty.die("Could not fetch any available versions for %s." % pkg.name) - dict_string = [" '%s' : '%s'," % (v, h) for v, h in version_hashes] - dict_string = ['{'] + dict_string + ["}"] - - tty.msg("Checksummed new versions of %s:" % pkg.name, *dict_string) + version_lines = [" version('%s', '%s')" % (v, h) for v, h in version_hashes] + tty.msg("Checksummed new versions of %s:" % pkg.name, *version_lines) diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py index 3647186a3c..842dcd7faf 100644 --- a/lib/spack/spack/cmd/edit.py +++ b/lib/spack/spack/cmd/edit.py @@ -27,9 +27,10 @@ import string from contextlib import closing import llnl.util.tty as tty -from llnl.util.filesystem import mkdirp +from llnl.util.filesystem import mkdirp, join_path import spack +import spack.cmd from spack.util.naming import mod_to_class description = "Open package files in $EDITOR" @@ -58,31 +59,43 @@ def setup_parser(subparser): '-f', '--force', dest='force', action='store_true', help="Open a new file in $EDITOR even if package doesn't exist.") subparser.add_argument( + '-c', '--command', dest='edit_command', action='store_true', + help="Edit the command with the supplied name instead of a package.") + subparser.add_argument( 'name', nargs='?', default=None, help="name of package to edit") def edit(parser, args): name = args.name - # By default open the directory where packages live. - if not name: - path = spack.packages_path + if args.edit_command: + if not name: + path = spack.cmd.command_path + else: + path = join_path(spack.cmd.command_path, name + ".py") + if not os.path.exists(path): + tty.die("No command named '%s'." % name) + else: - path = spack.db.filename_for_package_name(name) - - if os.path.exists(path): - if not os.path.isfile(path): - tty.die("Something's wrong. '%s' is not a file!" % path) - if not os.access(path, os.R_OK|os.W_OK): - tty.die("Insufficient permissions on '%s'!" % path) - elif not args.force: - tty.die("No package '%s'. Use spack create, or supply -f/--force " - "to edit a new file." % name) + # By default open the directory where packages or commands live. + if not name: + path = spack.packages_path else: - mkdirp(os.path.dirname(path)) - with closing(open(path, "w")) as pkg_file: - pkg_file.write( - package_template.substitute(name=name, class_name=mod_to_class(name))) + path = spack.db.filename_for_package_name(name) + + if os.path.exists(path): + if not os.path.isfile(path): + tty.die("Something's wrong. '%s' is not a file!" % path) + if not os.access(path, os.R_OK|os.W_OK): + tty.die("Insufficient permissions on '%s'!" % path) + elif not args.force: + tty.die("No package '%s'. Use spack create, or supply -f/--force " + "to edit a new file." % name) + else: + mkdirp(os.path.dirname(path)) + with closing(open(path, "w")) as pkg_file: + pkg_file.write( + package_template.substitute(name=name, class_name=mod_to_class(name))) # If everything checks out, go ahead and edit. spack.editor(path) diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py index a1c35269be..29568b8c5d 100644 --- a/lib/spack/spack/cmd/info.py +++ b/lib/spack/spack/cmd/info.py @@ -24,23 +24,98 @@ ############################################################################## import re import textwrap -from llnl.util.tty.colify import colify +from StringIO import StringIO +from llnl.util.tty.colify import * import spack import spack.fetch_strategy as fs description = "Get detailed information on a particular package" def setup_parser(subparser): - subparser.add_argument('name', metavar="PACKAGE", help="name of packages to get info on") + subparser.add_argument('-r', '--rst', action='store_true', + help="List all packages in reStructured text, for docs.") + subparser.add_argument('name', metavar="PACKAGE", nargs='?', help="name of packages to get info on") -def info(parser, args): - pkg = spack.db.get(args.name) +def format_doc(pkg, **kwargs): + """Wrap doc string at 72 characters and format nicely""" + indent = kwargs.get('indent', 0) + + if not pkg.__doc__: + return "" + + doc = re.sub(r'\s+', ' ', pkg.__doc__) + lines = textwrap.wrap(doc, 72) + results = StringIO() + for line in lines: + results.write((" " * indent) + line + "\n") + return results.getvalue() + + +def github_url(pkg): + """Link to a package file on github.""" + return ("https://github.com/scalability-llnl/spack/blob/master/var/spack/packages/%s/package.py" % + pkg.name) + + +def rst_table(elts): + """Print out a RST-style table.""" + cols = StringIO() + ncol, widths = colify(elts, output=cols, tty=True) + header = " ".join("=" * (w-1) for w in widths) + return "%s\n%s%s" % (header, cols.getvalue(), header) + + +def info_rst(): + """Print out information on all packages in restructured text.""" + pkgs = sorted(spack.db.all_packages(), key=lambda s:s.name.lower()) + + print "Package List" + print "==================" + + print "This is a list of things you can install using Spack. It is" + print "automatically generated based on the packages in the latest Spack" + print "release." + print + + print "Spack currently has %d mainline packages:" % len(pkgs) + print + print rst_table("`%s`_" % p.name for p in pkgs) + print + print "-----" + + # Output some text for each package. + for pkg in pkgs: + print + print ".. _%s:" % pkg.name + print + print pkg.name + print "-" * len(pkg.name) + print "Links" + print " * `Homepage <%s>`__" % pkg.homepage + print " * `%s/package.py <%s>`__" % (pkg.name, github_url(pkg)) + print + if pkg.versions: + print "Versions:" + print " " + ", ".join(str(v) for v in reversed(sorted(pkg.versions))) + if pkg.dependencies: + print "Dependencies" + print " " + ", ".join("`%s`_" % d if d != "mpi" else d + for d in pkg.dependencies) + print + print "Description" + print format_doc(pkg, indent=2) + print + print "-----" + + +def info_text(pkg): + """Print out a plain text description of a package.""" print "Package: ", pkg.name print "Homepage: ", pkg.homepage print - print "Versions: " + print "Safe versions: " if not pkg.versions: print("None.") @@ -59,7 +134,7 @@ def info(parser, args): print " None" print - print "Virtual pkgs: " + print "Virtual packages: " if pkg.provided: for spec, when in pkg.provided.items(): print " %s provides %s" % (when, spec) @@ -69,9 +144,17 @@ def info(parser, args): print print "Description:" if pkg.__doc__: - doc = re.sub(r'\s+', ' ', pkg.__doc__) - lines = textwrap.wrap(doc, 72) - for line in lines: - print " " + line + print format_doc(pkg, indent=4) else: print " None" + + +def info(parser, args): + if args.rst: + info_rst() + + else: + if not args.name: + tty.die("You must supply a package name.") + pkg = spack.db.get(args.name) + info_text(pkg) diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py index 2b996371ea..5c7051d6a9 100644 --- a/lib/spack/spack/cmd/list.py +++ b/lib/spack/spack/cmd/list.py @@ -22,15 +22,44 @@ # 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 llnl.util.tty as tty +from external import argparse from llnl.util.tty.colify import colify + import spack +import fnmatch description ="List available spack packages" def setup_parser(subparser): - pass + subparser.add_argument( + 'filter', nargs=argparse.REMAINDER, + help='Optional glob patterns to filter results.') + subparser.add_argument( + '-i', '--insensitive', action='store_true', default=False, + help='Filtering will be case insensitive.') def list(parser, args): + # Start with all package names. + pkgs = spack.db.all_package_names() + + # filter if a filter arg was provided + if args.filter: + def match(p, f): + if args.insensitive: + p = p.lower() + f = f.lower() + return fnmatch.fnmatchcase(p, f) + pkgs = [p for p in pkgs if any(match(p, f) for f in args.filter)] + + # sort before displaying. + sorted_packages = sorted(pkgs, key=lambda s:s.lower()) + # Print all the package names in columns - colify(spack.db.all_package_names()) + indent=0 + if sys.stdout.isatty(): + tty.msg("%d packages." % len(sorted_packages)) + indent=2 + colify(sorted_packages, indent=indent) diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py index e92737ab4e..3fc05d471d 100644 --- a/lib/spack/spack/cmd/location.py +++ b/lib/spack/spack/cmd/location.py @@ -49,6 +49,9 @@ def setup_parser(subparser): '-p', '--package-dir', action='store_true', help="Directory enclosing a spec's package.py file.") directories.add_argument( + '-P', '--packages', action='store_true', + help="Top-level packages directory for Spack.") + directories.add_argument( '-s', '--stage-dir', action='store_true', help="Stage directory for a spec.") directories.add_argument( '-b', '--build-dir', action='store_true', @@ -65,21 +68,39 @@ def location(parser, args): elif args.spack_root: print spack.prefix + elif args.packages: + print spack.db.root + else: - specs = spack.cmd.parse_specs(args.spec, concretize=True) + specs = spack.cmd.parse_specs(args.spec) if not specs: tty.die("You must supply a spec.") if len(specs) != 1: - tty.die("Too many specs. Need only one.") + tty.die("Too many specs. Supply only one.") spec = specs[0] if args.install_dir: - print spec.prefix + # install_dir command matches against installed specs. + matching_specs = spack.db.get_installed(spec) + if not matching_specs: + tty.die("Spec '%s' matches no installed packages." % spec) + + elif len(matching_specs) > 1: + args = ["%s matches multiple packages." % spec, + "Matching packages:"] + args += [" " + str(s) for s in matching_specs] + args += ["Use a more specific spec."] + tty.die(*args) + + print matching_specs[0].prefix elif args.package_dir: + # This one just needs the spec name. print join_path(spack.db.root, spec.name) else: + # These versions need concretized specs. + spec.concretize() pkg = spack.db.get(spec) if args.stage_dir: diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py index 90fbf08241..35e3b898ec 100644 --- a/lib/spack/spack/compiler.py +++ b/lib/spack/spack/compiler.py @@ -190,6 +190,12 @@ class Compiler(object): except ProcessError, e: tty.debug("Couldn't get version for compiler %s" % full_path, e) return None + except Exception, e: + # Catching "Exception" here is fine because it just + # means something went wrong running a candidate executable. + tty.debug("Error while executing candidate compiler %s" % full_path, + "%s: %s" %(e.__class__.__name__, e)) + return None successful = [key for key in parmap(check, checks) if key is not None] return dict(((v, p, s), path) for v, p, s, path in successful) diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py index 097b24bb87..f0d27d590e 100644 --- a/lib/spack/spack/compilers/gcc.py +++ b/lib/spack/spack/compilers/gcc.py @@ -56,7 +56,7 @@ class Gcc(Compiler): return get_compiler_version( fc, '-dumpversion', # older gfortran versions don't have simple dumpversion output. - r'(?:GNU Fortran \(GCC\))?(\d+\.\d+\.\d+)') + r'(?:GNU Fortran \(GCC\))?(\d+\.\d+(?:\.\d+)?)') @classmethod diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py index 779cd76bae..98c78c2e08 100644 --- a/lib/spack/spack/fetch_strategy.py +++ b/lib/spack/spack/fetch_strategy.py @@ -144,6 +144,7 @@ class URLFetchStrategy(FetchStrategy): # Run curl but grab the mime type from the http headers headers = spack.curl('-#', # status bar '-O', # save file to disk + '-f', # fail on >400 errors '-D', '-', # print out HTML headers '-L', self.url, return_output=True, fail_on_error=False) @@ -153,6 +154,10 @@ class URLFetchStrategy(FetchStrategy): if self.archive_file: os.remove(self.archive_file) + if spack.curl.returncode == 22: + # This is a 404. Curl will print the error. + raise FailedDownloadError(url) + if spack.curl.returncode == 60: # This is a certificate error. Suggest spack -k raise FailedDownloadError( diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 160f84f9df..649e772a10 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -891,7 +891,7 @@ def find_versions_of_archive(archive_url, **kwargs): list_depth = kwargs.get('list_depth', 1) if not list_url: - list_url = os.path.dirname(archive_url) + list_url = url.find_list_url(archive_url) # This creates a regex from the URL with a capture group for the # version part of the URL. The capture group is converted to a diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py index aa986662bf..9eae3261c2 100644 --- a/lib/spack/spack/test/__init__.py +++ b/lib/spack/spack/test/__init__.py @@ -36,6 +36,7 @@ import spack.test.install """Names of tests to be included in Spack's test suite""" test_names = ['versions', 'url_parse', + 'url_substitution', 'packages', 'stage', 'spec_syntax', diff --git a/lib/spack/spack/test/url_substitution.py b/lib/spack/spack/test/url_substitution.py new file mode 100644 index 0000000000..db7ddd251d --- /dev/null +++ b/lib/spack/spack/test/url_substitution.py @@ -0,0 +1,73 @@ +############################################################################## +# 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 test does sanity checks on substituting new versions into URLs +""" +import unittest + +import spack +import spack.url as url +from spack.packages import PackageDB + + +class PackageSanityTest(unittest.TestCase): + def test_hypre_url_substitution(self): + base = "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-2.9.0b.tar.gz" + + self.assertEqual(url.substitute_version(base, '2.9.0b'), base) + self.assertEqual( + url.substitute_version(base, '2.8.0b'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-2.8.0b.tar.gz") + self.assertEqual( + url.substitute_version(base, '2.7.0b'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-2.7.0b.tar.gz") + self.assertEqual( + url.substitute_version(base, '2.6.0b'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-2.6.0b.tar.gz") + self.assertEqual( + url.substitute_version(base, '1.14.0b'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-1.14.0b.tar.gz") + self.assertEqual( + url.substitute_version(base, '1.13.0b'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-1.13.0b.tar.gz") + self.assertEqual( + url.substitute_version(base, '2.0.0'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-2.0.0.tar.gz") + self.assertEqual( + url.substitute_version(base, '1.6.0'), + "https://computation-rnd.llnl.gov/linear_solvers/download/hypre-1.6.0.tar.gz") + + + def test_otf2_url_substitution(self): + base = "http://www.vi-hps.org/upload/packages/otf2/otf2-1.4.tar.gz" + + self.assertEqual(url.substitute_version(base, '1.4'), base) + + self.assertEqual( + url.substitute_version(base, '1.3.1'), + "http://www.vi-hps.org/upload/packages/otf2/otf2-1.3.1.tar.gz") + self.assertEqual( + url.substitute_version(base, '1.2.1'), + "http://www.vi-hps.org/upload/packages/otf2/otf2-1.2.1.tar.gz") diff --git a/lib/spack/spack/url.py b/lib/spack/spack/url.py index 902ce9817d..e2fbb19f5d 100644 --- a/lib/spack/spack/url.py +++ b/lib/spack/spack/url.py @@ -78,6 +78,26 @@ class UndetectableNameError(UrlParseError): "Couldn't parse package name in: " + path, path) +def find_list_url(url): + """Finds a good list URL for the supplied URL. This depends on + the site. By default, just assumes that a good list URL is the + dirname of an archive path. For github URLs, this returns the + URL of the project's releases page. + """ + + url_types = [ + # e.g. https://github.com/scalability-llnl/callpath/archive/v1.0.1.tar.gz + (r'^(https://github.com/[^/]+/[^/]+)/archive/', lambda m: m.group(1) + '/releases') + ] + + for pattern, fun in url_types: + match = re.search(pattern, url) + if match: + return fun(match) + else: + return os.path.dirname(url) + + def parse_version_string_with_indices(path): """Try to extract a version string from a filename or URL. This is taken largely from Homebrew's Version class.""" diff --git a/share/spack/csh/spack.csh b/share/spack/csh/spack.csh index 30c4ec1361..b21da23836 100644 --- a/share/spack/csh/spack.csh +++ b/share/spack/csh/spack.csh @@ -46,8 +46,17 @@ set _sp_spec="" set _sp_modtype = "" switch ($_sp_subcommand) case cd: + shift _sp_args # get rid of 'cd' + + set _sp_arg="" + [ $#_sp_args -gt 0 ] && set _sp_arg = ($_sp_args[1]) shift _sp_args - cd `spack location $_sp_args` + + if ( "$_sp_arg" == "-h" ) then + \spack cd -h + else + cd `\spack location $_sp_arg $_sp_args` + endif breaksw case use: case unuse: diff --git a/share/spack/setup-env.sh b/share/spack/setup-env.sh index 6f56d4739b..91b1dc4630 100755 --- a/share/spack/setup-env.sh +++ b/share/spack/setup-env.sh @@ -76,7 +76,12 @@ function spack { # command. case $_sp_subcommand in "cd") - cd $(spack location "$@") + _sp_arg="$1"; shift + if [ "$_sp_arg" = "-h" ]; then + command spack cd -h + else + cd $(spack location $_sp_arg "$@") + fi return ;; "use"|"unuse"|"load"|"unload") diff --git a/var/spack/packages/automaded/package.py b/var/spack/packages/automaded/package.py new file mode 100644 index 0000000000..9fbd93e3b3 --- /dev/null +++ b/var/spack/packages/automaded/package.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 import * + +class Automaded(Package): + """AutomaDeD (Automata-based Debugging for Dissimilar parallel + tasks) is a tool for automatic diagnosis of performance and + correctness problems in MPI applications. It creates + control-flow models of each MPI process and, when a failure + occurs, these models are leveraged to find the origin of + problems automatically. MPI calls are intercepted (using + wrappers) to create the models. When an MPI application hangs, + AutomaDeD creates a progress-dependence graph that helps + finding the process (or group of processes) that caused the hang. + """ + + homepage = "https://github.com/scalability-llnl/AutomaDeD" + url = "https://github.com/scalability-llnl/AutomaDeD/archive/v1.0.tar.gz" + + version('1.0', '16a3d4def2c4c77d0bc4b21de8b3ab03') + + depends_on('mpi') + depends_on('boost') + depends_on('callpath') + + def install(self, spec, prefix): + cmake("-DSTATE_TRACKER_WITH_CALLPATH=ON", *std_cmake_args) + make() + make("install") diff --git a/var/spack/packages/bib2xhtml/package.py b/var/spack/packages/bib2xhtml/package.py new file mode 100644 index 0000000000..7f8e0cfe5a --- /dev/null +++ b/var/spack/packages/bib2xhtml/package.py @@ -0,0 +1,27 @@ +from spack import * +from glob import glob + +class Bib2xhtml(Package): + """bib2xhtml is a program that converts BibTeX files into HTML.""" + homepage = "http://www.spinellis.gr/sw/textproc/bib2xhtml/" + url='http://www.spinellis.gr/sw/textproc/bib2xhtml/bib2xhtml-v3.0-15-gf506.tar.gz' + + version('3.0-15-gf506', 'a26ba02fe0053bbbf2277bdf0acf8645') + + def url_for_version(self, v): + return ('http://www.spinellis.gr/sw/textproc/bib2xhtml/bib2xhtml-v%s.tar.gz' % v) + + def install(self, spec, prefix): + # Add the bst include files to the install directory + bst_include = join_path(prefix.share, 'bib2xhtml') + mkdirp(bst_include) + for bstfile in glob('html-*bst'): + install(bstfile, bst_include) + + # Install the script and point it at the user's favorite perl + # and the bst include directory. + mkdirp(prefix.bin) + install('bib2xhtml', prefix.bin) + filter_file(r'#!/usr/bin/perl', + '#!/usr/bin/env BSTINPUTS=%s perl' % bst_include, + join_path(prefix.bin, 'bib2xhtml')) diff --git a/var/spack/packages/callpath/package.py b/var/spack/packages/callpath/package.py index 6102458291..f8a1eab9f7 100644 --- a/var/spack/packages/callpath/package.py +++ b/var/spack/packages/callpath/package.py @@ -31,6 +31,7 @@ class Callpath(Package): homepage = "https://github.com/scalability-llnl/callpath" url = "https://github.com/scalability-llnl/callpath/archive/v1.0.1.tar.gz" + version('1.0.2', 'b1994d5ee7c7db9d27586fc2dcf8f373') version('1.0.1', '0047983d2a52c5c335f8ba7f5bab2325') depends_on("libelf") diff --git a/var/spack/packages/cmake/package.py b/var/spack/packages/cmake/package.py index ca6553df84..890af9baa9 100644 --- a/var/spack/packages/cmake/package.py +++ b/var/spack/packages/cmake/package.py @@ -25,6 +25,8 @@ from spack import * class Cmake(Package): + """A cross-platform, open-source build system. CMake is a family of + tools designed to build, test and package software.""" homepage = 'https://www.cmake.org' url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz' diff --git a/var/spack/packages/dyninst/package.py b/var/spack/packages/dyninst/package.py index 069237f7ff..df19ac7bc0 100644 --- a/var/spack/packages/dyninst/package.py +++ b/var/spack/packages/dyninst/package.py @@ -25,6 +25,8 @@ from spack import * class Dyninst(Package): + """API for dynamic binary instrumentation. Modify programs while they + are executing without recompiling, re-linking, or re-executing.""" homepage = "https://paradyn.org" url = "http://www.dyninst.org/sites/default/files/downloads/dyninst/8.1.2/DyninstAPI-8.1.2.tgz" list_url = "http://www.dyninst.org/downloads/dyninst-8.x" diff --git a/var/spack/packages/launchmon/package.py b/var/spack/packages/launchmon/package.py index b6773a85bc..bdf988bdd3 100644 --- a/var/spack/packages/launchmon/package.py +++ b/var/spack/packages/launchmon/package.py @@ -25,6 +25,8 @@ from spack import * class Launchmon(Package): + """Software infrastructure that enables HPC run-time tools to + co-locate tool daemons with a parallel job.""" homepage = "http://sourceforge.net/projects/launchmon" url = "http://downloads.sourceforge.net/project/launchmon/launchmon/1.0.1%20release/launchmon-1.0.1.tar.gz" diff --git a/var/spack/packages/libNBC/package.py b/var/spack/packages/libNBC/package.py new file mode 100644 index 0000000000..6d08f3219c --- /dev/null +++ b/var/spack/packages/libNBC/package.py @@ -0,0 +1,43 @@ +############################################################################## +# 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 import * + +class Libnbc(Package): + """LibNBC is a prototypic implementation of a nonblocking + interface for MPI collective operations. Based on ANSI C and + MPI-1, it supports all MPI-1 collective operations in a + nonblocking manner. LibNBC is distributed under the BSD license. + """ + homepage = "http://unixer.de/research/nbcoll/libnbc/" + url = "http://unixer.de/research/nbcoll/libnbc/libNBC-1.1.1.tar.gz" + + version('1.1.1', 'ece5c94992591a9fa934a90e5dbe50ce') + + depends_on("mpi") + + def install(self, spec, prefix): + configure("--prefix=%s" % prefix) + make() + make("install") diff --git a/var/spack/packages/libunwind/package.py b/var/spack/packages/libunwind/package.py index aeadc85eb3..239fcbcfd5 100644 --- a/var/spack/packages/libunwind/package.py +++ b/var/spack/packages/libunwind/package.py @@ -25,6 +25,8 @@ from spack import * class Libunwind(Package): + """A portable and efficient C programming interface (API) to determine + the call-chain of a program.""" homepage = "http://www.nongnu.org/libunwind/" url = "http://download.savannah.gnu.org/releases/libunwind/libunwind-1.1.tar.gz" diff --git a/var/spack/packages/mpich/package.py b/var/spack/packages/mpich/package.py index 19a1efe9c3..57378626ab 100644 --- a/var/spack/packages/mpich/package.py +++ b/var/spack/packages/mpich/package.py @@ -23,6 +23,7 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################## from spack import * +import os class Mpich(Package): """MPICH is a high performance and widely portable implementation of @@ -38,8 +39,41 @@ class Mpich(Package): provides('mpi@:1', when='@1:') def install(self, spec, prefix): - configure( - "--prefix=" + prefix, - "--enable-shared") + config_args = ["--prefix=" + prefix, + "--enable-shared"] + + # TODO: Spack should make it so that you can't actually find + # these compilers if they're "disabled" for the current + # compiler configuration. + if not self.compiler.f77: + config_args.append("--disable-f77") + + if not self.compiler.fc: + config_args.append("--disable-fc") + + configure(*config_args) make() make("install") + + self.filter_compilers() + + + def filter_compilers(self): + """Run after install to make the MPI compilers use the + compilers that Spack built the package with. + + If this isn't done, they'll have CC, CXX, F77, and FC set + to Spack's generic cc, c++, f77, and f90. We want them to + be bound to whatever compiler they were built with. + """ + bin = self.prefix.bin + mpicc = os.path.join(bin, 'mpicc') + mpicxx = os.path.join(bin, 'mpicxx') + mpif77 = os.path.join(bin, 'mpif77') + mpif90 = os.path.join(bin, 'mpif90') + + kwargs = { 'ignore_absent' : True, 'backup' : False, 'string' : True } + filter_file('CC="cc"', 'CC="%s"' % self.compiler.cc, mpicc, **kwargs) + filter_file('CXX="c++"', 'CXX="%s"' % self.compiler.cxx, mpicxx, **kwargs) + filter_file('F77="f77"', 'F77="%s"' % self.compiler.f77, mpif77, **kwargs) + filter_file('FC="f90"', 'FC="%s"' % self.compiler.fc, mpif90, **kwargs) diff --git a/var/spack/packages/netgauge/package.py b/var/spack/packages/netgauge/package.py new file mode 100644 index 0000000000..c2378b0718 --- /dev/null +++ b/var/spack/packages/netgauge/package.py @@ -0,0 +1,43 @@ +############################################################################## +# 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 import * + +class Netgauge(Package): + """Netgauge is a high-precision network parameter measurement + tool. It supports benchmarking of many different network protocols + and communication patterns. The main focus lies on accuracy, + statistical analysis and easy extensibility. + """ + homepage = "http://unixer.de/research/netgauge/" + url = "http://unixer.de/research/netgauge/netgauge-2.4.6.tar.gz" + + version('2.4.6', 'e0e040ec6452e93ca21ccc54deac1d7f') + + depends_on("mpi") + + def install(self, spec, prefix): + configure("--prefix=%s" % prefix) + make() + make("install") diff --git a/var/spack/packages/openmpi/package.py b/var/spack/packages/openmpi/package.py index 0ce09bdd8d..1ef8a8f000 100644 --- a/var/spack/packages/openmpi/package.py +++ b/var/spack/packages/openmpi/package.py @@ -10,22 +10,32 @@ class Openmpi(Package): """ homepage = "http://www.open-mpi.org" - url = "http://www.open-mpi.org/software/ompi/v1.6/downloads/openmpi-1.6.5.tar.bz2" - version('1.6.5', '03aed2a4aa4d0b27196962a2a65fc475') + version('1.8.2', 'ab538ed8e328079d566fc797792e016e', + url='http://www.open-mpi.org/software/ompi/v1.8/downloads/openmpi-1.8.2.tar.gz') - provides('mpi@:2') + version('1.6.5', '03aed2a4aa4d0b27196962a2a65fc475', + url = "http://www.open-mpi.org/software/ompi/v1.6/downloads/openmpi-1.6.5.tar.bz2") + patch('ad_lustre_rwcontig_open_source.patch', when="@1.6.5") + patch('llnl-platforms.patch', when="@1.6.5") - patch('ad_lustre_rwcontig_open_source.patch') - patch('llnl-platforms.patch') + provides('mpi@:2') def install(self, spec, prefix): - configure("--prefix=%s" % prefix, - "--with-platform=contrib/platform/lanl/tlcc2/optimized-nopanasas") + config_args = ["--prefix=%s" % prefix] + + # TODO: use variants for this, e.g. +lanl, +llnl, etc. + # use this for LANL builds, but for LLNL builds, we need: + # "--with-platform=contrib/platform/llnl/optimized" + if self.version == ver("1.6.5"): + confg_args.append("--with-platform=contrib/platform/lanl/tlcc2/optimized-nopanasas") - # TODO: implement variants next, so we can have LLNL and LANL options. - # use above for LANL builds, but for LLNL builds, we need this - # "--with-platform=contrib/platform/llnl/optimized") + # TODO: Spack should make it so that you can't actually find + # these compilers if they're "disabled" for the current + # compiler configuration. + if not self.compiler.f77 and not self.compiler.fc: + config_args.append("--enable-mpi-fortran=no") + configure(*config_args) make() make("install") diff --git a/var/spack/packages/scalasca/package.py b/var/spack/packages/scalasca/package.py index b76d0a7df5..cf7a40c1f5 100644 --- a/var/spack/packages/scalasca/package.py +++ b/var/spack/packages/scalasca/package.py @@ -11,25 +11,47 @@ class Scalasca(Package): # FIXME: add a proper url for your package's homepage here. homepage = "http://www.scalasca.org" - url = "http://apps.fz-juelich.de/scalasca/releases/scalasca/2.1/dist/scalasca-2.1-rc2.tar.gz" + url = "http://apps.fz-juelich.de/scalasca/releases/scalasca/2.1/dist/scalasca-2.1.tar.gz" - version('2.1-rc2', '1a95a39e5430539753e956a7524a756b') + version('2.1', 'bab9c2b021e51e2ba187feec442b96e6', + url = 'http://apps.fz-juelich.de/scalasca/releases/scalasca/2.1/dist/scalasca-2.1.tar.gz' ) depends_on("mpi") depends_on("otf2@1.4") - depends_on("cube") + depends_on("cube@4.2.3") + + backend_user_provided = """\ +CC=cc +CXX=c++ +F77=f77 +FC=f90 +CFLAGS=-fPIC +CXXFLAGS=-fPIC +""" + frontend_user_provided = """\ +CC_FOR_BUILD=cc +CXX_FOR_BUILD=c++ +F77_FOR_BUILD=f70 +FC_FOR_BUILD=f90 +CFLAGS_FOR_BUILD=-fPIC +CXXFLAGS_FOR_BUILD=-fPIC +""" + mpi_user_provided = """\ +MPICC=mpicc +MPICXX=mpicxx +MPIF77=mpif77 +MPIFC=mpif90 +MPI_CFLAGS=-fPIC +MPI_CXXFLAGS=-fPIC +""" def install(self, spec, prefix): configure_args = ["--prefix=%s" % prefix, + "--with-custom-compilers", "--with-otf2=%s" % spec['otf2'].prefix.bin, "--with-cube=%s" % spec['cube'].prefix.bin, "--enable-shared"] - if spec.satisfies('%gcc'): - configure_args.append('--with-nocross-compiler-suite=gcc') - if spec.satisfies('%intel'): - configure_args.append('--with-nocross-compiler-suite=intel') - configure(*configure_args) make() diff --git a/var/spack/packages/scr/package.py b/var/spack/packages/scr/package.py index d480dba62f..d456ecaba0 100644 --- a/var/spack/packages/scr/package.py +++ b/var/spack/packages/scr/package.py @@ -25,6 +25,9 @@ from spack import * class Scr(Package): + """SCR caches checkpoint data in storage on the compute nodes of a + Linux cluster to provide a fast, scalable checkpoint/restart + capability for MPI codes""" homepage = "https://computation-rnd.llnl.gov/scr" url = "http://downloads.sourceforge.net/project/scalablecr/releases/scr-1.1-7.tar.gz" diff --git a/var/spack/packages/spindle/package.py b/var/spack/packages/spindle/package.py index bb0b74ab6f..fd59282ebb 100644 --- a/var/spack/packages/spindle/package.py +++ b/var/spack/packages/spindle/package.py @@ -25,6 +25,11 @@ from spack import * class Spindle(Package): + """Spindle improves the library-loading performance of dynamically + linked HPC applications. Without Spindle large MPI jobs can + overload on a shared file system when loading dynamically + linked libraries, causing site-wide performance problems. + """ homepage = "https://computation-rnd.llnl.gov/spindle" url = "https://github.com/hpc/Spindle/archive/v0.8.1.tar.gz" list_url = "https://github.com/hpc/Spindle/releases" diff --git a/var/spack/packages/stat/package.py b/var/spack/packages/stat/package.py index fbd1418d41..05e74b6a84 100644 --- a/var/spack/packages/stat/package.py +++ b/var/spack/packages/stat/package.py @@ -27,9 +27,5 @@ class Stat(Package): "--with-stackwalker=%s" % spec['dyninst'].prefix, "--with-libdwarf=%s" % spec['libdwarf'].prefix) - # TODO: remove once SPACK-19 is fixed - import shutil - shutil.copy2('/usr/bin/libtool', 'libtool') - make(parallel=False) make("install") diff --git a/var/spack/packages/sundials/package.py b/var/spack/packages/sundials/package.py new file mode 100644 index 0000000000..8b784c8c3c --- /dev/null +++ b/var/spack/packages/sundials/package.py @@ -0,0 +1,39 @@ +############################################################################## +# 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 import * + +class Sundials(Package): + """SUNDIALS (SUite of Nonlinear and DIfferential/ALgebraic equation Solvers)""" + homepage = "http://computation.llnl.gov/casc/sundials/" + url = "http://computation.llnl.gov/casc/sundials/download/code/sundials-2.5.0.tar.gz" + + version('2.5.0', 'aba8b56eec600de3109cfb967aa3ba0f') + + depends_on("mpi") + + def install(self, spec, prefix): + configure("--prefix=%s" % prefix) + make() + make("install") |