From 8d92fd66401a46af641c799394480140300f2e3d Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Wed, 7 Nov 2018 18:43:35 -0800 Subject: env: make `spack config edit` and `spack config get` environment-aware - with no arguments, these commands will now edit or dump the environment's `spack.yaml` file. - users may not know where named environments live - this makes it convenient for users to get to the spack.yaml configuration file for their named environment. --- lib/spack/spack/cmd/config.py | 83 +++++++++++++++++++++++++++++----- lib/spack/spack/environment.py | 6 ++- lib/spack/spack/test/cmd/config.py | 93 ++++++++++++++++++++++++++++++++++++++ lib/spack/spack/test/config.py | 14 ------ lib/spack/spack/test/conftest.py | 14 ++++++ 5 files changed, 183 insertions(+), 27 deletions(-) create mode 100644 lib/spack/spack/test/cmd/config.py (limited to 'lib') diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py index 754b1e8336..4fd21768c8 100644 --- a/lib/spack/spack/cmd/config.py +++ b/lib/spack/spack/cmd/config.py @@ -3,7 +3,13 @@ # # SPDX-License-Identifier: (Apache-2.0 OR MIT) +from __future__ import print_function +import os + +import llnl.util.tty as tty + import spack.config +import spack.environment as ev from spack.util.editor import editor @@ -27,6 +33,7 @@ def setup_parser(subparser): get_parser.add_argument('section', help="configuration section to print. " "options: %(choices)s", + nargs='?', metavar='SECTION', choices=spack.config.section_schemas) @@ -43,29 +50,81 @@ def setup_parser(subparser): help="configuration section to edit. " "options: %(choices)s", metavar='SECTION', + nargs='?', choices=spack.config.section_schemas) + edit_parser.add_argument( + '--print-file', action='store_true', + help="print the file name that would be edited") + + +def _get_scope_and_section(args): + """Extract config scope and section from arguments.""" + scope = args.scope + section = args.section + + # w/no args and an active environment, point to env manifest + if not args.section: + env = ev.get_env(args, 'config edit', required=False) + if env: + scope = env.env_file_config_scope_name() + + # set scope defaults + elif not args.scope: + if section == 'compilers': + scope = spack.config.default_modify_scope() + else: + scope = 'user' + + return scope, section def config_get(args): - spack.config.config.print_section(args.section) + """Dump merged YAML configuration for a specific section. + + With no arguments and an active environment, print the contents of + the environment's manifest file (spack.yaml). + + """ + scope, section = _get_scope_and_section(args) + + if scope and scope.startswith('env:'): + config_file = spack.config.config.get_config_filename(scope, section) + if os.path.exists(config_file): + with open(config_file) as f: + print(f.read()) + else: + tty.die('environment has no %s file' % ev.manifest_name) + + elif section is not None: + spack.config.config.print_section(section) + + else: + tty.die('`spack config get` requires a section argument ' + 'or an active environment.') def config_blame(args): + """Print out line-by-line blame of merged YAML.""" spack.config.config.print_section(args.section, blame=True) def config_edit(args): - if not args.scope: - if args.section == 'compilers': - args.scope = spack.config.default_modify_scope() - else: - args.scope = 'user' - if not args.section: - args.section = None - - config = spack.config.config - config_file = config.get_config_filename(args.scope, args.section) - editor(config_file) + """Edit the configuration file for a specific scope and config section. + + With no arguments and an active environment, edit the spack.yaml for + the active environment. + + """ + scope, section = _get_scope_and_section(args) + if not scope and not section: + tty.die('`spack config edit` requires a section argument ' + 'or an active environment.') + + config_file = spack.config.config.get_config_filename(scope, section) + if args.print_file: + print(config_file) + else: + editor(config_file) def config(parser, args): diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index 208eb485ae..1ac5a99159 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -483,9 +483,13 @@ class Environment(object): return scopes + def env_file_config_scope_name(self): + """Name of the config scope of this environment's manifest file.""" + return 'env:%s' % self.name + def env_file_config_scope(self): """Get the configuration scope for the environment's manifest file.""" - config_name = 'env:%s' % self.name + config_name = self.env_file_config_scope_name() return spack.config.SingleFileScope(config_name, self.manifest_path, spack.schema.env.schema, diff --git a/lib/spack/spack/test/cmd/config.py b/lib/spack/spack/test/cmd/config.py new file mode 100644 index 0000000000..493236709b --- /dev/null +++ b/lib/spack/spack/test/cmd/config.py @@ -0,0 +1,93 @@ +# 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) + +import os + +from llnl.util.filesystem import mkdirp + +import spack.config +import spack.environment as ev +from spack.main import SpackCommand + +config = SpackCommand('config') + + +def test_get_config_scope(mock_config): + assert config('get', 'compilers').strip() == 'compilers: {}' + + +def test_get_config_scope_merged(mock_config): + low_path = mock_config.scopes['low'].path + high_path = mock_config.scopes['high'].path + + mkdirp(low_path) + mkdirp(high_path) + + with open(os.path.join(low_path, 'repos.yaml'), 'w') as f: + f.write('''\ +repos: +- repo3 +''') + + with open(os.path.join(high_path, 'repos.yaml'), 'w') as f: + f.write('''\ +repos: +- repo1 +- repo2 +''') + + assert config('get', 'repos').strip() == '''repos: +- repo1 +- repo2 +- repo3''' + + +def test_config_edit(): + """Ensure `spack config edit` edits the right paths.""" + dms = spack.config.default_modify_scope() + dms_path = spack.config.config.scopes[dms].path + user_path = spack.config.config.scopes['user'].path + + comp_path = os.path.join(dms_path, 'compilers.yaml') + repos_path = os.path.join(user_path, 'repos.yaml') + + assert config('edit', '--print-file', 'compilers').strip() == comp_path + assert config('edit', '--print-file', 'repos').strip() == repos_path + + +def test_config_get_gets_spack_yaml(mutable_mock_env_path): + env = ev.create('test') + + config('get', fail_on_error=False) + assert config.returncode == 1 + + with env: + config('get', fail_on_error=False) + assert config.returncode == 1 + + env.write() + + assert 'mpileaks' not in config('get') + + env.add('mpileaks') + env.write() + + assert 'mpileaks' in config('get') + + +def test_config_edit_edits_spack_yaml(mutable_mock_env_path): + env = ev.create('test') + with env: + assert config('edit', '--print-file').strip() == env.manifest_path + + +def test_config_edit_fails_correctly_with_no_env(mutable_mock_env_path): + output = config('edit', '--print-file', fail_on_error=False) + assert "requires a section argument or an active environment" in output + + +def test_config_get_fails_correctly_with_no_env(mutable_mock_env_path): + output = config('get', fail_on_error=False) + assert "requires a section argument or an active environment" in output diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index 1a162bd598..1235d57f30 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -49,20 +49,6 @@ config_override_list = { 'build_stage:': ['patha', 'pathb']}} -@pytest.fixture() -def mock_config(tmpdir): - """Mocks the configuration scope.""" - real_configuration = spack.config.config - - spack.config.config = spack.config.Configuration( - *[spack.config.ConfigScope(name, str(tmpdir.join(name))) - for name in ['low', 'high']]) - - yield spack.config.config - - spack.config.config = real_configuration - - @pytest.fixture() def write_config_file(tmpdir): """Returns a function that writes a config file.""" diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py index 4405335cf4..ad0644da3e 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -309,6 +309,20 @@ def mutable_config(tmpdir_factory, configuration_dir, config): spack.package_prefs.PackagePrefs.clear_caches() +@pytest.fixture() +def mock_config(tmpdir): + """Mocks two configuration scopes: 'low' and 'high'.""" + real_configuration = spack.config.config + + spack.config.config = spack.config.Configuration( + *[spack.config.ConfigScope(name, str(tmpdir.join(name))) + for name in ['low', 'high']]) + + yield spack.config.config + + spack.config.config = real_configuration + + def _populate(mock_db): r"""Populate a mock database with packages. -- cgit v1.2.3-60-g2f50