From c9e40b725decb25dc0d841754a0ec365a14e735a Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Thu, 12 Dec 2013 04:25:31 -0800 Subject: [PATCH] Start of basic documentation --- bin/spack | 2 +- lib/spack/docs/.gitignore | 2 + lib/spack/docs/Makefile | 12 +- .../docs/_themes/sphinx_rtd_theme/footer.html | 4 +- lib/spack/docs/basic_usage.rst | 233 ++++++++++++++++ lib/spack/docs/conf.py | 10 +- lib/spack/docs/developer_guide.rst | 31 +++ lib/spack/docs/exts/sphinxcontrib/LICENSE | 25 ++ lib/spack/docs/exts/sphinxcontrib/__init__.py | 9 + .../docs/exts/sphinxcontrib/programoutput.py | 263 ++++++++++++++++++ lib/spack/docs/features.rst | 96 +++++++ lib/spack/docs/getting_started.rst | 44 +++ lib/spack/docs/index.rst | 31 ++- lib/spack/docs/packaging_guide.rst | 35 +++ lib/spack/docs/site_configuration.rst | 9 + .../cmd/{install-spack.py => bootstrap.py} | 6 +- lib/spack/spack/cmd/clean.py | 4 +- .../spack/cmd/{sys-type.py => sys_type.py} | 0 lib/spack/spack/package.py | 151 +++++----- lib/spack/spack/relations.py | 17 +- lib/spack/spack/spec.py | 59 ++-- lib/spack/spack/stage.py | 16 +- lib/spack/spack/version.py | 16 +- 23 files changed, 945 insertions(+), 130 deletions(-) create mode 100644 lib/spack/docs/.gitignore create mode 100644 lib/spack/docs/basic_usage.rst create mode 100644 lib/spack/docs/developer_guide.rst create mode 100644 lib/spack/docs/exts/sphinxcontrib/LICENSE create mode 100644 lib/spack/docs/exts/sphinxcontrib/__init__.py create mode 100644 lib/spack/docs/exts/sphinxcontrib/programoutput.py create mode 100644 lib/spack/docs/features.rst create mode 100644 lib/spack/docs/getting_started.rst create mode 100644 lib/spack/docs/packaging_guide.rst create mode 100644 lib/spack/docs/site_configuration.rst rename lib/spack/spack/cmd/{install-spack.py => bootstrap.py} (91%) rename lib/spack/spack/cmd/{sys-type.py => sys_type.py} (100%) diff --git a/bin/spack b/bin/spack index 3ad3a07bbc..0f8159abe5 100755 --- a/bin/spack +++ b/bin/spack @@ -35,7 +35,7 @@ parser.add_argument('-m', '--mock', action='store_true', dest='mock', # each command module implements a parser() function, to which we pass its # subparser for setup. -subparsers = parser.add_subparsers(metavar='COMMAND', dest="command") +subparsers = parser.add_subparsers(metavar='SUBCOMMAND', dest="command") import spack.cmd for cmd in spack.cmd.commands: diff --git a/lib/spack/docs/.gitignore b/lib/spack/docs/.gitignore new file mode 100644 index 0000000000..4d5300fbb9 --- /dev/null +++ b/lib/spack/docs/.gitignore @@ -0,0 +1,2 @@ +spack*.rst +_build diff --git a/lib/spack/docs/Makefile b/lib/spack/docs/Makefile index c413ed2694..99d1cbaff6 100644 --- a/lib/spack/docs/Makefile +++ b/lib/spack/docs/Makefile @@ -7,6 +7,9 @@ SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build +export PYTHONPATH = ../../spack +APIDOC_FILES = spack*.rst + # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter @@ -14,10 +17,13 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext apidoc all: html +apidoc: + sphinx-apidoc -T -o . $(PYTHONPATH)/spack + help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @@ -41,9 +47,9 @@ help: @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: - -rm -rf $(BUILDDIR)/* + -rm -rf $(BUILDDIR)/* $(APIDOC_FILES) -html: +html: apidoc $(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 e42d753ff6..6007d5eb90 100644 --- a/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html +++ b/lib/spack/docs/_themes/sphinx_rtd_theme/footer.html @@ -24,7 +24,7 @@ {%- if last_updated %} {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %} {%- endif %} -

- +    {% trans %}Sphinx theme provided by Read the Docs{% endtrans %} +

diff --git a/lib/spack/docs/basic_usage.rst b/lib/spack/docs/basic_usage.rst new file mode 100644 index 0000000000..425fe0c288 --- /dev/null +++ b/lib/spack/docs/basic_usage.rst @@ -0,0 +1,233 @@ +Basic usage +===================== + +Nearly everything you do wtih spack will involve the ``spack`` +command. Like many well-known tools (``git``, ``cvs``, ``svn``, +``yum``, ``port``, ``apt-get``, etc.), ``spack`` is generally called +with a *subcommand* indicating the action you want to perform. + +Getting Help +----------------------- + + +``spack help`` +~~~~~~~~~~~~~~~~~~~~~~ + +The first subcommand you should know is ``spack help``. Run with no +arguments, it will give a list of all spack options and subcommands: + +.. command-output:: spack help + +If you want help on the usage of a particular subcommand, you can pass +it as an argument to ``spack help``: + +.. command-output:: spack help install + +Alternately, you can use ``spack -h`` in place of ``spack help``, or +``spack -h`` to get help on a particular subcommand. + + +Viewing available packages +------------------------------ + +The first thing you will likely want to do with spack is find out what +software is available to install. There are two main commands for +this: ``spack list`` and ``spack info``. + + +``spack list`` +~~~~~~~~~~~~~~~~ + +The ``spack list`` command does what you might expect. it prints out a +list of all the available packages you can install. Use it like +this: + +.. command-output:: spack list + +The packages are listed by name in alphabetical order. If you just +want to see *installed* packages, you should use ``spack list -i`` + + +``spack info`` +~~~~~~~~~~~~~~~~ + +To get information on a particular package from the full list, you can +run ``spack info ``. e.g., for ``mpich``: + +.. command-output:: spack info mpich + +This gives basic information about the package, such as where it can +be downloaded, what other packages it depends on, virtual package +information, and a text description, if one is available. We'll give +more details on dependencies and virtual dependencies later in this +guide. + + +Installing and uninstalling +------------------------------ + +``spack install`` +~~~~~~~~~~~~~~~~~~~~~ + +You can install any package from ``spack list``, using ``spack +install``. In the simplest case, if you just want the latest version +and you don't care about any configuration, you can just run ``spack +install ``: + +.. code-block:: sh + + spack install mpileaks + +This will fetch the tarball for ``mpileaks``, expand it, verify that +it was donwloaded without errors, build the package, and install it in +its own directory in ``$SPACK_HOME/opt``. If the requested packages +depends on other packages in order to build, then they will also be +fetched and installed. + +Spack also allows you to ask for *specific* configurations of a +package. For example, if you want to install something with a +specific version, you can add ``@`` after the package name, followed +by the version you want: + +.. code-block:: sh + + spack install mpich@3.0.4 + +You can install as many versions of the same pacakge as you want, and +they will not interfere with each other. Spack installs each package +into its own unique prefix. If you or another user links a library +against soething you install using Spack, it will continue to work +until you explicitly uninstall it. + +The version isn't all that you can customize on a spack command line. +Spack can install many configurations, with different versions, +compilers, compiler versions, compile-time options (variants), and +even architectures (e.g., on a machine that requires cross-compiling). +Spack is also unique in that it lets you customize the *dependencies* +you build a package with. That is, you could have two configurations +of the same version of a package: one built with boost 1.39.0, and the +other version built with version 1.43.0. + +Spack calls the descriptor used to refer to a particular package +configuration a **spec**. In the command lines above, both +``mpileaks`` and ``mpileaks@3.0.4`` are specs. Specs and their syntax +are covered in more detail in :ref:`sec-specs`. + + + + +``spack uninstall`` +~~~~~~~~~~~~~~~~~~~~~ + +To uninstall a package, just type ``spack uninstall ``. This +will completely remove the directory in which the package was installed. + +.. code-block:: sh + + spack uninstall mpich + +If there are other installed packages depend on the package you're +uninstalling, spack will issue a warning to this effect. In general, +you should remove the other packages *before* removing the package +they depend on, or you risk breaking packages on your system. If you +still want to remove the package without regard for its dependencies, +you can run ``spack uninstall -f `` to override Spack's +warning. + +If you have more than one version of the same package installed, spack +may not be able to figure out which on eyou want uninstalled. For +example, if you have both ``mpich@3.0.2`` and ``mpich@3.1`` installed, +and you type ``spack uninstall mpich``, then Spack will not know which +one you're referring to, and it will ask you to be more specific by +providing a version to differentiate, For example, ``spack uninstall +mpich@3.1`` is unambiguous. + + +.. _sec-specs: + +Specs +------------------------- + +Dependencies +------------------------- + +Virtual dependencies +------------------------- + +Versions, compilers, and architectures +---------------------------------------- + +``spack versions`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +``spack compilers`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +Architectures +~~~~~~~~~~~~~~~~~~~~~~~~ + +Spack's specs allow insatllations for multiple architectures to coexist +within the same prefix. It is also intended to support multiple +architecutres for cross-compilation. + + +Package lifecycle +------------------------------ + +The ``spack install`` command performs a number of tasks before it +finally installs each package. It downloads an archive, expands it in +a temporary directory, and only then performs the installation. Spack +has several commands that allow finer-grained control over each of +these stages of the build process. + + +``spack fetch`` +~~~~~~~~~~~~~~~~~ + +This is the first step of ``spack install``. It takes a spec and +determines the correct download URL to use for the requested package +version. It then downloads the archive, checks it against an MD5 +checksum, and stores it in a staging directory if the check was +successful. The staging directory will be located under +``$SPACK_HOME/var/spack``. + +If run after the archive has already been downloaded, ``spack fetch`` +is idempotent and will not download the archive again. + +``spack stage`` +~~~~~~~~~~~~~~~~~ + +This is the second step in installation after ``spack fetch``. It +expands the downloaded archive in its temporary directory, where it +will be built by ``spack install``. If the archive has already been +expanded, then this command does nothing. + +``spack clean`` +~~~~~~~~~~~~~~~~~ + +This command has several variations, each undoing one of the +installation tasks. They are: + +``spack clean`` + Runs ``make clean`` in the expanded archive directory. This is useful + if an attempted build failed, and something needs to be changed to get + a package to build. If a particular package does not have a ``make clean`` + target, this will do nothing. + +``spack clean -w`` or ``spack clean --work`` + This deletes the entire build directory and re-expands it from the downloaded + archive. This is useful if a package does not support a proper ``make clean`` + target. + +``spack clean -d`` or ``spack clean --dist`` + This deletes the build directory *and* the downloaded archive. If + ``fetch``, ``stage``, or ``install`` are run again after this, the + process will start from scratch, and the archive archive will be + downloaded again. Useful if somehow a bad archive is downloaded + accidentally and needs to be cleaned out of the staging area. + + + + +``spack purge`` +~~~~~~~~~~~~~~~~~ diff --git a/lib/spack/docs/conf.py b/lib/spack/docs/conf.py index df472fd4c4..68b70f359f 100644 --- a/lib/spack/docs/conf.py +++ b/lib/spack/docs/conf.py @@ -16,7 +16,10 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath('exts')) +os.environ['PATH'] += os.pathsep + '../../../bin' + +todo_include_todos = True # -- General configuration ----------------------------------------------------- @@ -25,7 +28,10 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.graphviz', + 'sphinx.ext.todo', + 'sphinxcontrib.programoutput'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/lib/spack/docs/developer_guide.rst b/lib/spack/docs/developer_guide.rst new file mode 100644 index 0000000000..79bb71b182 --- /dev/null +++ b/lib/spack/docs/developer_guide.rst @@ -0,0 +1,31 @@ +Developer Guide +===================== + + +Spec objects +------------------------- + +Package objects +------------------------- + +Stage objects +------------------------- + +Writing commands +------------------------- + +Unit tests +------------------------- + +Unit testing +------------------------- + + +Developer commands +------------------------- + +``spack doc`` +~~~~~~~~~~~~~~~~~ + +``spack test`` +~~~~~~~~~~~~~~~~~ diff --git a/lib/spack/docs/exts/sphinxcontrib/LICENSE b/lib/spack/docs/exts/sphinxcontrib/LICENSE new file mode 100644 index 0000000000..43b87a5992 --- /dev/null +++ b/lib/spack/docs/exts/sphinxcontrib/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2010, 2011, 2012 Sebastian Wiesner +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/spack/docs/exts/sphinxcontrib/__init__.py b/lib/spack/docs/exts/sphinxcontrib/__init__.py new file mode 100644 index 0000000000..591cf0e16e --- /dev/null +++ b/lib/spack/docs/exts/sphinxcontrib/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +""" + sphinxcontrib + ~~~~~~~~~~~~~ + + Contains 3rd party Sphinx extensions. +""" + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/lib/spack/docs/exts/sphinxcontrib/programoutput.py b/lib/spack/docs/exts/sphinxcontrib/programoutput.py new file mode 100644 index 0000000000..3f6a4f1595 --- /dev/null +++ b/lib/spack/docs/exts/sphinxcontrib/programoutput.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2010, 2011, 2012, Sebastian Wiesner +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: + +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" + sphinxcontrib.programoutput + =========================== + + This extension provides a directive to include the output of commands as + literal block while building the docs. + + .. moduleauthor:: Sebastian Wiesner +""" + +from __future__ import (print_function, division, unicode_literals, + absolute_import) + +import sys +import os +import shlex +from subprocess import Popen, PIPE, STDOUT +from collections import defaultdict, namedtuple + +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst.directives import flag, unchanged, nonnegative_int + + +__version__ = '0.9' + + +class program_output(nodes.Element): + pass + + +def _slice(value): + parts = [int(v.strip()) for v in value.split(',')] + if len(parts) > 2: + raise ValueError('too many slice parts') + return tuple((parts + [None] * 2)[:2]) + + +class ProgramOutputDirective(rst.Directive): + has_content = False + final_argument_whitespace = True + required_arguments = 1 + + option_spec = dict(shell=flag, prompt=flag, nostderr=flag, + ellipsis=_slice, extraargs=unchanged, + returncode=nonnegative_int, cwd=unchanged) + + def run(self): + env = self.state.document.settings.env + + node = program_output() + node.line = self.lineno + node['command'] = self.arguments[0] + + if self.name == 'command-output': + node['show_prompt'] = True + else: + node['show_prompt'] = 'prompt' in self.options + + node['hide_standard_error'] = 'nostderr' in self.options + node['extraargs'] = self.options.get('extraargs', '') + _, cwd = env.relfn2path(self.options.get('cwd', '/')) + node['working_directory'] = cwd + node['use_shell'] = 'shell' in self.options + node['returncode'] = self.options.get('returncode', 0) + if 'ellipsis' in self.options: + node['strip_lines'] = self.options['ellipsis'] + return [node] + + +_Command = namedtuple( + 'Command', 'command shell hide_standard_error working_directory') + + +class Command(_Command): + """ + A command to be executed. + """ + + def __new__(cls, command, shell=False, hide_standard_error=False, + working_directory='/'): + if isinstance(command, list): + command = tuple(command) + # `chdir()` resolves symlinks, so we need to resolve them too for + # caching to make sure that different symlinks to the same directory + # don't result in different cache keys. Also normalize paths to make + # sure that identical paths are also equal as strings. + working_directory = os.path.normpath(os.path.realpath( + working_directory)) + return _Command.__new__(cls, command, shell, hide_standard_error, + working_directory) + + @classmethod + def from_program_output_node(cls, node): + """ + Create a command from a :class:`program_output` node. + """ + extraargs = node.get('extraargs', '') + command = (node['command'] + ' ' + extraargs).strip() + return cls(command, node['use_shell'], + node['hide_standard_error'], node['working_directory']) + + def execute(self): + """ + Execute this command. + + Return the :class:`~subprocess.Popen` object representing the running + command. + """ + if self.shell: + if sys.version_info[0] < 3 and isinstance(self.command, unicode): + command = self.command.encode(sys.getfilesystemencoding()) + else: + command = self.command + else: + if sys.version_info[0] < 3 and isinstance(self.command, unicode): + command = shlex.split(self.command.encode( + sys.getfilesystemencoding())) + elif isinstance(self.command, str): + command = shlex.split(self.command) + else: + command = self.command + return Popen(command, shell=self.shell, stdout=PIPE, + stderr=PIPE if self.hide_standard_error else STDOUT, + cwd=self.working_directory) + + def get_output(self): + """ + Get the output of this command. + + Return a tuple ``(returncode, output)``. ``returncode`` is the + integral return code of the process, ``output`` is the output as + unicode string, with final trailing spaces and new lines stripped. + """ + process = self.execute() + output = process.communicate()[0].decode( + sys.getfilesystemencoding(), 'replace').rstrip() + return process.returncode, output + + def __str__(self): + if isinstance(self.command, tuple): + return repr(list(self.command)) + return repr(self.command) + + +class ProgramOutputCache(defaultdict): + """ + Execute command and cache their output. + + This class is a mapping. Its keys are :class:`Command` objects represeting + command invocations. Its values are tuples of the form ``(returncode, + output)``, where ``returncode`` is the integral return code of the command, + and ``output`` is the output as unicode string. + + The first time, a key is retrieved from this object, the command is + invoked, and its result is cached. Subsequent access to the same key + returns the cached value. + """ + + def __missing__(self, command): + """ + Called, if a command was not found in the cache. + + ``command`` is an instance of :class:`Command`. + """ + result = command.get_output() + self[command] = result + return result + + +def run_programs(app, doctree): + """ + Execute all programs represented by ``program_output`` nodes in + ``doctree``. Each ``program_output`` node in ``doctree`` is then + replaced with a node, that represents the output of this program. + + The program output is retrieved from the cache in + ``app.env.programoutput_cache``. + """ + if app.config.programoutput_use_ansi: + # enable ANSI support, if requested by config + from sphinxcontrib.ansi import ansi_literal_block + node_class = ansi_literal_block + else: + node_class = nodes.literal_block + + cache = app.env.programoutput_cache + + for node in doctree.traverse(program_output): + command = Command.from_program_output_node(node) + try: + returncode, output = cache[command] + except EnvironmentError as error: + error_message = 'Command {0} failed: {1}'.format(command, error) + error_node = doctree.reporter.error(error_message, base_node=node) + node.replace_self(error_node) + else: + if returncode != node['returncode']: + app.warn('Unexpected return code {0} from command {1}'.format( + returncode, command)) + + # replace lines with ..., if ellipsis is specified + if 'strip_lines' in node: + lines = output.splitlines() + start, stop = node['strip_lines'] + lines[start:stop] = ['...'] + output = '\n'.join(lines) + + if node['show_prompt']: + tmpl = app.config.programoutput_prompt_template + output = tmpl.format(command=node['command'], output=output, + returncode=returncode) + + new_node = node_class(output, output) + new_node['language'] = 'text' + node.replace_self(new_node) + + +def init_cache(app): + """ + Initialize the cache for program output at + ``app.env.programoutput_cache``, if not already present (e.g. being + loaded from a pickled environment). + + The cache is of type :class:`ProgramOutputCache`. + """ + if not hasattr(app.env, 'programoutput_cache'): + app.env.programoutput_cache = ProgramOutputCache() + + +def setup(app): + app.add_config_value('programoutput_use_ansi', False, 'env') + app.add_config_value('programoutput_prompt_template', + '$ {command}\n{output}', 'env') + app.add_directive('program-output', ProgramOutputDirective) + app.add_directive('command-output', ProgramOutputDirective) + app.connect(str('builder-inited'), init_cache) + app.connect(str('doctree-read'), run_programs) diff --git a/lib/spack/docs/features.rst b/lib/spack/docs/features.rst new file mode 100644 index 0000000000..7039563698 --- /dev/null +++ b/lib/spack/docs/features.rst @@ -0,0 +1,96 @@ +Feature Overview +================== + +This is an overview of features that make Spack different from other +`package managers `_ +and `port systems `_. + +Simple package installation +---------------------------- + +Installing packages is easy with Spack when you just want the default +version. This installs the latest version of mpileaks and all of its +dependencies: + +.. code-block:: sh + + $ spack install mpileaks + +Custom versions & configurations +------------------------------------------- + +If there's some aspect of your package that you want to customize, you +can do that too. + +.. code-block:: sh + + # Install a particular version by appending @ + $ spack install mpileaks@1.1.2 + + # Or your favorite compiler (and its version), with % + $ spack install mpileaks@1.1.2 %gcc@4.7.3 + + # Add some special compile-time options with + + $ spack install mpileaks@1.1.2 %gcc@4.7.3 +debug + + # Cross-compile for a different architecture with = + $ spack install mpileaks@1.1.2 =bgqos_0 + +Customize dependencies +------------------------------------- + +You can customize package dependencies with ``^``. Suppose that +``mpileaks`` depends indirectly on ``libelf`` and ``libdwarf``. Using +``^``, you can add custom configurations for the dependencies, too. + +.. code-block:: sh + + # Install mpileaks and link it with specific versions of libelf and libdwarf + $ spack install mpileaks@1.1.2 %gcc@4.7.3 +debug ^libelf@0.8.12 ^libdwarf@20130729+debug + + +Non-destructive installs +------------------------------------- + +Spack installs every unique package configuration in its own prefix, +so you can install as many different versions and configurations as +you want. New installs will not break existing ones. + + +Packages can peacefully coexist +------------------------------------- + +Spack uses ``RPATH`` everywhere, so users do not need to customize +``LD_LIBRARY_PATH``. If you use a library or run a program, it will +run the way you built it. + + +Creating packages is easy +------------------------------------- + +To create your own packages, give spack the tarball URL. Spack +creates all the boilerplate for you. + +.. code-block:: sh + + $ spack create http://scalability.llnl.gov/mpileaks/downloads/mpileaks-1.0.tar.gz + +Creates ``mpileaks.py``: + +.. code-block:: python + + from spack import * + + class Mpileaks(Package): + homepage = "http://www.example.com/" + url = "http://scalability.llnl.gov/mpileaks/downloads/mpileaks-1.0.tar.gz" + md5 = "4136d7b4c04df68b686570afa26988ac" + + def install(self, prefix): + configure("--prefix=%s" % prefix) + make() + make("install") + +Packages are pure python, so you have complete freedom when writing +build code. Spack also provides a number of feature that make it +easier to write packages. diff --git a/lib/spack/docs/getting_started.rst b/lib/spack/docs/getting_started.rst new file mode 100644 index 0000000000..ea7b949fe6 --- /dev/null +++ b/lib/spack/docs/getting_started.rst @@ -0,0 +1,44 @@ +Getting Started +==================== + +Download +-------------------- + +Getting spack is easy. Clone it using `git `_ +with the following command: + +.. code-block:: sh + + $ git clone ssh://git@cz-stash.llnl.gov:7999/scale/spack.git + +This will create a directory called ``spack``. We'll assume that the +full path to this directory is in some environment called +``SPACK_HOME``. Add ``$SPACK_HOME/bin`` to your path and you're ready +to go: + +.. code-block:: sh + + $ export PATH=spack/bin:$SPACK_HOME + $ spack install mpich + +In general, most of your interactions with Spack will be through the +``spack`` command. + + +Install +-------------------- + +You don't need to install Spack; it's ready to run as soon as you +clone it from git. + +You may want to run it out of a prefix other than the git repository +you cloned. The ``spack bootstrap`` command provides this +functionality. To install spack in a new directory, simply type: + +.. code-block:: sh + + $ spack bootstrap /my/favorite/prefix + +This will install a new spack script in /my/favorite/prefix/bin, which +you can use just like you would the regular spack script. Each copy +of spack installs packages into its own ``$PREFIX/opt`` directory. diff --git a/lib/spack/docs/index.rst b/lib/spack/docs/index.rst index 089c1fe5ee..092d696983 100644 --- a/lib/spack/docs/index.rst +++ b/lib/spack/docs/index.rst @@ -3,15 +3,39 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to Spack's documentation! +Spack Documentation ================================= -Contents: +Spack builds and installs software the way you want it. Other tools +let you install the latest version once. Spack lets you install the +versions you want, built with the compilers, libraries, and options +you want. Spack is non-destructive; installing a new version does not +break your old installs. See the :doc:`features` for more highlights. + +Get spack and install your first package: + +.. code-block:: sh + + $ git clone ssh://git@cz-stash.llnl.gov:7999/scale/spack.git + $ cd spack/bin + $ ./spack install mpich + +If you're new to spack and want to start using it, see :doc:`getting_started`, +or refer to the full manual below. + +Table of Contents +--------------------- .. toctree:: :maxdepth: 2 - + features + getting_started + basic_usage + packaging_guide + site_configuration + developer_guide + API Docs Indices and tables ================== @@ -19,4 +43,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/lib/spack/docs/packaging_guide.rst b/lib/spack/docs/packaging_guide.rst new file mode 100644 index 0000000000..371c85df4c --- /dev/null +++ b/lib/spack/docs/packaging_guide.rst @@ -0,0 +1,35 @@ +Packaging Guide +===================== + + +Package files +------------------------- + + +Dependencies +------------------------- + + +Virtual dependencies +------------------------- + + +Virtual dependencies +------------------------- + + + +Packaging commands +------------------------- + +``spack edit`` +~~~~~~~~~~~~~~~~~~~~ + +``spack create`` +~~~~~~~~~~~~~~~~~~~~ + +``spack checksum`` +~~~~~~~~~~~~~~~~~~~~ + +``spack graph`` +~~~~~~~~~~~~~~~~~~~~ diff --git a/lib/spack/docs/site_configuration.rst b/lib/spack/docs/site_configuration.rst new file mode 100644 index 0000000000..bc790db257 --- /dev/null +++ b/lib/spack/docs/site_configuration.rst @@ -0,0 +1,9 @@ +Site-specific configuration +=================================== + +Temporary space +---------------------------- + + +Concretization policies +---------------------------- diff --git a/lib/spack/spack/cmd/install-spack.py b/lib/spack/spack/cmd/bootstrap.py similarity index 91% rename from lib/spack/spack/cmd/install-spack.py rename to lib/spack/spack/cmd/bootstrap.py index a8663e97ac..66dda5dbc9 100644 --- a/lib/spack/spack/cmd/install-spack.py +++ b/lib/spack/spack/cmd/bootstrap.py @@ -17,7 +17,7 @@ def get_origin_url(): return origin_url.strip() -def install_spack(parser, args): +def bootstrap(parser, args): origin_url = get_origin_url() prefix = args.prefix @@ -28,7 +28,7 @@ def install_spack(parser, args): files_in_the_way = os.listdir(prefix) if files_in_the_way: - tty.die("There are already files there! Delete these files before installing spack.", + tty.die("There are already files there! Delete these files before boostrapping spack.", *files_in_the_way) tty.msg("Installing:", @@ -41,5 +41,5 @@ def install_spack(parser, args): check_call(['git', 'fetch', 'origin', 'master:refs/remotes/origin/master', '-n', '-q']) check_call(['git', 'reset', '--hard', 'origin/master', '-q']) - tty.msg("Successfully installed spack in %s" % prefix, + tty.msg("Successfully created a new spack in %s" % prefix, "Run %s/bin/spack to use this installation." % prefix) diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py index 52eaf2893f..ff5ae4ba2c 100644 --- a/lib/spack/spack/cmd/clean.py +++ b/lib/spack/spack/cmd/clean.py @@ -9,9 +9,9 @@ def setup_parser(subparser): subparser.add_argument('-c', "--clean", action="store_true", dest='clean', - help="run make clean in the stage directory (default)") + help="run make clean in the build directory (default)") subparser.add_argument('-w', "--work", action="store_true", dest='work', - help="delete and re-expand the entire stage directory") + help="delete the build directory and re-expand it from its archive.") subparser.add_argument('-d', "--dist", action="store_true", dest='dist', help="delete the downloaded archive.") subparser.add_argument('packages', nargs=argparse.REMAINDER, diff --git a/lib/spack/spack/cmd/sys-type.py b/lib/spack/spack/cmd/sys_type.py similarity index 100% rename from lib/spack/spack/cmd/sys-type.py rename to lib/spack/spack/cmd/sys_type.py diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index a28bfae411..69392efdd5 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -35,8 +35,8 @@ class Package(object): """This is the superclass for all spack packages. - The Package class - ================== + ***The Package class*** + Package is where the bulk of the work of installing packages is done. A package defines how to fetch, verfiy (via, e.g., md5), build, and @@ -53,35 +53,39 @@ class Package(object): in this directory. Spack automatically scans the python files there and figures out which one to import when you invoke it. - An example package - ==================== + **An example package** + Let's look at the cmake package to start with. This package lives in $prefix/lib/spack/spack/packages/cmake.py: - from spack import * - class Cmake(Package): - homepage = 'https://www.cmake.org' - url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz' - md5 = '097278785da7182ec0aea8769d06860c' + .. code-block:: python - def install(self, prefix): - configure('--prefix=%s' % prefix, - '--parallel=%s' % make_jobs) - make() - make('install') + from spack import * + class Cmake(Package): + homepage = 'https://www.cmake.org' + url = 'http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz' + md5 = '097278785da7182ec0aea8769d06860c' + + def install(self, prefix): + configure('--prefix=%s' % prefix, + '--parallel=%s' % make_jobs) + make() + make('install') + + **Naming conventions** - Naming conventions - --------------------- There are two names you should care about: - 1. The module name, 'cmake'. - - User will refers to this name, e.g. 'spack install cmake'. - - Corresponds to the name of the file, 'cmake.py', and it can - include _, -, and numbers (it can even start with a number). + 1. The module name, ``cmake``. - 2. The class name, "Cmake". This is formed by converting -'s or _'s - in the module name to camel case. If the name starts with a number, - we prefix the class name with 'Num_'. Examples: + * User will refers to this name, e.g. 'spack install cmake'. + * Corresponds to the name of the file, 'cmake.py', and it can + include ``_``, ``-``, and numbers (it can even start with a + number). + + 2. The class name, "Cmake". This is formed by converting `-` or + ``_`` in the module name to camel case. If the name starts with + a number, we prefix the class name with ``Num_``. Examples: Module Name Class Name foo_bar FooBar @@ -91,23 +95,28 @@ def install(self, prefix): The class name is what spack looks for when it loads a package module. - Required Attributes - --------------------- + **Required Attributes** + Aside from proper naming, here is the bare minimum set of things you need when you make a package: - homepage informational URL, so that users know what they're - installing. - url URL of the source archive that spack will fetch. + homepage + informational URL, so that users know what they're + installing. - md5 md5 hash of the source archive, so that we can - verify that it was downloaded securely and correctly. + url + URL of the source archive that spack will fetch. - install() This function tells spack how to build and install the - software it downloaded. + md5 + md5 hash of the source archive, so that we can + verify that it was downloaded securely and correctly. + + install() + This function tells spack how to build and install the + software it downloaded. + + **Optional Attributes** - Optional Attributes - --------------------- You can also optionally add these attributes, if needed: list_url Webpage to scrape for available version strings. Default is the @@ -121,8 +130,8 @@ def install(self, prefix): your package needs special version formatting in its URL. boost is an example of a package that needs this. - Creating Packages - =================== + ***Creating Packages*** + As a package creator, you can probably ignore most of the preceding information, because you can use the 'spack create' command to do it all automatically. @@ -130,8 +139,8 @@ def install(self, prefix): You as the package creator generally only have to worry about writing your install function and specifying dependencies. - spack create - ---------------- + **spack create** + Most software comes in nicely packaged tarballs, like this one: http://www.cmake.org/files/v2.8/cmake-2.8.10.2.tar.gz @@ -147,15 +156,17 @@ def install(self, prefix): Once this skeleton code is generated, spack pops up the new package in your $EDITOR so that you can modify the parts that need changes. - Dependencies - --------------- + **Dependencies** + If your package requires another in order to build, you can specify that like this: - class Stackwalker(Package): - ... - depends_on("libdwarf") - ... + .. code-block:: python + + class Stackwalker(Package): + ... + depends_on("libdwarf") + ... This tells spack that before it builds stackwalker, it needs to build the libdwarf package as well. Note that this is the module name, not @@ -170,8 +181,8 @@ class Stackwalker(Package): it will find the dependencies automatically. - The Install Function - ---------------------- + **The Install Function** + The install function is designed so that someone not too terribly familiar with Python could write a package installer. For example, we put a number of commands in install scope that you can use almost like shell commands. @@ -195,47 +206,58 @@ class Stackwalker(Package): them are created and set on the module. - Parallel Builds - ------------------- + **Parallel Builds** + By default, Spack will run make in parallel when you run make() in your install function. Spack figures out how many cores are available on your system and runs make with -j. If you do not want this behavior, you can explicitly mark a package not to use parallel make: - class SomePackage(Package): - ... - parallel = False - ... + .. code-block:: python + + class SomePackage(Package): + ... + parallel = False + ... This changes thd default behavior so that make is sequential. If you still want to build some parts in parallel, you can do this in your install function: - make(parallel=True) + .. code-block:: python + + make(parallel=True) Likewise, if you do not supply parallel = True in your Package, you can keep the default parallel behavior and run make like this when you want a sequential build: - make(parallel=False) + .. code-block:: python + + make(parallel=False) + + **Package Lifecycle** - Package Lifecycle - ================== This section is really only for developers of new spack commands. A package's lifecycle over a run of Spack looks something like this: - p = Package() # Done for you by spack + .. code-block:: python - p.do_fetch() # called by spack commands in spack/cmd. - p.do_stage() # see spack.stage.Stage docs. - p.do_install() # calls package's install() function - p.do_uninstall() + p = Package() # Done for you by spack + + p.do_fetch() # called by spack commands in spack/cmd. + p.do_stage() # see spack.stage.Stage docs. + p.do_install() # calls package's install() function + p.do_uninstall() There are also some other commands that clean the build area: - p.do_clean() # runs make clean - p.do_clean_work() # removes the build directory and + + .. code-block:: python + + p.do_clean() # runs make clean + p.do_clean_work() # removes the build directory and # re-expands the archive. - p.do_clean_dist() # removes the stage directory entirely + p.do_clean_dist() # removes the stage directory entirely The convention used here is that a do_* function is intended to be called internally by Spack commands (in spack.cmd). These aren't for package @@ -244,6 +266,7 @@ class SomePackage(Package): Package creators override functions like install() (all of them do this), clean() (some of them do this), and others to provide custom behavior. + """ # diff --git a/lib/spack/spack/relations.py b/lib/spack/spack/relations.py index 4be4bb6cad..b6913d69b8 100644 --- a/lib/spack/spack/relations.py +++ b/lib/spack/spack/relations.py @@ -68,9 +68,11 @@ def _caller_locals(): del stack -def _ensure_caller_is_spack_package(): - """Make sure that the caller is a spack package. If it's not, - raise ScopeError. if it is, return its name.""" +def _get_calling_package_name(): + """Make sure that the caller is a class definition, and return + the module's name. This is useful for getting the name of + spack packages from inside a relation function. + """ stack = inspect.stack() try: # get calling function name (the relation) @@ -85,9 +87,6 @@ def _ensure_caller_is_spack_package(): raise ScopeError(relation) module_name = caller_locals['__module__'] - if not module_name.startswith(packages_module()): - raise ScopeError(relation) - base_name = module_name.split('.')[-1] return base_name @@ -121,7 +120,7 @@ def _parse_local_spec(spec_like, pkg_name): """Adds a dependencies local variable in the locals of the calling class, based on args. """ def depends_on(*specs): - pkg = _ensure_caller_is_spack_package() + pkg = _get_calling_package_name() dependencies = _caller_locals().setdefault('dependencies', {}) for string in specs: @@ -136,7 +135,7 @@ def provides(*specs, **kwargs): 'mpi', other packages can declare that they depend on "mpi", and spack can use the providing package to satisfy the dependency. """ - pkg = _ensure_caller_is_spack_package() + pkg = _get_calling_package_name() spec_string = kwargs.get('when', pkg) provider_spec = _parse_local_spec(spec_string, pkg) @@ -170,7 +169,7 @@ class ScopeError(RelationError): def __init__(self, relation): super(ScopeError, self).__init__( relation, - "Cannot inovke '%s' from outside of a Spack package!" % relation) + "Must invoke '%s' from inside a class definition!" % relation) class CircularReferenceError(RelationError): diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index d7c642a81e..17ef523f57 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -6,8 +6,10 @@ The syntax looks like this: - spack install mpileaks ^openmpi @1.2:1.4 +debug %intel @12.1 - 0 1 2 3 4 5 +.. code-block:: sh + + $ spack install mpileaks ^openmpi @1.2:1.4 +debug %intel @12.1 =bgqos_0 + 0 1 2 3 4 5 6 The first part of this is the command, 'spack install'. The rest of the line is a spec for a particular installation of the mpileaks package. @@ -37,7 +39,10 @@ if it comes immediately after the compiler name. Otherwise it will be associated with the current package spec. -Here is the EBNF grammar for a spec: +6. The architecture to build with. This is needed on machines where + cross-compilation is required + +Here is the EBNF grammar for a spec:: spec-list = { spec [ dep-list ] } dep_list = { ^ spec } @@ -80,8 +85,7 @@ """This map determines the coloring of specs when using color output. We make the fields different colors to enhance readability. - See spack.color for descriptions of the color codes. -""" + See spack.color for descriptions of the color codes. """ color_formats = {'%' : '@g', # compiler '@' : '@c', # version '=' : '@m', # architecture @@ -129,8 +133,7 @@ def __call__(self, match): class Compiler(object): """The Compiler field represents the compiler or range of compiler versions that a package should be built with. Compilers have a - name and a version list. - """ + name and a version list. """ def __init__(self, name, version=None): if name not in spack.compilers.supported_compilers(): raise UnknownCompilerError(name) @@ -348,10 +351,11 @@ def package(self): @property def virtual(self): """Right now, a spec is virtual if no package exists with its name. + TODO: revisit this -- might need to use a separate namespace and - be more explicit about this. - Possible idea: just use conventin and make virtual deps all - caps, e.g., MPI vs mpi. + be more explicit about this. + Possible idea: just use conventin and make virtual deps all + caps, e.g., MPI vs mpi. """ return not packages.exists(self.name) @@ -463,13 +467,15 @@ def _expand_virtual_packages(self): and normalize again to include the provider's (potentially virtual) dependencies. Repeat until there are no virtual deps. - TODO: If a provider depends on something that conflicts with - other dependencies in the spec being expanded, this can - produce a conflicting spec. For example, if mpich depends - on hwloc@:1.3 but something in the spec needs hwloc1.4:, - then we should choose an MPI other than mpich. Cases like - this are infrequent, but should implement this before it is - a problem. + .. todo:: + + If a provider depends on something that conflicts with + other dependencies in the spec being expanded, this can + produce a conflicting spec. For example, if mpich depends + on hwloc@:1.3 but something in the spec needs hwloc1.4:, + then we should choose an MPI other than mpich. Cases like + this are infrequent, but should implement this before it is + a problem. """ while True: virtuals =[v for v in self.preorder_traversal() if v.virtual] @@ -625,16 +631,19 @@ def normalize(self): """When specs are parsed, any dependencies specified are hanging off the root, and ONLY the ones that were explicitly provided are there. Normalization turns a partial flat spec into a DAG, where: - 1) ALL dependencies of the root package are in the DAG. - 2) Each node's dependencies dict only contains its direct deps. - 3) There is only ONE unique spec for each package in the DAG. - - This includes virtual packages. If there a non-virtual - package that provides a virtual package that is in the spec, - then we replace the virtual package with the non-virtual one. - 4) The spec DAG matches package DAG. + + 1. ALL dependencies of the root package are in the DAG. + 2. Each node's dependencies dict only contains its direct deps. + 3. There is only ONE unique spec for each package in the DAG. + + * This includes virtual packages. If there a non-virtual + package that provides a virtual package that is in the spec, + then we replace the virtual package with the non-virtual one. + + 4. The spec DAG matches package DAG. TODO: normalize should probably implement some form of cycle detection, - to ensure that the spec is actually a DAG. + to ensure that the spec is actually a DAG. """ # Ensure first that all packages in the DAG exist. self.validate_package_names() diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py index d38413d155..9c5248e9ca 100644 --- a/lib/spack/spack/stage.py +++ b/lib/spack/spack/stage.py @@ -18,12 +18,16 @@ class Stage(object): expanded, and built before being installed. It also handles downloading the archive. A stage's lifecycle looks like this: - setup() Create the stage directory. - fetch() Fetch a source archive into the stage. - expand_archive() Expand the source archive. - Build and install the archive. This is handled - by the Package class. - destroy() Remove the stage once the package has been installed. + setup() + Create the stage directory. + fetch() + Fetch a source archive into the stage. + expand_archive() + Expand the source archive. + + Build and install the archive. This is handled by the Package class. + destroy() + Remove the stage once the package has been installed. If spack.use_tmp_stage is True, spack will attempt to create stages in a tmp directory. Otherwise, stages are created directly in diff --git a/lib/spack/spack/version.py b/lib/spack/spack/version.py index 904992f832..5564d80a4e 100644 --- a/lib/spack/spack/version.py +++ b/lib/spack/spack/version.py @@ -1,15 +1,15 @@ """ This file implements Version and version-ish objects. These are: - Version - A single version of a package. - VersionRange - A range of versions of a package. - VersionList - A list of Versions and VersionRanges. +Version + A single version of a package. +VersionRange + A range of versions of a package. +VersionList + A list of Versions and VersionRanges. All of these types support the following operations, which can -be called on any of the types: +be called on any of the types:: __eq__, __ne__, __lt__, __gt__, __ge__, __le__, __hash__ __contains__ @@ -18,8 +18,6 @@ union intersection concrete - True if the Version, VersionRange or VersionList represents - a single version. """ import os import sys