summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2017-05-08 13:18:29 -0700
committerGitHub <noreply@github.com>2017-05-08 13:18:29 -0700
commitff3b5d88e4229516e9655a9a75f818453613e8e4 (patch)
tree56889884b1a68d740f1ddd702125c87e47a64493
parent7923579a424d8d9280717760cea1742cdb594d07 (diff)
downloadspack-ff3b5d88e4229516e9655a9a75f818453613e8e4.tar.gz
spack-ff3b5d88e4229516e9655a9a75f818453613e8e4.tar.bz2
spack-ff3b5d88e4229516e9655a9a75f818453613e8e4.tar.xz
spack-ff3b5d88e4229516e9655a9a75f818453613e8e4.zip
rework spack help (#3033)
- Full help is now only generated lazily, when needed. - Executing specific commands doesn't require loading all of them. - All commands are only loaded if we need them for help. - There is now short and long help: - short help (spack help) shows only basic spack options - long help (spack help -a) shows all spack options - Both divide help on commands into high-level sections - Commands now specify attributes from which help is auto-generated: - description: used in help to describe the command. - section: help section - level: short or long - Clean up command descriptions - Add a `spack docs` command to open full documentation in the browser. - move `spack doc` command to `spack pydoc` for clarity - Add a `spack --spec` command to show documentation on the spec syntax.
-rwxr-xr-xbin/spack222
-rw-r--r--lib/spack/spack/__init__.py2
-rw-r--r--lib/spack/spack/cmd/activate.py2
-rw-r--r--lib/spack/spack/cmd/arch.py2
-rw-r--r--lib/spack/spack/cmd/bootstrap.py2
-rw-r--r--lib/spack/spack/cmd/build.py3
-rw-r--r--lib/spack/spack/cmd/cd.py2
-rw-r--r--lib/spack/spack/cmd/checksum.py2
-rw-r--r--lib/spack/spack/cmd/clean.py2
-rw-r--r--lib/spack/spack/cmd/compiler.py2
-rw-r--r--lib/spack/spack/cmd/compilers.py4
-rw-r--r--lib/spack/spack/cmd/config.py2
-rw-r--r--lib/spack/spack/cmd/configure.py4
-rw-r--r--lib/spack/spack/cmd/create.py3
-rw-r--r--lib/spack/spack/cmd/deactivate.py2
-rw-r--r--lib/spack/spack/cmd/debug.py2
-rw-r--r--lib/spack/spack/cmd/dependents.py2
-rw-r--r--lib/spack/spack/cmd/diy.py2
-rw-r--r--lib/spack/spack/cmd/docs.py33
-rw-r--r--lib/spack/spack/cmd/edit.py2
-rw-r--r--lib/spack/spack/cmd/env.py4
-rw-r--r--lib/spack/spack/cmd/extensions.py2
-rw-r--r--lib/spack/spack/cmd/fetch.py2
-rw-r--r--lib/spack/spack/cmd/find.py4
-rw-r--r--lib/spack/spack/cmd/flake8.py4
-rw-r--r--lib/spack/spack/cmd/graph.py2
-rw-r--r--lib/spack/spack/cmd/help.py90
-rw-r--r--lib/spack/spack/cmd/info.py2
-rw-r--r--lib/spack/spack/cmd/install.py2
-rw-r--r--lib/spack/spack/cmd/list.py5
-rw-r--r--lib/spack/spack/cmd/load.py4
-rw-r--r--lib/spack/spack/cmd/location.py2
-rw-r--r--lib/spack/spack/cmd/md5.py2
-rw-r--r--lib/spack/spack/cmd/mirror.py2
-rw-r--r--lib/spack/spack/cmd/module.py3
-rw-r--r--lib/spack/spack/cmd/patch.py2
-rw-r--r--lib/spack/spack/cmd/pkg.py2
-rw-r--r--lib/spack/spack/cmd/providers.py2
-rw-r--r--lib/spack/spack/cmd/purge.py2
-rw-r--r--lib/spack/spack/cmd/pydoc.py (renamed from lib/spack/spack/cmd/doc.py)6
-rw-r--r--lib/spack/spack/cmd/python.py2
-rw-r--r--lib/spack/spack/cmd/reindex.py3
-rw-r--r--lib/spack/spack/cmd/repo.py2
-rw-r--r--lib/spack/spack/cmd/restage.py2
-rw-r--r--lib/spack/spack/cmd/setup.py2
-rw-r--r--lib/spack/spack/cmd/spec.py4
-rw-r--r--lib/spack/spack/cmd/stage.py2
-rw-r--r--lib/spack/spack/cmd/test.py4
-rw-r--r--lib/spack/spack/cmd/uninstall.py4
-rw-r--r--lib/spack/spack/cmd/unload.py4
-rw-r--r--lib/spack/spack/cmd/unuse.py2
-rw-r--r--lib/spack/spack/cmd/url.py2
-rw-r--r--lib/spack/spack/cmd/use.py2
-rw-r--r--lib/spack/spack/cmd/versions.py2
-rw-r--r--lib/spack/spack/cmd/view.py4
-rw-r--r--lib/spack/spack/main.py468
-rwxr-xr-xshare/spack/qa/run-unit-tests4
57 files changed, 736 insertions, 218 deletions
diff --git a/bin/spack b/bin/spack
index 5ab805fe54..496c705042 100755
--- a/bin/spack
+++ b/bin/spack
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# flake8: noqa
##############################################################################
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
@@ -26,34 +25,32 @@
##############################################################################
from __future__ import print_function
+import os
import sys
+
if sys.version_info[:2] < (2, 6):
v_info = sys.version_info[:3]
sys.exit("Spack requires Python 2.6 or higher."
"This is Python %d.%d.%d." % v_info)
-import os
-import inspect
-
# Find spack's location and its prefix.
-SPACK_FILE = os.path.realpath(os.path.expanduser(__file__))
-os.environ["SPACK_FILE"] = SPACK_FILE
-SPACK_PREFIX = os.path.dirname(os.path.dirname(SPACK_FILE))
+spack_file = os.path.realpath(os.path.expanduser(__file__))
+spack_prefix = os.path.dirname(os.path.dirname(spack_file))
# Allow spack libs to be imported in our scripts
-SPACK_LIB_PATH = os.path.join(SPACK_PREFIX, "lib", "spack")
-sys.path.insert(0, SPACK_LIB_PATH)
+spack_lib_path = os.path.join(spack_prefix, "lib", "spack")
+sys.path.insert(0, spack_lib_path)
# Add external libs
-SPACK_EXTERNAL_LIBS = os.path.join(SPACK_LIB_PATH, "external")
-sys.path.insert(0, SPACK_EXTERNAL_LIBS)
+spack_external_libs = os.path.join(spack_lib_path, "external")
+sys.path.insert(0, spack_external_libs)
# Handle vendoring of YAML specially, as it has two versions.
if sys.version_info[0] == 2:
- SPACK_YAML_LIBS = os.path.join(SPACK_EXTERNAL_LIBS, "yaml/lib")
+ spack_yaml_libs = os.path.join(spack_external_libs, "yaml/lib")
else:
- SPACK_YAML_LIBS = os.path.join(SPACK_EXTERNAL_LIBS, "yaml/lib3")
-sys.path.insert(0, SPACK_YAML_LIBS)
+ spack_yaml_libs = os.path.join(spack_external_libs, "yaml/lib3")
+sys.path.insert(0, spack_yaml_libs)
# Quick and dirty check to clean orphaned .pyc files left over from
# previous revisions. These files were present in earlier versions of
@@ -61,13 +58,13 @@ sys.path.insert(0, SPACK_YAML_LIBS)
# imports. If we leave them, Spack will fail in mysterious ways.
# TODO: more elegant solution for orphaned pyc files.
orphaned_pyc_files = [
- os.path.join(SPACK_EXTERNAL_LIBS, 'functools.pyc'),
- os.path.join(SPACK_EXTERNAL_LIBS, 'ordereddict.pyc'),
- os.path.join(SPACK_LIB_PATH, 'spack', 'platforms', 'cray_xc.pyc'),
- os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'package-list.pyc'),
- os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'test-install.pyc'),
- os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'url-parse.pyc'),
- os.path.join(SPACK_LIB_PATH, 'spack', 'test', 'yaml.pyc')
+ os.path.join(spack_external_libs, 'functools.pyc'),
+ os.path.join(spack_external_libs, 'ordereddict.pyc'),
+ os.path.join(spack_lib_path, 'spack', 'platforms', 'cray_xc.pyc'),
+ os.path.join(spack_lib_path, 'spack', 'cmd', 'package-list.pyc'),
+ os.path.join(spack_lib_path, 'spack', 'cmd', 'test-install.pyc'),
+ os.path.join(spack_lib_path, 'spack', 'cmd', 'url-parse.pyc'),
+ os.path.join(spack_lib_path, 'spack', 'test', 'yaml.pyc')
]
for pyc_file in orphaned_pyc_files:
@@ -79,183 +76,6 @@ for pyc_file in orphaned_pyc_files:
print("WARNING: Spack may fail mysteriously. "
"Couldn't remove orphaned .pyc file: %s" % pyc_file)
-# If there is no working directory, use the spack prefix.
-try:
- working_dir = os.getcwd()
-except OSError:
- os.chdir(SPACK_PREFIX)
- working_dir = SPACK_PREFIX
-
-# clean up the scope and start using spack package instead.
-del SPACK_FILE, SPACK_PREFIX, SPACK_LIB_PATH
-import llnl.util.tty as tty
-from llnl.util.tty.color import *
-import spack
-from spack.error import SpackError
-import argparse
-import pstats
-
-# Get the allowed names of statistics for cProfile, and make a list of
-# groups of 7 names to wrap them nicely.
-stat_names = pstats.Stats.sort_arg_dict_default
-stat_lines = list(zip(*(iter(stat_names),)*7))
-
-# Command parsing
-parser = argparse.ArgumentParser(
- formatter_class=argparse.RawTextHelpFormatter,
- description="Spack: the Supercomputing PACKage Manager." + colorize("""
-
-spec expressions:
- PACKAGE [CONSTRAINTS]
-
- CONSTRAINTS:
- @c{@version}
- @g{%compiler @compiler_version}
- @B{+variant}
- @r{-variant} or @r{~variant}
- @m{=architecture}
- [^DEPENDENCY [CONSTRAINTS] ...]"""))
-
-parser.add_argument('-d', '--debug', action='store_true',
- help="write out debug logs during compile")
-parser.add_argument('-D', '--pdb', action='store_true',
- help="run spack under the pdb debugger")
-parser.add_argument('-k', '--insecure', action='store_true',
- help="do not check ssl certificates when downloading")
-parser.add_argument('-m', '--mock', action='store_true',
- help="use mock packages instead of real ones")
-parser.add_argument('-p', '--profile', action='store_true',
- help="profile execution using cProfile")
-parser.add_argument('-P', '--sorted-profile', default=None, metavar="STAT",
- help="profile and sort by one or more of:\n[%s]" %
- ',\n '.join([', '.join(line) for line in stat_lines]))
-parser.add_argument('--lines', default=20, action='store',
- help="lines of profile output: default 20; 'all' for all")
-parser.add_argument('-v', '--verbose', action='store_true',
- help="print additional output during builds")
-parser.add_argument('-s', '--stacktrace', action='store_true',
- help="add stacktrace info to all printed statements")
-parser.add_argument('-V', '--version', action='version',
- version="%s" % spack.spack_version)
-
-# each command module implements a parser() function, to which we pass its
-# subparser for setup.
-subparsers = parser.add_subparsers(metavar='SUBCOMMAND', dest="command")
-
-
-import spack.cmd
-for cmd in spack.cmd.commands:
- module = spack.cmd.get_module(cmd)
- cmd_name = cmd.replace('_', '-')
- subparser = subparsers.add_parser(cmd_name, help=module.description)
- module.setup_parser(subparser)
-
-
-def _main(args, unknown_args):
- # Set up environment based on args.
- tty.set_verbose(args.verbose)
- tty.set_debug(args.debug)
- tty.set_stacktrace(args.stacktrace)
- spack.debug = args.debug
-
- if spack.debug:
- import spack.util.debug as debug
- debug.register_interrupt_handler()
-
- # Run any available pre-run hooks
- spack.hooks.pre_run()
-
- spack.spack_working_dir = working_dir
- if args.mock:
- from spack.repository import RepoPath
- spack.repo.swap(RepoPath(spack.mock_packages_path))
-
- # If the user asked for it, don't check ssl certs.
- if args.insecure:
- tty.warn("You asked for --insecure. Will NOT check SSL certificates.")
- spack.insecure = True
-
- # Try to load the particular command asked for and run it
- command = spack.cmd.get_command(args.command.replace('-', '_'))
-
- # Allow commands to inject an optional argument and get unknown args
- # if they want to handle them.
- info = dict(inspect.getmembers(command))
- varnames = info['__code__'].co_varnames
- argcount = info['__code__'].co_argcount
-
- # Actually execute the command
- try:
- if argcount == 3 and varnames[2] == 'unknown_args':
- return_val = command(parser, args, unknown_args)
- else:
- if unknown_args:
- tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
- return_val = command(parser, args)
- except SpackError as e:
- e.die()
- except Exception as e:
- tty.die(str(e))
- except KeyboardInterrupt:
- sys.stderr.write('\n')
- tty.die("Keyboard interrupt.")
-
- # Allow commands to return values if they want to exit with some other code.
- if return_val is None:
- sys.exit(0)
- elif isinstance(return_val, int):
- sys.exit(return_val)
- else:
- tty.die("Bad return value from command %s: %s"
- % (args.command, return_val))
-
-
-def main(args):
- # Just print help and exit if run with no arguments at all
- if len(args) == 1:
- parser.print_help()
- sys.exit(1)
-
- # actually parse the args.
- args, unknown = parser.parse_known_args()
-
- if args.profile or args.sorted_profile:
- import cProfile
-
- try:
- nlines = int(args.lines)
- except ValueError:
- if args.lines != 'all':
- tty.die('Invalid number for --lines: %s' % args.lines)
- nlines = -1
-
- # allow comma-separated list of fields
- sortby = ['time']
- if args.sorted_profile:
- sortby = args.sorted_profile.split(',')
- for stat in sortby:
- if stat not in stat_names:
- tty.die("Invalid sort field: %s" % stat)
-
- try:
- # make a profiler and run the code.
- pr = cProfile.Profile()
- pr.enable()
- _main(args, unknown)
- finally:
- pr.disable()
-
- # print out profile stats.
- stats = pstats.Stats(pr)
- stats.sort_stats(*sortby)
- stats.print_stats(nlines)
-
- elif args.pdb:
- import pdb
- pdb.runctx('_main(args, unknown)', globals(), locals())
- else:
- _main(args, unknown)
-
-
-if __name__ == '__main__':
- main(sys.argv)
+# Once we've set up the system path, run the spack main method
+import spack.main # noqa
+sys.exit(spack.main.main())
diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py
index 73963b848c..27283d10a9 100644
--- a/lib/spack/spack/__init__.py
+++ b/lib/spack/spack/__init__.py
@@ -217,5 +217,5 @@ __all__ += [
# Add default values for attributes that would otherwise be modified from
# Spack main script
-debug = True
+debug = False
spack_working_dir = None
diff --git a/lib/spack/spack/cmd/activate.py b/lib/spack/spack/cmd/activate.py
index f21799753b..f7e826efd6 100644
--- a/lib/spack/spack/cmd/activate.py
+++ b/lib/spack/spack/cmd/activate.py
@@ -28,6 +28,8 @@ import spack
import spack.cmd
description = "activate a package extension"
+section = "extensions"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/arch.py b/lib/spack/spack/cmd/arch.py
index 1079e7f215..d4241dcae9 100644
--- a/lib/spack/spack/cmd/arch.py
+++ b/lib/spack/spack/cmd/arch.py
@@ -27,6 +27,8 @@ from __future__ import print_function
import spack.architecture as architecture
description = "print architecture information about this machine"
+section = "system"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/bootstrap.py b/lib/spack/spack/cmd/bootstrap.py
index a804086a38..b6daf4f09b 100644
--- a/lib/spack/spack/cmd/bootstrap.py
+++ b/lib/spack/spack/cmd/bootstrap.py
@@ -33,6 +33,8 @@ from spack.util.executable import ProcessError, which
_SPACK_UPSTREAM = 'https://github.com/llnl/spack'
description = "create a new installation of spack in another prefix"
+section = "admin"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/build.py b/lib/spack/spack/cmd/build.py
index 877f2ce0cf..cc63c6593b 100644
--- a/lib/spack/spack/cmd/build.py
+++ b/lib/spack/spack/cmd/build.py
@@ -27,6 +27,9 @@ import spack.cmd.configure as cfg
from spack import *
description = 'stops at build stage when installing a package, if possible'
+section = "build"
+level = "long"
+
build_system_to_phase = {
AutotoolsPackage: 'build',
diff --git a/lib/spack/spack/cmd/cd.py b/lib/spack/spack/cmd/cd.py
index 784ad4ac83..531f3c59fd 100644
--- a/lib/spack/spack/cmd/cd.py
+++ b/lib/spack/spack/cmd/cd.py
@@ -26,6 +26,8 @@ import spack.cmd.location
import spack.modules
description = "cd to spack directories in the shell"
+section = "environment"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/checksum.py b/lib/spack/spack/cmd/checksum.py
index fda9beed27..d8a17fd383 100644
--- a/lib/spack/spack/cmd/checksum.py
+++ b/lib/spack/spack/cmd/checksum.py
@@ -36,6 +36,8 @@ from spack.util.naming import *
from spack.version import *
description = "checksum available versions of a package"
+section = "packaging"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/clean.py b/lib/spack/spack/cmd/clean.py
index 6c70b5bd38..23507822ef 100644
--- a/lib/spack/spack/cmd/clean.py
+++ b/lib/spack/spack/cmd/clean.py
@@ -30,6 +30,8 @@ import spack
import spack.cmd
description = "remove build stage and source tarball for packages"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py
index 6067d44c5e..f2eeca20ab 100644
--- a/lib/spack/spack/cmd/compiler.py
+++ b/lib/spack/spack/cmd/compiler.py
@@ -39,6 +39,8 @@ from spack.spec import CompilerSpec, ArchSpec
from spack.util.environment import get_path
description = "manage compilers"
+section = "system"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/compilers.py b/lib/spack/spack/cmd/compilers.py
index 934fc6cf06..f0e21f987e 100644
--- a/lib/spack/spack/cmd/compilers.py
+++ b/lib/spack/spack/cmd/compilers.py
@@ -25,7 +25,9 @@
import spack
from spack.cmd.compiler import compiler_list
-description = "list available compilers, same as 'spack compiler list'"
+description = "list available compilers"
+section = "system"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
index a647e3ed6e..61c2c6f0e8 100644
--- a/lib/spack/spack/cmd/config.py
+++ b/lib/spack/spack/cmd/config.py
@@ -25,6 +25,8 @@
import spack.config
description = "get and set configuration options"
+section = "config"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/configure.py b/lib/spack/spack/cmd/configure.py
index 7f6c07c34b..7cab566052 100644
--- a/lib/spack/spack/cmd/configure.py
+++ b/lib/spack/spack/cmd/configure.py
@@ -30,7 +30,9 @@ import spack.cmd.install as inst
from spack import *
-description = 'stops at configuration stage when installing a package, if possible' # NOQA: ignore=E501
+description = 'stage and configure a package but do not install'
+section = "build"
+level = "long"
build_system_to_phase = {
diff --git a/lib/spack/spack/cmd/create.py b/lib/spack/spack/cmd/create.py
index adaf388387..89ba050a53 100644
--- a/lib/spack/spack/cmd/create.py
+++ b/lib/spack/spack/cmd/create.py
@@ -40,6 +40,9 @@ from spack.util.naming import *
from spack.url import *
description = "create a new package file"
+section = "packaging"
+level = "short"
+
package_template = '''\
##############################################################################
diff --git a/lib/spack/spack/cmd/deactivate.py b/lib/spack/spack/cmd/deactivate.py
index 7ea2039236..3d8020d064 100644
--- a/lib/spack/spack/cmd/deactivate.py
+++ b/lib/spack/spack/cmd/deactivate.py
@@ -31,6 +31,8 @@ import spack.store
from spack.graph import topological_sort
description = "deactivate a package extension"
+section = "extensions"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/debug.py b/lib/spack/spack/cmd/debug.py
index 06dea9ea70..ba5f783839 100644
--- a/lib/spack/spack/cmd/debug.py
+++ b/lib/spack/spack/cmd/debug.py
@@ -34,6 +34,8 @@ import spack
from spack.util.executable import which
description = "debugging commands for troubleshooting Spack"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/dependents.py b/lib/spack/spack/cmd/dependents.py
index c752ffb943..6c481548d3 100644
--- a/lib/spack/spack/cmd/dependents.py
+++ b/lib/spack/spack/cmd/dependents.py
@@ -31,6 +31,8 @@ import spack.store
import spack.cmd
description = "show installed packages that depend on another"
+section = "basic"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/diy.py b/lib/spack/spack/cmd/diy.py
index c67e189f73..14d28bb3f4 100644
--- a/lib/spack/spack/cmd/diy.py
+++ b/lib/spack/spack/cmd/diy.py
@@ -34,6 +34,8 @@ import spack.cmd.common.arguments as arguments
from spack.stage import DIYStage
description = "do-it-yourself: build from an existing source directory"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/docs.py b/lib/spack/spack/cmd/docs.py
new file mode 100644
index 0000000000..fe026da4a7
--- /dev/null
+++ b/lib/spack/spack/cmd/docs.py
@@ -0,0 +1,33 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import webbrowser
+
+description = 'open spack documentation in a web browser'
+section = 'help'
+level = 'short'
+
+
+def docs(parser, args):
+ webbrowser.open('https://spack.readthedocs.io')
diff --git a/lib/spack/spack/cmd/edit.py b/lib/spack/spack/cmd/edit.py
index 01f2b61887..0287b8cd67 100644
--- a/lib/spack/spack/cmd/edit.py
+++ b/lib/spack/spack/cmd/edit.py
@@ -33,6 +33,8 @@ from spack.spec import Spec
from spack.repository import Repo
description = "open package files in $EDITOR"
+section = "packaging"
+level = "short"
def edit_package(name, repo_path, namespace):
diff --git a/lib/spack/spack/cmd/env.py b/lib/spack/spack/cmd/env.py
index ed18940ac0..034b710b85 100644
--- a/lib/spack/spack/cmd/env.py
+++ b/lib/spack/spack/cmd/env.py
@@ -31,7 +31,9 @@ import llnl.util.tty as tty
import spack.cmd
import spack.build_environment as build_env
-description = "run a command with the install environment for a spec"
+description = "show install environment for a spec, and run commands"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/extensions.py b/lib/spack/spack/cmd/extensions.py
index 94a3e8288f..d073d42cc3 100644
--- a/lib/spack/spack/cmd/extensions.py
+++ b/lib/spack/spack/cmd/extensions.py
@@ -33,6 +33,8 @@ import spack.cmd.find
import spack.store
description = "list extensions for package"
+section = "extensions"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/fetch.py b/lib/spack/spack/cmd/fetch.py
index 35cc23a963..cf39d4a40b 100644
--- a/lib/spack/spack/cmd/fetch.py
+++ b/lib/spack/spack/cmd/fetch.py
@@ -28,6 +28,8 @@ import spack
import spack.cmd
description = "fetch archives for packages"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index 3a6d8270fb..0142155fe1 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -29,7 +29,9 @@ import spack.cmd.common.arguments as arguments
from spack.cmd import display_specs
-description = "find installed spack packages"
+description = "list and search installed packages"
+section = "basic"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/flake8.py b/lib/spack/spack/cmd/flake8.py
index 42d36a3beb..9753b20e78 100644
--- a/lib/spack/spack/cmd/flake8.py
+++ b/lib/spack/spack/cmd/flake8.py
@@ -36,7 +36,11 @@ from llnl.util.filesystem import *
import spack
from spack.util.executable import *
+
description = "runs source code style checks on Spack. requires flake8"
+section = "developer"
+level = "long"
+
"""List of directories to exclude from checks."""
exclude_directories = [spack.external_path]
diff --git a/lib/spack/spack/cmd/graph.py b/lib/spack/spack/cmd/graph.py
index ee401d8fb7..3a82f39ce5 100644
--- a/lib/spack/spack/cmd/graph.py
+++ b/lib/spack/spack/cmd/graph.py
@@ -34,6 +34,8 @@ from spack.spec import *
from spack.graph import *
description = "generate graphs of package dependency relationships"
+section = "basic"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/help.py b/lib/spack/spack/cmd/help.py
index e867ca1295..313b082e2f 100644
--- a/lib/spack/spack/cmd/help.py
+++ b/lib/spack/spack/cmd/help.py
@@ -22,16 +22,100 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import sys
+from llnl.util.tty import colorize
+
description = "get help on spack and its commands"
+section = "help"
+level = "short"
+
+#
+# These are longer guides on particular aspects of Spack. Currently there
+# is only one on spec syntax.
+#
+spec_guide = """\
+spec expression syntax:
+
+ package [constraints] [^dependency [constraints] ...]
+
+ package any package from 'spack list'
+
+ constraints:
+ versions:
+ @c{@version} single version
+ @c{@min:max} version range (inclusive)
+ @c{@min:} version <min> or higher
+ @c{@:max} up to version <max> (inclusive)
+
+ compilers:
+ @g{%compiler} build with <compiler>
+ @g{%compiler@version} build with specific compiler version
+ @g{%compiler@min:max} specific version range (see above)
+
+ variants:
+ @B{+variant} enable <variant>
+ @r{-variant} or @r{~variant} disable <variant>
+ @B{variant=value} set non-boolean <variant> to <value>
+ @B{variant=value1,value2,value3} set multi-value <variant> values
+
+ architecture variants:
+ @m{target=target} specific <target> processor
+ @m{os=operating_system} specific <operating_system>
+ @m{platform=platform} linux, darwin, cray, bgq, etc.
+ @m{arch=platform-os-target} shortcut for all three above
+
+ cross-compiling:
+ @m{os=backend} or @m{os=be} build for compute node (backend)
+ @m{os=frontend} or @m{os=fe} build for login node (frontend)
+
+ dependencies:
+ ^dependency [constraints] specify constraints on dependencies
+
+ examples:
+ hdf5 any hdf5 configuration
+ hdf5 @c{@1.10.1} hdf5 version 1.10.1
+ hdf5 @c{@1.8:} hdf5 1.8 or higher
+ hdf5 @c{@1.8:} @g{%gcc} hdf5 1.8 or higher built with gcc
+ hdf5 @B{+mpi} hdf5 with mpi enabled
+ hdf5 @r{~mpi} hdf5 with mpi disabled
+ hdf5 @B{+mpi} ^mpich hdf5 with mpi, using mpich
+ hdf5 @B{+mpi} ^openmpi@c{@1.7} hdf5 wtih mpi, using openmpi 1.7
+ boxlib @B{dim=2} boxlib built for 2 dimensions
+ libdwarf @g{%intel} ^libelf@g{%gcc}
+ libdwarf, built with intel compiler, linked to libelf built with gcc
+ mvapich2 @g{%pgi} @B{fabrics=psm,mrail,sock}
+ mvapich2, built with pgi compiler, with support for multiple fabrics
+"""
+
+
+guides = {
+ 'spec': spec_guide,
+}
def setup_parser(subparser):
- subparser.add_argument('help_command', nargs='?', default=None,
- help='command to get help on')
+ help_cmd_group = subparser.add_mutually_exclusive_group()
+ help_cmd_group.add_argument('help_command', nargs='?', default=None,
+ help='command to get help on')
+
+ help_all_group = subparser.add_mutually_exclusive_group()
+ help_all_group.add_argument(
+ '-a', '--all', action='store_const', const='long', default='short',
+ help='print all available commands')
+
+ help_spec_group = subparser.add_mutually_exclusive_group()
+ help_spec_group.add_argument(
+ '--spec', action='store_const', dest='guide', const='spec',
+ default=None, help='print all available commands')
def help(parser, args):
+ if args.guide:
+ print(colorize(guides[args.guide]))
+ return 0
+
if args.help_command:
+ parser.add_command(args.help_command)
parser.parse_args([args.help_command, '-h'])
else:
- parser.print_help()
+ sys.stdout.write(parser.format_help(level=args.all))
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index 86ec839b90..62de5484af 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -31,6 +31,8 @@ import spack
import spack.fetch_strategy as fs
description = "get detailed information on a particular package"
+section = "basic"
+level = "short"
def padder(str_list, extra=0):
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index fb01fc2d5e..87fad76181 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -41,6 +41,8 @@ from spack.fetch_strategy import FetchError
from spack.package import PackageBase
description = "build and install packages"
+section = "build"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py
index bcfb092945..72be99d260 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -35,7 +35,10 @@ import llnl.util.tty as tty
import spack
from llnl.util.tty.colify import colify
-description = "print available spack packages to stdout in different formats"
+description = "list and search available packages"
+section = "basic"
+level = "short"
+
formatters = {}
diff --git a/lib/spack/spack/cmd/load.py b/lib/spack/spack/cmd/load.py
index cdc3a741ae..106a95c9c2 100644
--- a/lib/spack/spack/cmd/load.py
+++ b/lib/spack/spack/cmd/load.py
@@ -25,7 +25,9 @@
import argparse
import spack.modules
-description = "add package to environment using modules"
+description = "add package to environment using `module load`"
+section = "environment"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/location.py b/lib/spack/spack/cmd/location.py
index d1a7825630..e713d028d2 100644
--- a/lib/spack/spack/cmd/location.py
+++ b/lib/spack/spack/cmd/location.py
@@ -31,6 +31,8 @@ import spack
import spack.cmd
description = "print out locations of various directories used by Spack"
+section = "environment"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/md5.py b/lib/spack/spack/cmd/md5.py
index fc205cc693..1d121f0120 100644
--- a/lib/spack/spack/cmd/md5.py
+++ b/lib/spack/spack/cmd/md5.py
@@ -32,6 +32,8 @@ import spack.util.crypto
from spack.stage import Stage, FailedDownloadError
description = "calculate md5 checksums for files/urls"
+section = "packaging"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/mirror.py b/lib/spack/spack/cmd/mirror.py
index 528fcbfc3f..e5b3b492b4 100644
--- a/lib/spack/spack/cmd/mirror.py
+++ b/lib/spack/spack/cmd/mirror.py
@@ -38,6 +38,8 @@ from spack.error import SpackError
from spack.util.spack_yaml import syaml_dict
description = "manage mirrors"
+section = "config"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py
index 37c79a358b..f8253aad6f 100644
--- a/lib/spack/spack/cmd/module.py
+++ b/lib/spack/spack/cmd/module.py
@@ -36,6 +36,9 @@ from spack.cmd.common import arguments
from spack.modules import module_types
description = "manipulate module files"
+section = "environment"
+level = "short"
+
# Dictionary that will be populated with the list of sub-commands
# Each sub-command must be callable and accept 3 arguments :
diff --git a/lib/spack/spack/cmd/patch.py b/lib/spack/spack/cmd/patch.py
index 2e332554ad..dfe45b0494 100644
--- a/lib/spack/spack/cmd/patch.py
+++ b/lib/spack/spack/cmd/patch.py
@@ -30,6 +30,8 @@ import spack
description = "patch expanded archive sources in preparation for install"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/pkg.py b/lib/spack/spack/cmd/pkg.py
index 12dcb81792..aca69e9c99 100644
--- a/lib/spack/spack/cmd/pkg.py
+++ b/lib/spack/spack/cmd/pkg.py
@@ -34,6 +34,8 @@ import spack
from spack.util.executable import *
description = "query packages associated with particular git revisions"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/providers.py b/lib/spack/spack/cmd/providers.py
index 470e3e5ed2..f30c28a951 100644
--- a/lib/spack/spack/cmd/providers.py
+++ b/lib/spack/spack/cmd/providers.py
@@ -30,6 +30,8 @@ import spack
import spack.cmd
description = "list packages that provide a particular virtual package"
+section = "basic"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/purge.py b/lib/spack/spack/cmd/purge.py
index 56165d5d97..b7ebb0fc69 100644
--- a/lib/spack/spack/cmd/purge.py
+++ b/lib/spack/spack/cmd/purge.py
@@ -26,6 +26,8 @@ import spack
import spack.stage as stage
description = "remove temporary build files and/or downloaded archives"
+section = "admin"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/doc.py b/lib/spack/spack/cmd/pydoc.py
index 12ae6b4973..c9003184c4 100644
--- a/lib/spack/spack/cmd/doc.py
+++ b/lib/spack/spack/cmd/pydoc.py
@@ -1,5 +1,5 @@
##############################################################################
-# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
@@ -24,11 +24,13 @@
##############################################################################
description = "run pydoc from within spack"
+section = "developer"
+level = "long"
def setup_parser(subparser):
subparser.add_argument('entity', help="run pydoc help on entity")
-def doc(parser, args):
+def pydoc(parser, args):
help(args.entity)
diff --git a/lib/spack/spack/cmd/python.py b/lib/spack/spack/cmd/python.py
index 6df9507580..3c4fbf9e87 100644
--- a/lib/spack/spack/cmd/python.py
+++ b/lib/spack/spack/cmd/python.py
@@ -32,6 +32,8 @@ import spack
description = "launch an interpreter as spack would launch a command"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/reindex.py b/lib/spack/spack/cmd/reindex.py
index 0bbd85069f..dff127bc06 100644
--- a/lib/spack/spack/cmd/reindex.py
+++ b/lib/spack/spack/cmd/reindex.py
@@ -26,6 +26,9 @@ import spack
import spack.store
description = "rebuild Spack's package database"
+section = "admin"
+level = "long"
+
def reindex(parser, args):
spack.store.db.reindex(spack.store.layout)
diff --git a/lib/spack/spack/cmd/repo.py b/lib/spack/spack/cmd/repo.py
index dd75f148c2..5beb0083e2 100644
--- a/lib/spack/spack/cmd/repo.py
+++ b/lib/spack/spack/cmd/repo.py
@@ -33,6 +33,8 @@ import spack.config
from spack.repository import *
description = "manage package source repositories"
+section = "config"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/restage.py b/lib/spack/spack/cmd/restage.py
index 36fee9237b..4cecf4b42e 100644
--- a/lib/spack/spack/cmd/restage.py
+++ b/lib/spack/spack/cmd/restage.py
@@ -30,6 +30,8 @@ import spack
import spack.cmd
description = "revert checked out package source code"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/setup.py b/lib/spack/spack/cmd/setup.py
index 82d00f4e11..79e7bca1ab 100644
--- a/lib/spack/spack/cmd/setup.py
+++ b/lib/spack/spack/cmd/setup.py
@@ -39,6 +39,8 @@ from spack import which
from spack.stage import DIYStage
description = "create a configuration script and module, but don't build"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index 2e917d2ee3..f4105900cb 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -29,7 +29,9 @@ import spack
import spack.cmd
import spack.cmd.common.arguments as arguments
-description = "print out abstract and concrete versions of a spec"
+description = "show what would be installed, given a spec"
+section = "build"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/stage.py b/lib/spack/spack/cmd/stage.py
index e0023b7254..a469cd896d 100644
--- a/lib/spack/spack/cmd/stage.py
+++ b/lib/spack/spack/cmd/stage.py
@@ -29,6 +29,8 @@ import spack
import spack.cmd
description = "expand downloaded archive in preparation for install"
+section = "build"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/test.py b/lib/spack/spack/cmd/test.py
index 9384e3a9e6..f7ec6a10e0 100644
--- a/lib/spack/spack/cmd/test.py
+++ b/lib/spack/spack/cmd/test.py
@@ -36,7 +36,9 @@ from llnl.util.tty.colify import colify
import spack
-description = "a thin wrapper around the pytest command"
+description = "run spack's unit tests"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
index f3eaddf88a..6880409c56 100644
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -33,7 +33,9 @@ import spack.repository
from llnl.util import tty
-description = "remove an installed package"
+description = "remove installed packages"
+section = "build"
+level = "short"
error_message = """You can either:
a) use a more specific spec, or
diff --git a/lib/spack/spack/cmd/unload.py b/lib/spack/spack/cmd/unload.py
index 5da6f5daa5..8a0511f64c 100644
--- a/lib/spack/spack/cmd/unload.py
+++ b/lib/spack/spack/cmd/unload.py
@@ -25,7 +25,9 @@
import argparse
import spack.modules
-description = "remove package from environment using module"
+description = "remove package from environment using `module unload`"
+section = "environment"
+level = "short"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/unuse.py b/lib/spack/spack/cmd/unuse.py
index e479749457..77312d1204 100644
--- a/lib/spack/spack/cmd/unuse.py
+++ b/lib/spack/spack/cmd/unuse.py
@@ -26,6 +26,8 @@ import argparse
import spack.modules
description = "remove package from environment using dotkit"
+section = "environment"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/url.py b/lib/spack/spack/cmd/url.py
index e5cfce0de3..28118a178a 100644
--- a/lib/spack/spack/cmd/url.py
+++ b/lib/spack/spack/cmd/url.py
@@ -34,6 +34,8 @@ from spack.util.web import find_versions_of_archive
from spack.util.naming import simplify_name
description = "debugging tool for url parsing"
+section = "developer"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/use.py b/lib/spack/spack/cmd/use.py
index c9714d9de0..e67de3a8b3 100644
--- a/lib/spack/spack/cmd/use.py
+++ b/lib/spack/spack/cmd/use.py
@@ -26,6 +26,8 @@ import argparse
import spack.modules
description = "add package to environment using dotkit"
+section = "environment"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/versions.py b/lib/spack/spack/cmd/versions.py
index a6f6805fb0..446f0a876d 100644
--- a/lib/spack/spack/cmd/versions.py
+++ b/lib/spack/spack/cmd/versions.py
@@ -29,6 +29,8 @@ import llnl.util.tty as tty
import spack
description = "list available versions of a package"
+section = "packaging"
+level = "long"
def setup_parser(subparser):
diff --git a/lib/spack/spack/cmd/view.py b/lib/spack/spack/cmd/view.py
index 72e139d123..8fb94d3f37 100644
--- a/lib/spack/spack/cmd/view.py
+++ b/lib/spack/spack/cmd/view.py
@@ -69,7 +69,9 @@ import spack
import spack.cmd
import llnl.util.tty as tty
-description = "produce a single-rooted directory view of a spec"
+description = "produce a single-rooted directory view of packages"
+section = "environment"
+level = "short"
def setup_parser(sp):
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
new file mode 100644
index 0000000000..39c64b3ce0
--- /dev/null
+++ b/lib/spack/spack/main.py
@@ -0,0 +1,468 @@
+##############################################################################
+# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the LICENSE file for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+"""This is the implementation of the Spack command line executable.
+
+In a normal Spack installation, this is invoked from the bin/spack script
+after the system path is set up.
+"""
+from __future__ import print_function
+
+import sys
+import os
+import inspect
+from argparse import _ArgumentGroup, ArgumentParser, RawTextHelpFormatter
+import pstats
+
+import llnl.util.tty as tty
+from llnl.util.tty.color import *
+
+import spack
+import spack.cmd
+from spack.error import SpackError
+
+
+# names of profile statistics
+stat_names = pstats.Stats.sort_arg_dict_default
+
+# help levels in order of detail (i.e., number of commands shown)
+levels = ['short', 'long']
+
+# intro text for help at different levels
+intro_by_level = {
+ 'short': 'These are common spack commands:',
+ 'long': 'Complete list of spack commands:',
+}
+
+# control top-level spack options shown in basic vs. advanced help
+options_by_level = {
+ 'short': 'hkV',
+ 'long': 'all'
+}
+
+# Longer text for each section, to show in help
+section_descriptions = {
+ 'admin': 'administration',
+ 'basic': 'query packages',
+ 'build': 'build packages',
+ 'config': 'configuration',
+ 'developer': 'developer',
+ 'environment': 'environment',
+ 'extensions': 'extensions',
+ 'help': 'more help',
+ 'packaging': 'create packages',
+ 'system': 'system',
+}
+
+# preferential command order for some sections (e.g., build pipeline is
+# in execution order, not alphabetical)
+section_order = {
+ 'basic': ['list', 'info', 'find'],
+ 'build': ['fetch', 'stage', 'patch', 'configure', 'build', 'restage',
+ 'install', 'uninstall', 'clean']
+}
+
+# Properties that commands are required to set.
+required_command_properties = ['level', 'section', 'description']
+
+
+def set_working_dir():
+ """Change the working directory to getcwd, or spack prefix if no cwd."""
+ try:
+ spack.spack_working_dir = os.getcwd()
+ except OSError:
+ os.chdir(spack_prefix)
+ spack.spack_working_dir = spack_prefix
+
+
+def add_all_commands(parser):
+ """Add all spack subcommands to the parser."""
+ for cmd in spack.cmd.commands:
+ parser.add_command(cmd)
+
+
+def index_commands():
+ """create an index of commands by section for this help level"""
+ index = {}
+ for command in spack.cmd.commands:
+ cmd_module = spack.cmd.get_module(command)
+
+ # make sure command modules have required properties
+ for p in required_command_properties:
+ prop = getattr(cmd_module, p, None)
+ if not prop:
+ tty.die("Command doesn't define a property '%s': %s"
+ % (p, command))
+
+ # add commands to lists for their level and higher levels
+ for level in reversed(levels):
+ level_sections = index.setdefault(level, {})
+ commands = level_sections.setdefault(cmd_module.section, [])
+ commands.append(command)
+ if level == cmd_module.level:
+ break
+
+ return index
+
+
+class SpackArgumentParser(ArgumentParser):
+ def format_help_sections(self, level):
+ """Format help on sections for a particular verbosity level.
+
+ Args:
+ level (str): 'short' or 'long' (more commands shown for long)
+ """
+ if level not in levels:
+ raise ValueError("level must be one of: %s" % levels)
+
+ # lazily add all commands to the parser when needed.
+ add_all_commands(self)
+
+ """Print help on subcommands in neatly formatted sections."""
+ formatter = self._get_formatter()
+
+ # Create a list of subcommand actions. Argparse internals are nasty!
+ # Note: you can only call _get_subactions() once. Even nastier!
+ if not hasattr(self, 'actions'):
+ self.actions = self._subparsers._actions[-1]._get_subactions()
+
+ # make a set of commands not yet added.
+ remaining = set(spack.cmd.commands)
+
+ def add_group(group):
+ formatter.start_section(group.title)
+ formatter.add_text(group.description)
+ formatter.add_arguments(group._group_actions)
+ formatter.end_section()
+
+ def add_subcommand_group(title, commands):
+ """Add informational help group for a specific subcommand set."""
+ cmd_set = set(commands)
+
+ # make a dict of commands of interest
+ cmds = dict((action.metavar, action) for action in self.actions
+ if action.metavar in cmd_set)
+
+ # add commands to a group in order, and add the group
+ group = _ArgumentGroup(self, title=title)
+ for name in commands:
+ group._add_action(cmds[name])
+ if name in remaining:
+ remaining.remove(name)
+ add_group(group)
+
+ # select only the options for the particular level we're showing.
+ show_options = options_by_level[level]
+ if show_options != 'all':
+ opts = dict((opt.option_strings[0].strip('-'), opt)
+ for opt in self._optionals._group_actions)
+
+ new_actions = [opts[letter] for letter in show_options]
+ self._optionals._group_actions = new_actions
+
+ options = ''.join(opt.option_strings[0].strip('-')
+ for opt in self._optionals._group_actions)
+
+ index = index_commands()
+
+ # usage
+ formatter.add_text(
+ "usage: %s [-%s] <command> [...]" % (self.prog, options))
+
+ # description
+ formatter.add_text(self.description)
+
+ # start subcommands
+ formatter.add_text(intro_by_level[level])
+
+ # add argument groups based on metadata in commands
+ sections = index[level]
+ for section in sorted(sections):
+ if section == 'help':
+ continue # Cover help in the epilog.
+
+ group_description = section_descriptions.get(section, section)
+
+ to_display = sections[section]
+ commands = []
+
+ # add commands whose order we care about first.
+ if section in section_order:
+ commands.extend(cmd for cmd in section_order[section]
+ if cmd in to_display)
+
+ # add rest in alphabetical order.
+ commands.extend(cmd for cmd in sorted(sections[section])
+ if cmd not in commands)
+
+ # add the group to the parser
+ add_subcommand_group(group_description, commands)
+
+ # optionals
+ add_group(self._optionals)
+
+ # epilog
+ formatter.add_text("""\
+{help}:
+ spack help -a list all available commands
+ spack help <command> help on a specific command
+ spack help --spec help on the spec syntax
+ spack docs open http://spack.rtfd.io/ in a browser"""
+.format(help=section_descriptions['help']))
+
+ # determine help from format above
+ return formatter.format_help()
+
+ def add_command(self, name):
+ """Add one subcommand to this parser."""
+ # lazily initialize any subparsers
+ if not hasattr(self, 'subparsers'):
+ # remove the dummy "command" argument.
+ self._remove_action(self._actions[-1])
+ self.subparsers = self.add_subparsers(metavar='COMMAND',
+ dest="command")
+
+ # each command module implements a parser() function, to which we
+ # pass its subparser for setup.
+ module = spack.cmd.get_module(name)
+ cmd_name = name.replace('_', '-')
+ subparser = self.subparsers.add_parser(
+ cmd_name, help=module.description, description=module.description)
+ module.setup_parser(subparser)
+ return module
+
+ def format_help(self, level='short'):
+ if self.prog == 'spack':
+ # use format_help_sections for the main spack parser, but not
+ # for subparsers
+ return self.format_help_sections(level)
+ else:
+ # in subparsers, self.prog is, e.g., 'spack install'
+ return super(SpackArgumentParser, self).format_help()
+
+
+def make_argument_parser():
+ """Create an basic argument parser without any subcommands added."""
+ parser = SpackArgumentParser(
+ formatter_class=RawTextHelpFormatter, add_help=False,
+ description=(
+ "A flexible package manager that supports multiple versions,\n"
+ "configurations, platforms, and compilers."))
+
+ # stat names in groups of 7, for nice wrapping.
+ stat_lines = list(zip(*(iter(stat_names),) * 7))
+
+ parser.add_argument('-h', '--help', action='store_true',
+ help="show this help message and exit")
+ parser.add_argument('-d', '--debug', action='store_true',
+ help="write out debug logs during compile")
+ parser.add_argument('-D', '--pdb', action='store_true',
+ help="run spack under the pdb debugger")
+ parser.add_argument('-k', '--insecure', action='store_true',
+ help="do not check ssl certificates when downloading")
+ parser.add_argument('-m', '--mock', action='store_true',
+ help="use mock packages instead of real ones")
+ parser.add_argument('-p', '--profile', action='store_true',
+ help="profile execution using cProfile")
+ parser.add_argument('-P', '--sorted-profile', default=None, metavar="STAT",
+ help="profile and sort by one or more of:\n[%s]" %
+ ',\n '.join([', '.join(line) for line in stat_lines]))
+ parser.add_argument('--lines', default=20, action='store',
+ help="lines of profile output; default 20; or 'all'")
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help="print additional output during builds")
+ parser.add_argument('-s', '--stacktrace', action='store_true',
+ help="add stacktraces to all printed statements")
+ parser.add_argument('-V', '--version', action='store_true',
+ help='show version number and exit')
+ return parser
+
+
+def setup_main_options(args):
+ """Configure spack globals based on the basic options."""
+ # Set up environment based on args.
+ tty.set_verbose(args.verbose)
+ tty.set_debug(args.debug)
+ tty.set_stacktrace(args.stacktrace)
+ spack.debug = args.debug
+
+ if spack.debug:
+ import spack.util.debug as debug
+ debug.register_interrupt_handler()
+
+ if args.mock:
+ from spack.repository import RepoPath
+ spack.repo.swap(RepoPath(spack.mock_packages_path))
+
+ # If the user asked for it, don't check ssl certs.
+ if args.insecure:
+ tty.warn("You asked for --insecure. Will NOT check SSL certificates.")
+ spack.insecure = True
+
+
+def allows_unknown_args(command):
+ """This is a basic argument injection test.
+
+ Commands may add an optional argument called "unknown args" to
+ indicate they can handle unknonwn args, and we'll pass the unknown
+ args in.
+ """
+ info = dict(inspect.getmembers(command))
+ varnames = info['__code__'].co_varnames
+ argcount = info['__code__'].co_argcount
+ return (argcount == 3 and varnames[2] == 'unknown_args')
+
+
+def _main(command, parser, args, unknown_args):
+ # many operations will fail without a working directory.
+ set_working_dir()
+
+ # only setup main options in here, after the real parse (we'll get it
+ # wrong if we do it after the initial, partial parse)
+ setup_main_options(args)
+ spack.hooks.pre_run()
+
+ # Now actually execute the command
+ try:
+ if allows_unknown_args(command):
+ return_val = command(parser, args, unknown_args)
+ else:
+ if unknown_args:
+ tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
+ return_val = command(parser, args)
+ except SpackError as e:
+ e.die() # gracefully die on any SpackErrors
+ except Exception as e:
+ if spack.debug:
+ raise
+ tty.die(str(e))
+ except KeyboardInterrupt:
+ sys.stderr.write('\n')
+ tty.die("Keyboard interrupt.")
+
+ # Allow commands to return and error code if they want
+ return 0 if return_val is None else return_val
+
+
+def _profile_wrapper(command, parser, args, unknown_args):
+ import cProfile
+
+ try:
+ nlines = int(args.lines)
+ except ValueError:
+ if args.lines != 'all':
+ tty.die('Invalid number for --lines: %s' % args.lines)
+ nlines = -1
+
+ # allow comma-separated list of fields
+ sortby = ['time']
+ if args.sorted_profile:
+ sortby = args.sorted_profile.split(',')
+ for stat in sortby:
+ if stat not in stat_names:
+ tty.die("Invalid sort field: %s" % stat)
+
+ try:
+ # make a profiler and run the code.
+ pr = cProfile.Profile()
+ pr.enable()
+ return _main(command, parser, args, unknown_args)
+
+ finally:
+ pr.disable()
+
+ # print out profile stats.
+ stats = pstats.Stats(pr)
+ stats.sort_stats(*sortby)
+ stats.print_stats(nlines)
+
+
+def main(argv=None):
+ """This is the entry point for the Spack command.
+
+ Args:
+ argv (list of str or None): command line arguments, NOT including
+ the executable name. If None, parses from sys.argv.
+ """
+ # Create a parser with a simple positional argument first. We'll
+ # lazily load the subcommand(s) we need later. This allows us to
+ # avoid loading all the modules from spack.cmd when we don't need
+ # them, which reduces startup latency.
+ parser = make_argument_parser()
+ parser.add_argument(
+ 'command', metavar='COMMAND', nargs='?', action='store')
+ args, unknown = parser.parse_known_args(argv)
+
+ # Just print help and exit if run with no arguments at all
+ no_args = (len(sys.argv) == 1) if argv is None else (len(argv) == 0)
+ if no_args:
+ parser.print_help()
+ return 1
+
+ # -h and -V are special as they do not require a command, but all the
+ # other options do nothing without a command.
+ if not args.command:
+ if args.version:
+ print(spack.spack_version)
+ return 0
+ else:
+ parser.print_help()
+ return 0 if args.help else 1
+
+ # Try to load the particular command the caller asked for. If there
+ # is no module for it, just die.
+ command_name = args.command.replace('-', '_')
+ try:
+ parser.add_command(command_name)
+ except ImportError:
+ if spack.debug:
+ raise
+ tty.die("Unknown command: %s" % args.command)
+
+ # Re-parse with the proper sub-parser added.
+ args, unknown = parser.parse_known_args()
+
+ # we now know whether options go with spack or the command
+ if args.version:
+ print(spack.spack_version)
+ return 0
+ elif args.help:
+ parser.print_help()
+ return 0
+
+ # now we can actually execute the command.
+ command = spack.cmd.get_command(command_name)
+ try:
+ if args.profile or args.sorted_profile:
+ _profile_wrapper(command, parser, args, unknown)
+ elif args.pdb:
+ import pdb
+ pdb.runctx('_main(command, parser, args, unknown)',
+ globals(), locals())
+ return 0
+ else:
+ return _main(command, parser, args, unknown)
+
+ except SystemExit as e:
+ return e.code
diff --git a/share/spack/qa/run-unit-tests b/share/spack/qa/run-unit-tests
index fe2ec6f54a..87203ba915 100755
--- a/share/spack/qa/run-unit-tests
+++ b/share/spack/qa/run-unit-tests
@@ -20,6 +20,10 @@ cd "$SPACK_ROOT"
# Print compiler information
spack config get compilers
+# Run spack help to cover command import
+${coverage_run} bin/spack -h
+${coverage_run} bin/spack help -a
+
# Profile and print top 20 lines for a simple call to spack spec
${coverage_run} bin/spack -p --lines 20 spec mpileaks