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.
This commit is contained in:
Todd Gamblin 2017-05-08 13:18:29 -07:00 committed by GitHub
parent 7923579a42
commit ff3b5d88e4
57 changed files with 736 additions and 218 deletions

222
bin/spack
View file

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# flake8: noqa
############################################################################## ##############################################################################
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. # Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory. # Produced at the Lawrence Livermore National Laboratory.
@ -26,34 +25,32 @@
############################################################################## ##############################################################################
from __future__ import print_function from __future__ import print_function
import os
import sys import sys
if sys.version_info[:2] < (2, 6): if sys.version_info[:2] < (2, 6):
v_info = sys.version_info[:3] v_info = sys.version_info[:3]
sys.exit("Spack requires Python 2.6 or higher." sys.exit("Spack requires Python 2.6 or higher."
"This is Python %d.%d.%d." % v_info) "This is Python %d.%d.%d." % v_info)
import os
import inspect
# Find spack's location and its prefix. # Find spack's location and its prefix.
SPACK_FILE = os.path.realpath(os.path.expanduser(__file__)) 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_PREFIX = os.path.dirname(os.path.dirname(SPACK_FILE))
# Allow spack libs to be imported in our scripts # Allow spack libs to be imported in our scripts
SPACK_LIB_PATH = os.path.join(SPACK_PREFIX, "lib", "spack") spack_lib_path = os.path.join(spack_prefix, "lib", "spack")
sys.path.insert(0, SPACK_LIB_PATH) sys.path.insert(0, spack_lib_path)
# Add external libs # Add external libs
SPACK_EXTERNAL_LIBS = os.path.join(SPACK_LIB_PATH, "external") spack_external_libs = os.path.join(spack_lib_path, "external")
sys.path.insert(0, SPACK_EXTERNAL_LIBS) sys.path.insert(0, spack_external_libs)
# Handle vendoring of YAML specially, as it has two versions. # Handle vendoring of YAML specially, as it has two versions.
if sys.version_info[0] == 2: 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: else:
SPACK_YAML_LIBS = os.path.join(SPACK_EXTERNAL_LIBS, "yaml/lib3") spack_yaml_libs = os.path.join(spack_external_libs, "yaml/lib3")
sys.path.insert(0, SPACK_YAML_LIBS) sys.path.insert(0, spack_yaml_libs)
# Quick and dirty check to clean orphaned .pyc files left over from # Quick and dirty check to clean orphaned .pyc files left over from
# previous revisions. These files were present in earlier versions of # 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. # imports. If we leave them, Spack will fail in mysterious ways.
# TODO: more elegant solution for orphaned pyc files. # TODO: more elegant solution for orphaned pyc files.
orphaned_pyc_files = [ orphaned_pyc_files = [
os.path.join(SPACK_EXTERNAL_LIBS, 'functools.pyc'), os.path.join(spack_external_libs, 'functools.pyc'),
os.path.join(SPACK_EXTERNAL_LIBS, 'ordereddict.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', 'platforms', 'cray_xc.pyc'),
os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'package-list.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', 'test-install.pyc'),
os.path.join(SPACK_LIB_PATH, 'spack', 'cmd', 'url-parse.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_lib_path, 'spack', 'test', 'yaml.pyc')
] ]
for pyc_file in orphaned_pyc_files: for pyc_file in orphaned_pyc_files:
@ -79,183 +76,6 @@ for pyc_file in orphaned_pyc_files:
print("WARNING: Spack may fail mysteriously. " print("WARNING: Spack may fail mysteriously. "
"Couldn't remove orphaned .pyc file: %s" % pyc_file) "Couldn't remove orphaned .pyc file: %s" % pyc_file)
# If there is no working directory, use the spack prefix. # Once we've set up the system path, run the spack main method
try: import spack.main # noqa
working_dir = os.getcwd() sys.exit(spack.main.main())
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)

View file

@ -217,5 +217,5 @@
# Add default values for attributes that would otherwise be modified from # Add default values for attributes that would otherwise be modified from
# Spack main script # Spack main script
debug = True debug = False
spack_working_dir = None spack_working_dir = None

View file

@ -28,6 +28,8 @@
import spack.cmd import spack.cmd
description = "activate a package extension" description = "activate a package extension"
section = "extensions"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -27,6 +27,8 @@
import spack.architecture as architecture import spack.architecture as architecture
description = "print architecture information about this machine" description = "print architecture information about this machine"
section = "system"
level = "short"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -33,6 +33,8 @@
_SPACK_UPSTREAM = 'https://github.com/llnl/spack' _SPACK_UPSTREAM = 'https://github.com/llnl/spack'
description = "create a new installation of spack in another prefix" description = "create a new installation of spack in another prefix"
section = "admin"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -27,6 +27,9 @@
from spack import * from spack import *
description = 'stops at build stage when installing a package, if possible' description = 'stops at build stage when installing a package, if possible'
section = "build"
level = "long"
build_system_to_phase = { build_system_to_phase = {
AutotoolsPackage: 'build', AutotoolsPackage: 'build',

View file

@ -26,6 +26,8 @@
import spack.modules import spack.modules
description = "cd to spack directories in the shell" description = "cd to spack directories in the shell"
section = "environment"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -36,6 +36,8 @@
from spack.version import * from spack.version import *
description = "checksum available versions of a package" description = "checksum available versions of a package"
section = "packaging"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -30,6 +30,8 @@
import spack.cmd import spack.cmd
description = "remove build stage and source tarball for packages" description = "remove build stage and source tarball for packages"
section = "build"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -39,6 +39,8 @@
from spack.util.environment import get_path from spack.util.environment import get_path
description = "manage compilers" description = "manage compilers"
section = "system"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -25,7 +25,9 @@
import spack import spack
from spack.cmd.compiler import compiler_list 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): def setup_parser(subparser):

View file

@ -25,6 +25,8 @@
import spack.config import spack.config
description = "get and set configuration options" description = "get and set configuration options"
section = "config"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -30,7 +30,9 @@
from spack import * 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 = { build_system_to_phase = {

View file

@ -40,6 +40,9 @@
from spack.url import * from spack.url import *
description = "create a new package file" description = "create a new package file"
section = "packaging"
level = "short"
package_template = '''\ package_template = '''\
############################################################################## ##############################################################################

View file

@ -31,6 +31,8 @@
from spack.graph import topological_sort from spack.graph import topological_sort
description = "deactivate a package extension" description = "deactivate a package extension"
section = "extensions"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -34,6 +34,8 @@
from spack.util.executable import which from spack.util.executable import which
description = "debugging commands for troubleshooting Spack" description = "debugging commands for troubleshooting Spack"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -31,6 +31,8 @@
import spack.cmd import spack.cmd
description = "show installed packages that depend on another" description = "show installed packages that depend on another"
section = "basic"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -34,6 +34,8 @@
from spack.stage import DIYStage from spack.stage import DIYStage
description = "do-it-yourself: build from an existing source directory" description = "do-it-yourself: build from an existing source directory"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -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')

View file

@ -33,6 +33,8 @@
from spack.repository import Repo from spack.repository import Repo
description = "open package files in $EDITOR" description = "open package files in $EDITOR"
section = "packaging"
level = "short"
def edit_package(name, repo_path, namespace): def edit_package(name, repo_path, namespace):

View file

@ -31,7 +31,9 @@
import spack.cmd import spack.cmd
import spack.build_environment as build_env 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): def setup_parser(subparser):

View file

@ -33,6 +33,8 @@
import spack.store import spack.store
description = "list extensions for package" description = "list extensions for package"
section = "extensions"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -28,6 +28,8 @@
import spack.cmd import spack.cmd
description = "fetch archives for packages" description = "fetch archives for packages"
section = "build"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -29,7 +29,9 @@
from spack.cmd import display_specs 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): def setup_parser(subparser):

View file

@ -36,7 +36,11 @@
import spack import spack
from spack.util.executable import * from spack.util.executable import *
description = "runs source code style checks on Spack. requires flake8" description = "runs source code style checks on Spack. requires flake8"
section = "developer"
level = "long"
"""List of directories to exclude from checks.""" """List of directories to exclude from checks."""
exclude_directories = [spack.external_path] exclude_directories = [spack.external_path]

View file

@ -34,6 +34,8 @@
from spack.graph import * from spack.graph import *
description = "generate graphs of package dependency relationships" description = "generate graphs of package dependency relationships"
section = "basic"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -22,16 +22,100 @@
# License along with this program; if not, write to the Free Software # License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # 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" 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): def setup_parser(subparser):
subparser.add_argument('help_command', nargs='?', default=None, help_cmd_group = subparser.add_mutually_exclusive_group()
help='command to get help on') 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): def help(parser, args):
if args.guide:
print(colorize(guides[args.guide]))
return 0
if args.help_command: if args.help_command:
parser.add_command(args.help_command)
parser.parse_args([args.help_command, '-h']) parser.parse_args([args.help_command, '-h'])
else: else:
parser.print_help() sys.stdout.write(parser.format_help(level=args.all))

View file

@ -31,6 +31,8 @@
import spack.fetch_strategy as fs import spack.fetch_strategy as fs
description = "get detailed information on a particular package" description = "get detailed information on a particular package"
section = "basic"
level = "short"
def padder(str_list, extra=0): def padder(str_list, extra=0):

View file

@ -41,6 +41,8 @@
from spack.package import PackageBase from spack.package import PackageBase
description = "build and install packages" description = "build and install packages"
section = "build"
level = "short"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -35,7 +35,10 @@
import spack import spack
from llnl.util.tty.colify import colify 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 = {} formatters = {}

View file

@ -25,7 +25,9 @@
import argparse import argparse
import spack.modules 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): def setup_parser(subparser):

View file

@ -31,6 +31,8 @@
import spack.cmd import spack.cmd
description = "print out locations of various directories used by Spack" description = "print out locations of various directories used by Spack"
section = "environment"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -32,6 +32,8 @@
from spack.stage import Stage, FailedDownloadError from spack.stage import Stage, FailedDownloadError
description = "calculate md5 checksums for files/urls" description = "calculate md5 checksums for files/urls"
section = "packaging"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -38,6 +38,8 @@
from spack.util.spack_yaml import syaml_dict from spack.util.spack_yaml import syaml_dict
description = "manage mirrors" description = "manage mirrors"
section = "config"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -36,6 +36,9 @@
from spack.modules import module_types from spack.modules import module_types
description = "manipulate module files" description = "manipulate module files"
section = "environment"
level = "short"
# Dictionary that will be populated with the list of sub-commands # Dictionary that will be populated with the list of sub-commands
# Each sub-command must be callable and accept 3 arguments : # Each sub-command must be callable and accept 3 arguments :

View file

@ -30,6 +30,8 @@
description = "patch expanded archive sources in preparation for install" description = "patch expanded archive sources in preparation for install"
section = "build"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -34,6 +34,8 @@
from spack.util.executable import * from spack.util.executable import *
description = "query packages associated with particular git revisions" description = "query packages associated with particular git revisions"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -30,6 +30,8 @@
import spack.cmd import spack.cmd
description = "list packages that provide a particular virtual package" description = "list packages that provide a particular virtual package"
section = "basic"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -26,6 +26,8 @@
import spack.stage as stage import spack.stage as stage
description = "remove temporary build files and/or downloaded archives" description = "remove temporary build files and/or downloaded archives"
section = "admin"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -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. # Produced at the Lawrence Livermore National Laboratory.
# #
# This file is part of Spack. # This file is part of Spack.
@ -24,11 +24,13 @@
############################################################################## ##############################################################################
description = "run pydoc from within spack" description = "run pydoc from within spack"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):
subparser.add_argument('entity', help="run pydoc help on entity") subparser.add_argument('entity', help="run pydoc help on entity")
def doc(parser, args): def pydoc(parser, args):
help(args.entity) help(args.entity)

View file

@ -32,6 +32,8 @@
description = "launch an interpreter as spack would launch a command" description = "launch an interpreter as spack would launch a command"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -26,6 +26,9 @@
import spack.store import spack.store
description = "rebuild Spack's package database" description = "rebuild Spack's package database"
section = "admin"
level = "long"
def reindex(parser, args): def reindex(parser, args):
spack.store.db.reindex(spack.store.layout) spack.store.db.reindex(spack.store.layout)

View file

@ -33,6 +33,8 @@
from spack.repository import * from spack.repository import *
description = "manage package source repositories" description = "manage package source repositories"
section = "config"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -30,6 +30,8 @@
import spack.cmd import spack.cmd
description = "revert checked out package source code" description = "revert checked out package source code"
section = "build"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -39,6 +39,8 @@
from spack.stage import DIYStage from spack.stage import DIYStage
description = "create a configuration script and module, but don't build" description = "create a configuration script and module, but don't build"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -29,7 +29,9 @@
import spack.cmd import spack.cmd
import spack.cmd.common.arguments as arguments 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): def setup_parser(subparser):

View file

@ -29,6 +29,8 @@
import spack.cmd import spack.cmd
description = "expand downloaded archive in preparation for install" description = "expand downloaded archive in preparation for install"
section = "build"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -36,7 +36,9 @@
import spack import spack
description = "a thin wrapper around the pytest command" description = "run spack's unit tests"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -33,7 +33,9 @@
from llnl.util import tty from llnl.util import tty
description = "remove an installed package" description = "remove installed packages"
section = "build"
level = "short"
error_message = """You can either: error_message = """You can either:
a) use a more specific spec, or a) use a more specific spec, or

View file

@ -25,7 +25,9 @@
import argparse import argparse
import spack.modules 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): def setup_parser(subparser):

View file

@ -26,6 +26,8 @@
import spack.modules import spack.modules
description = "remove package from environment using dotkit" description = "remove package from environment using dotkit"
section = "environment"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -34,6 +34,8 @@
from spack.util.naming import simplify_name from spack.util.naming import simplify_name
description = "debugging tool for url parsing" description = "debugging tool for url parsing"
section = "developer"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -26,6 +26,8 @@
import spack.modules import spack.modules
description = "add package to environment using dotkit" description = "add package to environment using dotkit"
section = "environment"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -29,6 +29,8 @@
import spack import spack
description = "list available versions of a package" description = "list available versions of a package"
section = "packaging"
level = "long"
def setup_parser(subparser): def setup_parser(subparser):

View file

@ -69,7 +69,9 @@
import spack.cmd import spack.cmd
import llnl.util.tty as tty 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): def setup_parser(sp):

468
lib/spack/spack/main.py Normal file
View file

@ -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

View file

@ -20,6 +20,10 @@ cd "$SPACK_ROOT"
# Print compiler information # Print compiler information
spack config get compilers 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 # Profile and print top 20 lines for a simple call to spack spec
${coverage_run} bin/spack -p --lines 20 spec mpileaks ${coverage_run} bin/spack -p --lines 20 spec mpileaks