From ff3b5d88e4229516e9655a9a75f818453613e8e4 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 8 May 2017 13:18:29 -0700 Subject: 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. --- bin/spack | 222 ++++++-------------------------------------------------------- 1 file changed, 21 insertions(+), 201 deletions(-) (limited to 'bin') 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()) -- cgit v1.2.3-60-g2f50