config: allow env.yaml to contain configuration in a single file
- add `SingleFileScope` to configuration, which allows us to pull config sections from a single file. - update `env.yaml` and tests to ensure that the env.yaml schema works when pulling configurtion from the env file.
This commit is contained in:
		@@ -146,6 +146,26 @@ def has_method(cls, name):
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def union_dicts(*dicts):
 | 
			
		||||
    """Use update() to combine all dicts into one.
 | 
			
		||||
 | 
			
		||||
    This builds a new dictionary, into which we ``update()`` each element
 | 
			
		||||
    of ``dicts`` in order.  Items from later dictionaries will override
 | 
			
		||||
    items from earlier dictionaries.
 | 
			
		||||
 | 
			
		||||
    Args:
 | 
			
		||||
        dicts (list): list of dictionaries
 | 
			
		||||
 | 
			
		||||
    Return: (dict): a merged dictionary containing combined keys and
 | 
			
		||||
        values from ``dicts``.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    result = {}
 | 
			
		||||
    for d in dicts:
 | 
			
		||||
        result.update(d)
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class memoized(object):
 | 
			
		||||
    """Decorator that caches the results of a function, storing them
 | 
			
		||||
       in an attribute of that function."""
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
from six import StringIO
 | 
			
		||||
from six import StringIO, text_type
 | 
			
		||||
 | 
			
		||||
from llnl.util.tty import terminal_size
 | 
			
		||||
from llnl.util.tty.color import clen, cextra
 | 
			
		||||
@@ -137,7 +137,7 @@ def colify(elts, **options):
 | 
			
		||||
            % next(options.iterkeys()))
 | 
			
		||||
 | 
			
		||||
    # elts needs to be an array of strings so we can count the elements
 | 
			
		||||
    elts = [str(elt) for elt in elts]
 | 
			
		||||
    elts = [text_type(elt) for elt in elts]
 | 
			
		||||
    if not elts:
 | 
			
		||||
        return (0, ())
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -44,17 +44,16 @@
 | 
			
		||||
# =============== Modifies Environment
 | 
			
		||||
 | 
			
		||||
def setup_create_parser(subparser):
 | 
			
		||||
    """create a new environment"""
 | 
			
		||||
    """create a new environment."""
 | 
			
		||||
    subparser.add_argument('env', help='name of environment to create')
 | 
			
		||||
    subparser.add_argument(
 | 
			
		||||
        '--init-file', dest='init_file',
 | 
			
		||||
        help='File with user specs to add and configuration yaml to use')
 | 
			
		||||
        'envfile', nargs='?', help='optional initialization file')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def environment_create(args):
 | 
			
		||||
    if os.path.exists(ev.root(args.environment)):
 | 
			
		||||
        raise tty.die("Environment already exists: " + args.environment)
 | 
			
		||||
 | 
			
		||||
    _environment_create(args.environment)
 | 
			
		||||
    if os.path.exists(ev.root(args.env)):
 | 
			
		||||
        raise tty.die("Environment already exists: " + args.env)
 | 
			
		||||
    _environment_create(args.env)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _environment_create(name, init_config=None):
 | 
			
		||||
@@ -310,7 +309,6 @@ def add_use_repo_argument(cmd_parser):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def setup_parser(subparser):
 | 
			
		||||
    subparser.add_argument('environment', help="name of environment")
 | 
			
		||||
    sp = subparser.add_subparsers(
 | 
			
		||||
        metavar='SUBCOMMAND', dest='environment_command')
 | 
			
		||||
 | 
			
		||||
@@ -324,6 +322,7 @@ def setup_parser(subparser):
 | 
			
		||||
 | 
			
		||||
def env(parser, args, **kwargs):
 | 
			
		||||
    """Look for a function called environment_<name> and call it."""
 | 
			
		||||
 | 
			
		||||
    function_name = 'environment_%s' % args.environment_command
 | 
			
		||||
    action = globals()[function_name]
 | 
			
		||||
    action(args)
 | 
			
		||||
 
 | 
			
		||||
@@ -177,6 +177,8 @@ def get_section(self, section):
 | 
			
		||||
    def write_section(self, section):
 | 
			
		||||
        filename = self.get_section_filename(section)
 | 
			
		||||
        data = self.get_section(section)
 | 
			
		||||
        _validate(data, section_schemas[section])
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            mkdirp(self.path)
 | 
			
		||||
            with open(filename, 'w') as f:
 | 
			
		||||
@@ -194,6 +196,77 @@ def __repr__(self):
 | 
			
		||||
        return '<ConfigScope: %s: %s>' % (self.name, self.path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SingleFileScope(ConfigScope):
 | 
			
		||||
    """This class represents a configuration scope in a single YAML file."""
 | 
			
		||||
    def __init__(self, name, path, schema, yaml_path=None):
 | 
			
		||||
        """Similar to ``ConfigScope`` but can be embedded in another schema.
 | 
			
		||||
 | 
			
		||||
        Arguments:
 | 
			
		||||
            schema (dict): jsonschema for the file to read
 | 
			
		||||
            yaml_path (list): list of dict keys in the schema where
 | 
			
		||||
                config data can be found.
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        super(SingleFileScope, self).__init__(name, path)
 | 
			
		||||
        self._raw_data = None
 | 
			
		||||
        self.schema = schema
 | 
			
		||||
        self.yaml_path = yaml_path or []
 | 
			
		||||
 | 
			
		||||
    def get_section_filename(self, section):
 | 
			
		||||
        return self.path
 | 
			
		||||
 | 
			
		||||
    def get_section(self, section):
 | 
			
		||||
        # read raw data from the file, which looks like:
 | 
			
		||||
        # {
 | 
			
		||||
        #   'config': {
 | 
			
		||||
        #      ... data ...
 | 
			
		||||
        #   },
 | 
			
		||||
        #   'packages': {
 | 
			
		||||
        #      ... data ...
 | 
			
		||||
        #   },
 | 
			
		||||
        # }
 | 
			
		||||
        if self._raw_data is None:
 | 
			
		||||
            self._raw_data = _read_config_file(self.path, self.schema)
 | 
			
		||||
            if self._raw_data is None:
 | 
			
		||||
                return None
 | 
			
		||||
 | 
			
		||||
            for key in self.yaml_path:
 | 
			
		||||
                self._raw_data = self._raw_data[key]
 | 
			
		||||
 | 
			
		||||
        # data in self.sections looks (awkwardly) like this:
 | 
			
		||||
        # {
 | 
			
		||||
        #   'config': {
 | 
			
		||||
        #      'config': {
 | 
			
		||||
        #         ... data ...
 | 
			
		||||
        #       }
 | 
			
		||||
        #   },
 | 
			
		||||
        #   'packages': {
 | 
			
		||||
        #      'packages': {
 | 
			
		||||
        #         ... data ...
 | 
			
		||||
        #      }
 | 
			
		||||
        #   }
 | 
			
		||||
        # }
 | 
			
		||||
        return self.sections.setdefault(
 | 
			
		||||
            section, {section: self._raw_data.get(section)})
 | 
			
		||||
 | 
			
		||||
    def write_section(self, section):
 | 
			
		||||
        _validate(self.sections, self.schema)
 | 
			
		||||
        try:
 | 
			
		||||
            parent = os.path.dirname(self.path)
 | 
			
		||||
            mkdirp(parent)
 | 
			
		||||
 | 
			
		||||
            tmp = os.path.join(parent, '.%s.tmp' % self.path)
 | 
			
		||||
            with open(tmp, 'w') as f:
 | 
			
		||||
                syaml.dump(self.sections, stream=f, default_flow_style=False)
 | 
			
		||||
            os.path.move(tmp, self.path)
 | 
			
		||||
        except (yaml.YAMLError, IOError) as e:
 | 
			
		||||
            raise ConfigFileError(
 | 
			
		||||
                "Error writing to config file: '%s'" % str(e))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return '<SingleFileScope: %s: %s>' % (self.name, self.path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ImmutableConfigScope(ConfigScope):
 | 
			
		||||
    """A configuration scope that cannot be written to.
 | 
			
		||||
 | 
			
		||||
@@ -215,7 +288,7 @@ class InternalConfigScope(ConfigScope):
 | 
			
		||||
    override settings from files.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name, data=None):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        super(InternalConfigScope, self).__init__(name, None)
 | 
			
		||||
        self.sections = syaml.syaml_dict()
 | 
			
		||||
 | 
			
		||||
        if data:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,8 @@
 | 
			
		||||
##############################################################################
 | 
			
		||||
# Copyright (c) 2013-2018, Lawrence Livermore National Security, LLC.
 | 
			
		||||
# Produced at the Lawrence Livermore National Laboratory.
 | 
			
		||||
# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
 | 
			
		||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
 | 
			
		||||
#
 | 
			
		||||
# 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/spack/spack
 | 
			
		||||
# Please also see the NOTICE and LICENSE files 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
 | 
			
		||||
##############################################################################
 | 
			
		||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import shutil
 | 
			
		||||
 
 | 
			
		||||
@@ -6,41 +6,52 @@
 | 
			
		||||
"""Schema for env.yaml configuration file.
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../spack/schema/env.py
 | 
			
		||||
   :lines: 32-
 | 
			
		||||
   :lines: 36-
 | 
			
		||||
"""
 | 
			
		||||
from llnl.util.lang import union_dicts
 | 
			
		||||
 | 
			
		||||
import spack.schema.merged
 | 
			
		||||
import spack.schema.modules
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
schema = {
 | 
			
		||||
    '$schema': 'http://json-schema.org/schema#',
 | 
			
		||||
    'title': 'Spack environment file schema',
 | 
			
		||||
    'definitions': spack.schema.modules.definitions,
 | 
			
		||||
    'type': 'object',
 | 
			
		||||
    'additionalProperties': False,
 | 
			
		||||
    'patternProperties': {
 | 
			
		||||
        '^env|spack$': {
 | 
			
		||||
            'type': 'object',
 | 
			
		||||
            'default': {},
 | 
			
		||||
            'properties': {
 | 
			
		||||
            'additionalProperties': False,
 | 
			
		||||
            'properties': union_dicts(
 | 
			
		||||
                # merged configuration scope schemas
 | 
			
		||||
                spack.schema.merged.properties,
 | 
			
		||||
                # extra environment schema properties
 | 
			
		||||
                {
 | 
			
		||||
                    'include': {
 | 
			
		||||
                        'type': 'array',
 | 
			
		||||
                        'items': {
 | 
			
		||||
                            'type': 'string'
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                    'specs': {
 | 
			
		||||
                    'type': 'object',
 | 
			
		||||
                    'default': {},
 | 
			
		||||
                    'additionalProperties': False,
 | 
			
		||||
                    'patternProperties': {
 | 
			
		||||
                        r'\w[\w-]*': {  # user spec
 | 
			
		||||
                            'type': 'object',
 | 
			
		||||
                            'default': {},
 | 
			
		||||
                        # Specs is a list of specs, which can have
 | 
			
		||||
                        # optional additional properties in a sub-dict
 | 
			
		||||
                        'type': 'array',
 | 
			
		||||
                        'default': [],
 | 
			
		||||
                        'additionalProperties': False,
 | 
			
		||||
                        'items': {
 | 
			
		||||
                            'anyOf': [
 | 
			
		||||
                                {'type': 'string'},
 | 
			
		||||
                                {'type': 'null'},
 | 
			
		||||
                                {'type': 'object'},
 | 
			
		||||
                            ]
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								lib/spack/spack/schema/merged.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								lib/spack/spack/schema/merged.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
 | 
			
		||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
 | 
			
		||||
#
 | 
			
		||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
			
		||||
 | 
			
		||||
"""Schema for configuration merged into one file.
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: ../spack/schema/merged.py
 | 
			
		||||
   :lines: 40-
 | 
			
		||||
"""
 | 
			
		||||
from llnl.util.lang import union_dicts
 | 
			
		||||
 | 
			
		||||
import spack.schema.compilers
 | 
			
		||||
import spack.schema.config
 | 
			
		||||
import spack.schema.mirrors
 | 
			
		||||
import spack.schema.modules
 | 
			
		||||
import spack.schema.packages
 | 
			
		||||
import spack.schema.repos
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#: Properties for inclusion in other schemas
 | 
			
		||||
properties = union_dicts(
 | 
			
		||||
    spack.schema.compilers.properties,
 | 
			
		||||
    spack.schema.config.properties,
 | 
			
		||||
    spack.schema.mirrors.properties,
 | 
			
		||||
    spack.schema.modules.properties,
 | 
			
		||||
    spack.schema.packages.properties,
 | 
			
		||||
    spack.schema.repos.properties
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#: Full schema with metadata
 | 
			
		||||
schema = {
 | 
			
		||||
    '$schema': 'http://json-schema.org/schema#',
 | 
			
		||||
    'title': 'Spack merged configuration file schema',
 | 
			
		||||
    'definitions': spack.schema.modules.definitions,
 | 
			
		||||
    'type': 'object',
 | 
			
		||||
    'additionalProperties': False,
 | 
			
		||||
    'properties': properties,
 | 
			
		||||
}
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
import spack.config
 | 
			
		||||
import spack.schema.compilers
 | 
			
		||||
import spack.schema.config
 | 
			
		||||
import spack.schema.env
 | 
			
		||||
import spack.schema.packages
 | 
			
		||||
import spack.schema.mirrors
 | 
			
		||||
import spack.schema.repos
 | 
			
		||||
@@ -49,7 +50,7 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture()
 | 
			
		||||
def config(tmpdir):
 | 
			
		||||
def mock_config(tmpdir):
 | 
			
		||||
    """Mocks the configuration scope."""
 | 
			
		||||
    real_configuration = spack.config.config
 | 
			
		||||
 | 
			
		||||
@@ -216,7 +217,7 @@ def compiler_specs():
 | 
			
		||||
    return CompilerSpecs(a=a, b=b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_write_key_in_memory(config, compiler_specs):
 | 
			
		||||
def test_write_key_in_memory(mock_config, compiler_specs):
 | 
			
		||||
    # Write b_comps "on top of" a_comps.
 | 
			
		||||
    spack.config.set('compilers', a_comps['compilers'], scope='low')
 | 
			
		||||
    spack.config.set('compilers', b_comps['compilers'], scope='high')
 | 
			
		||||
@@ -226,7 +227,7 @@ def test_write_key_in_memory(config, compiler_specs):
 | 
			
		||||
    check_compiler_config(b_comps['compilers'], *compiler_specs.b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_write_key_to_disk(config, compiler_specs):
 | 
			
		||||
def test_write_key_to_disk(mock_config, compiler_specs):
 | 
			
		||||
    # Write b_comps "on top of" a_comps.
 | 
			
		||||
    spack.config.set('compilers', a_comps['compilers'], scope='low')
 | 
			
		||||
    spack.config.set('compilers', b_comps['compilers'], scope='high')
 | 
			
		||||
@@ -239,7 +240,7 @@ def test_write_key_to_disk(config, compiler_specs):
 | 
			
		||||
    check_compiler_config(b_comps['compilers'], *compiler_specs.b)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_write_to_same_priority_file(config, compiler_specs):
 | 
			
		||||
def test_write_to_same_priority_file(mock_config, compiler_specs):
 | 
			
		||||
    # Write b_comps in the same file as a_comps.
 | 
			
		||||
    spack.config.set('compilers', a_comps['compilers'], scope='low')
 | 
			
		||||
    spack.config.set('compilers', b_comps['compilers'], scope='low')
 | 
			
		||||
@@ -260,7 +261,7 @@ def test_write_to_same_priority_file(config, compiler_specs):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# repos
 | 
			
		||||
def test_write_list_in_memory(config):
 | 
			
		||||
def test_write_list_in_memory(mock_config):
 | 
			
		||||
    spack.config.set('repos', repos_low['repos'], scope='low')
 | 
			
		||||
    spack.config.set('repos', repos_high['repos'], scope='high')
 | 
			
		||||
 | 
			
		||||
@@ -268,7 +269,7 @@ def test_write_list_in_memory(config):
 | 
			
		||||
    assert config == repos_high['repos'] + repos_low['repos']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_substitute_config_variables(config):
 | 
			
		||||
def test_substitute_config_variables(mock_config):
 | 
			
		||||
    prefix = spack.paths.prefix.lstrip('/')
 | 
			
		||||
 | 
			
		||||
    assert os.path.join(
 | 
			
		||||
@@ -328,7 +329,7 @@ def test_substitute_config_variables(config):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.regression('7924')
 | 
			
		||||
def test_merge_with_defaults(config, write_config_file):
 | 
			
		||||
def test_merge_with_defaults(mock_config, write_config_file):
 | 
			
		||||
    """This ensures that specified preferences merge with defaults as
 | 
			
		||||
       expected. Originally all defaults were initialized with the
 | 
			
		||||
       exact same object, which led to aliasing problems. Therefore
 | 
			
		||||
@@ -344,14 +345,14 @@ def test_merge_with_defaults(config, write_config_file):
 | 
			
		||||
    assert cfg['baz']['version'] == ['c']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_substitute_user(config):
 | 
			
		||||
def test_substitute_user(mock_config):
 | 
			
		||||
    user = getpass.getuser()
 | 
			
		||||
    assert '/foo/bar/' + user + '/baz' == canonicalize_path(
 | 
			
		||||
        '/foo/bar/$user/baz'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_substitute_tempdir(config):
 | 
			
		||||
def test_substitute_tempdir(mock_config):
 | 
			
		||||
    tempdir = tempfile.gettempdir()
 | 
			
		||||
    assert tempdir == canonicalize_path('$tempdir')
 | 
			
		||||
    assert tempdir + '/foo/bar/baz' == canonicalize_path(
 | 
			
		||||
@@ -359,12 +360,12 @@ def test_substitute_tempdir(config):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_config(config, write_config_file):
 | 
			
		||||
def test_read_config(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    assert spack.config.get('config') == config_low['config']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_config_override_all(config, write_config_file):
 | 
			
		||||
def test_read_config_override_all(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    write_config_file('config', config_override_all, 'high')
 | 
			
		||||
    assert spack.config.get('config') == {
 | 
			
		||||
@@ -372,7 +373,7 @@ def test_read_config_override_all(config, write_config_file):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_config_override_key(config, write_config_file):
 | 
			
		||||
def test_read_config_override_key(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    write_config_file('config', config_override_key, 'high')
 | 
			
		||||
    assert spack.config.get('config') == {
 | 
			
		||||
@@ -381,7 +382,7 @@ def test_read_config_override_key(config, write_config_file):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_config_merge_list(config, write_config_file):
 | 
			
		||||
def test_read_config_merge_list(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    write_config_file('config', config_merge_list, 'high')
 | 
			
		||||
    assert spack.config.get('config') == {
 | 
			
		||||
@@ -390,7 +391,7 @@ def test_read_config_merge_list(config, write_config_file):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_read_config_override_list(config, write_config_file):
 | 
			
		||||
def test_read_config_override_list(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    write_config_file('config', config_override_list, 'high')
 | 
			
		||||
    assert spack.config.get('config') == {
 | 
			
		||||
@@ -399,33 +400,33 @@ def test_read_config_override_list(config, write_config_file):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_internal_config_update(config, write_config_file):
 | 
			
		||||
def test_internal_config_update(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
 | 
			
		||||
    before = config.get('config')
 | 
			
		||||
    before = mock_config.get('config')
 | 
			
		||||
    assert before['install_tree'] == 'install_tree_path'
 | 
			
		||||
 | 
			
		||||
    # add an internal configuration scope
 | 
			
		||||
    scope = spack.config.InternalConfigScope('command_line')
 | 
			
		||||
    assert 'InternalConfigScope' in repr(scope)
 | 
			
		||||
 | 
			
		||||
    config.push_scope(scope)
 | 
			
		||||
    mock_config.push_scope(scope)
 | 
			
		||||
 | 
			
		||||
    command_config = config.get('config', scope='command_line')
 | 
			
		||||
    command_config = mock_config.get('config', scope='command_line')
 | 
			
		||||
    command_config['install_tree'] = 'foo/bar'
 | 
			
		||||
 | 
			
		||||
    config.set('config', command_config, scope='command_line')
 | 
			
		||||
    mock_config.set('config', command_config, scope='command_line')
 | 
			
		||||
 | 
			
		||||
    after = config.get('config')
 | 
			
		||||
    after = mock_config.get('config')
 | 
			
		||||
    assert after['install_tree'] == 'foo/bar'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_internal_config_filename(config, write_config_file):
 | 
			
		||||
def test_internal_config_filename(mock_config, write_config_file):
 | 
			
		||||
    write_config_file('config', config_low, 'low')
 | 
			
		||||
    config.push_scope(spack.config.InternalConfigScope('command_line'))
 | 
			
		||||
    mock_config.push_scope(spack.config.InternalConfigScope('command_line'))
 | 
			
		||||
 | 
			
		||||
    with pytest.raises(NotImplementedError):
 | 
			
		||||
        config.get_config_filename('command_line', 'config')
 | 
			
		||||
        mock_config.get_config_filename('command_line', 'config')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_mark_internal():
 | 
			
		||||
@@ -597,7 +598,7 @@ def test_config_parse_list_in_dict(tmpdir):
 | 
			
		||||
        assert "mirrors.yaml:5" in str(e)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_bad_config_section(config):
 | 
			
		||||
def test_bad_config_section(mock_config):
 | 
			
		||||
    """Test that getting or setting a bad section gives an error."""
 | 
			
		||||
    with pytest.raises(spack.config.ConfigSectionError):
 | 
			
		||||
        spack.config.set('foobar', 'foobar')
 | 
			
		||||
@@ -606,7 +607,7 @@ def test_bad_config_section(config):
 | 
			
		||||
        spack.config.get('foobar')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_bad_command_line_scopes(tmpdir, config):
 | 
			
		||||
def test_bad_command_line_scopes(tmpdir, mock_config):
 | 
			
		||||
    cfg = spack.config.Configuration()
 | 
			
		||||
 | 
			
		||||
    with tmpdir.as_cwd():
 | 
			
		||||
@@ -631,9 +632,9 @@ def test_add_command_line_scopes(tmpdir, mutable_config):
 | 
			
		||||
    with open(config_yaml, 'w') as f:
 | 
			
		||||
            f.write("""\
 | 
			
		||||
config:
 | 
			
		||||
    verify_ssh: False
 | 
			
		||||
    verify_ssl: False
 | 
			
		||||
    dirty: False
 | 
			
		||||
"""'')
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
    spack.config._add_command_line_scopes(mutable_config, [str(tmpdir)])
 | 
			
		||||
 | 
			
		||||
@@ -644,7 +645,7 @@ def test_immutable_scope(tmpdir):
 | 
			
		||||
        f.write("""\
 | 
			
		||||
config:
 | 
			
		||||
    install_tree: dummy_tree_value
 | 
			
		||||
"""'')
 | 
			
		||||
""")
 | 
			
		||||
    scope = spack.config.ImmutableConfigScope('test', str(tmpdir))
 | 
			
		||||
 | 
			
		||||
    data = scope.get_section('config')
 | 
			
		||||
@@ -654,6 +655,38 @@ def test_immutable_scope(tmpdir):
 | 
			
		||||
        scope.write_section('config')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_single_file_scope(tmpdir, config):
 | 
			
		||||
    env_yaml = str(tmpdir.join("env.yaml"))
 | 
			
		||||
    with open(env_yaml, 'w') as f:
 | 
			
		||||
        f.write("""\
 | 
			
		||||
env:
 | 
			
		||||
    config:
 | 
			
		||||
        verify_ssl: False
 | 
			
		||||
        dirty: False
 | 
			
		||||
    packages:
 | 
			
		||||
        libelf:
 | 
			
		||||
            compiler: [ 'gcc@4.5.3' ]
 | 
			
		||||
    repos:
 | 
			
		||||
        - /x/y/z
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
    scope = spack.config.SingleFileScope(
 | 
			
		||||
        'env', env_yaml, spack.schema.env.schema, ['env'])
 | 
			
		||||
 | 
			
		||||
    with spack.config.override(scope):
 | 
			
		||||
        # from the single-file config
 | 
			
		||||
        assert spack.config.get('config:verify_ssl') is False
 | 
			
		||||
        assert spack.config.get('config:dirty') is False
 | 
			
		||||
        assert spack.config.get('packages:libelf:compiler') == ['gcc@4.5.3']
 | 
			
		||||
 | 
			
		||||
        # from the lower config scopes
 | 
			
		||||
        assert spack.config.get('config:checksum') is True
 | 
			
		||||
        assert spack.config.get('config:checksum') is True
 | 
			
		||||
        assert spack.config.get('packages:externalmodule:buildable') is False
 | 
			
		||||
        assert spack.config.get('repos') == [
 | 
			
		||||
            '/x/y/z', '$spack/var/spack/repos/builtin']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_schema(name, file_contents):
 | 
			
		||||
    """Check a Spack YAML schema against some data"""
 | 
			
		||||
    f = StringIO(file_contents)
 | 
			
		||||
@@ -661,6 +694,39 @@ def check_schema(name, file_contents):
 | 
			
		||||
    spack.config._validate(data, name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_good_env_yaml(tmpdir):
 | 
			
		||||
    check_schema(spack.schema.env.schema, """\
 | 
			
		||||
spack:
 | 
			
		||||
    config:
 | 
			
		||||
        verify_ssl: False
 | 
			
		||||
        dirty: False
 | 
			
		||||
    repos:
 | 
			
		||||
        - ~/my/repo/location
 | 
			
		||||
    mirrors:
 | 
			
		||||
        remote: /foo/bar/baz
 | 
			
		||||
    compilers:
 | 
			
		||||
        - compiler:
 | 
			
		||||
            spec: cce@2.1
 | 
			
		||||
            operating_system: cnl
 | 
			
		||||
            modules: []
 | 
			
		||||
            paths:
 | 
			
		||||
                cc: /path/to/cc
 | 
			
		||||
                cxx: /path/to/cxx
 | 
			
		||||
                fc: /path/to/fc
 | 
			
		||||
                f77: /path/to/f77
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_bad_env_yaml(tmpdir):
 | 
			
		||||
    with pytest.raises(spack.config.ConfigFormatError):
 | 
			
		||||
        check_schema(spack.schema.env.schema, """\
 | 
			
		||||
env:
 | 
			
		||||
    foobar:
 | 
			
		||||
        verify_ssl: False
 | 
			
		||||
        dirty: False
 | 
			
		||||
""")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_bad_config_yaml(tmpdir):
 | 
			
		||||
    with pytest.raises(spack.config.ConfigFormatError):
 | 
			
		||||
        check_schema(spack.schema.config.schema, """\
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user