diff --git a/.gitignore b/.gitignore index 7010bf7ede..ed2012d208 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *~ .DS_Store .idea +/etc/spackconfig diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py new file mode 100644 index 0000000000..25d302f94b --- /dev/null +++ b/lib/spack/spack/cmd/config.py @@ -0,0 +1,77 @@ +############################################################################## +# Copyright (c) 2013, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://scalability-llnl.github.io/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License (as published by +# the Free Software Foundation) version 2.1 dated February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## +import sys +import argparse + +import llnl.util.tty as tty + +import spack.config + +description = "Get and set configuration options." + +def setup_parser(subparser): + scope_group = subparser.add_mutually_exclusive_group() + + # File scope + scope_group.add_argument( + '--user', action='store_const', const='user', dest='scope', + help="Use config file in user home directory (default).") + scope_group.add_argument( + '--site', action='store_const', const='site', dest='scope', + help="Use config file in spack prefix.") + + # Get (vs. default set) + subparser.add_argument( + '--get', action='store_true', dest='get', + help="Get the value associated with a key.") + + # positional arguments (value is only used on set) + subparser.add_argument( + 'key', help="Get the value associated with KEY") + subparser.add_argument( + 'value', nargs='?', default=None, + help="Value to associate with key") + + +def config(parser, args): + key, value = args.key, args.value + + # If we're writing need to do a few checks. + if not args.get: + # Default scope for writing is user scope. + if not args.scope: + args.scope = 'user' + + if args.value is None: + tty.die("No value for '%s'. " % args.key + + "Spack config requires a key and a value.") + + config = spack.config.get_config(args.scope) + + if args.get: + print config.get_value(key) + else: + config.set_value(key, value) + config.write() diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py new file mode 100644 index 0000000000..b36b83bfaa --- /dev/null +++ b/lib/spack/spack/config.py @@ -0,0 +1,449 @@ +############################################################################## +# Copyright (c) 2013, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://scalability-llnl.github.io/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License (as published by +# the Free Software Foundation) version 2.1 dated February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## +"""This module implements Spack's configuration file handling. + +Configuration file scopes +=============================== + +When Spack runs, it pulls configuration data from several config +files, much like bash shells. In Spack, there are two configuration +scopes: + + 1. ``site``: Spack loads site-wide configuration options from + ``$(prefix)/etc/spackconfig``. + + 2. ``user``: Spack next loads per-user configuration options from + ~/.spackconfig. + +If user options have the same names as site options, the user options +take precedence. + + +Configuration file format +=============================== + +Configuration files are formatted using .gitconfig syntax, which is +much like Windows .INI format. This format is implemented by Python's +ConfigParser class, and it's easy to read and versatile. + +The file is divided into sections, like this ``compiler`` section:: + + [compiler] + cc = /usr/bin/gcc + +In each section there are options (cc), and each option has a value +(/usr/bin/gcc). + +Borrowing from git, we also allow named sections, e.g.: + + [compiler "gcc@4.7.3"] + cc = /usr/bin/gcc + +This is a compiler section, but it's for the specific compiler, +``gcc@4.7.3``. ``gcc@4.7.3`` is the name. + + +Keys +=============================== + +Together, the section, name, and option, separated by periods, are +called a ``key``. Keys can be used on the command line to set +configuration options explicitly (this is also borrowed from git). + +For example, to change the C compiler used by gcc@4.7.3, you could do +this: + + spack config compiler.gcc@4.7.3.cc /usr/local/bin/gcc + +That will create a named compiler section in the user's .spackconfig +like the one shown above. +""" +import os +import re +import inspect +from collections import OrderedDict +import ConfigParser as cp + +from llnl.util.lang import memoized + +import spack +import spack.error + +__all__ = [ + 'SpackConfigParser', 'get_config', 'SpackConfigurationError', + 'InvalidConfigurationScopeError', 'InvalidSectionNameError', + 'ReadOnlySpackConfigError', 'ConfigParserError', 'NoOptionError', + 'NoSectionError'] + +_named_section_re = r'([^ ]+) "([^"]+)"' + +"""Names of scopes and their corresponding configuration files.""" +_scopes = OrderedDict({ + 'site' : os.path.join(spack.etc_path, 'spackconfig'), + 'user' : os.path.expanduser('~/.spackconfig') +}) + +_field_regex = r'^([\w-]*)' \ + r'(?:\.(.*(?=.)))?' \ + r'(?:\.([\w-]+))?$' + +_section_regex = r'^([\w-]*)\s*' \ + r'\"([^"]*\)\"$' + + +def get_config(scope=None): + """Get a Spack configuration object, which can be used to set options. + + With no arguments, this returns a SpackConfigParser with config + options loaded from all config files. This is how client code + should read Spack configuration options. + + Optionally, a scope parameter can be provided. Valid scopes + are ``site`` and ``user``. If a scope is provided, only the + options from that scope's configuration file are loaded. The + caller can set or unset options, then call ``write()`` on the + config object to write it back out to the original config file. + """ + if scope is None: + return SpackConfigParser() + elif scope not in _scopes: + raise UnknownConfigurationScopeError(scope) + else: + return SpackConfigParser(_scopes[scope]) + + +def _parse_key(key): + """Return the section, name, and option the field describes. + Values are returned in a 3-tuple. + + e.g.: + The field name ``compiler.gcc@4.7.3.cc`` refers to the 'cc' key + in a section that looks like this: + + [compiler "gcc@4.7.3"] + cc = /usr/local/bin/gcc + + * The section is ``compiler`` + * The name is ``gcc@4.7.3`` + * The key is ``cc`` + """ + match = re.search(_field_regex, key) + if match: + return match.groups() + else: + raise InvalidSectionNameError(key) + + +def _make_section_name(section, name): + if not name: + return section + return '%s "%s"' % (section, name) + + +def _autokey(fun): + """Allow a function to be called with a string key like + 'compiler.gcc.cc', or with the section, name, and option + separated. Function should take at least three args, e.g.: + + fun(self, section, name, option, [...]) + + This will allow the function above to be called normally or + with a string key, e.g.: + + fun(self, key, [...]) + """ + argspec = inspect.getargspec(fun) + fun_nargs = len(argspec[0]) + + def string_key_func(*args): + nargs = len(args) + if nargs == fun_nargs - 2: + section, name, option = _parse_key(args[1]) + return fun(args[0], section, name, option, *args[2:]) + + elif nargs == fun_nargs: + return fun(*args) + + else: + raise TypeError( + "%s takes %d or %d args (found %d)." + % (fun.__name__, fun_nargs - 2, fun_nargs, len(args))) + return string_key_func + + + +class SpackConfigParser(cp.RawConfigParser): + """Slightly modified from Python's raw config file parser to accept + leading whitespace. + """ + # Slightly modified Python option expression. This one allows + # leading whitespace. + OPTCRE = re.compile( + r'\s*(?P