From 2ef8d09fc78f44f2f60b165f65595fccc161a54c Mon Sep 17 00:00:00 2001 From: Massimiliano Culpo Date: Tue, 19 Dec 2023 10:26:53 +0100 Subject: `spack config get`/`blame`: with no args, show entire config This PR changes the default behavior of `spack config get` and `spack config blame` to print a flattened version of the entire spack configuration, including any active environment, if the commands are invoked with no section arguments. The new behavior is used in Gitlab CI to help debug CI configuration, but it can also be useful when asking for more information in issues, or when simply debugging Spack. --- lib/spack/spack/cmd/config.py | 52 ++++++++++++++++------- lib/spack/spack/config.py | 4 +- lib/spack/spack/test/cmd/config.py | 29 +++---------- share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml | 7 +++ 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py index e7d459ff77..e7b859adb7 100644 --- a/lib/spack/spack/cmd/config.py +++ b/lib/spack/spack/cmd/config.py @@ -5,6 +5,7 @@ import collections import os import shutil +import sys from typing import List import llnl.util.filesystem as fs @@ -48,6 +49,7 @@ def setup_parser(subparser): blame_parser.add_argument( "section", help="configuration section to print\n\noptions: %(choices)s", + nargs="?", metavar="section", choices=spack.config.SECTION_SCHEMAS, ) @@ -131,32 +133,50 @@ def _get_scope_and_section(args): return scope, section +def print_configuration(args, *, blame: bool) -> None: + if args.scope and args.section is None: + tty.die(f"the argument --scope={args.scope} requires specifying a section.") + + if args.section is not None: + spack.config.CONFIG.print_section(args.section, blame=blame, scope=args.scope) + return + + print_flattened_configuration(blame=blame) + + +def print_flattened_configuration(*, blame: bool) -> None: + """Prints to stdout a flattened version of the configuration. + + Args: + blame: if True, shows file provenance for each entry in the configuration. + """ + env = ev.active_environment() + if env is not None: + pristine = env.manifest.pristine_yaml_content + flattened = pristine.copy() + flattened[spack.schema.env.TOP_LEVEL_KEY] = pristine[spack.schema.env.TOP_LEVEL_KEY].copy() + else: + flattened = syaml.syaml_dict() + flattened[spack.schema.env.TOP_LEVEL_KEY] = syaml.syaml_dict() + + for config_section in spack.config.SECTION_SCHEMAS: + current = spack.config.get(config_section) + flattened[spack.schema.env.TOP_LEVEL_KEY][config_section] = current + syaml.dump_config(flattened, stream=sys.stdout, default_flow_style=False, blame=blame) + + def config_get(args): """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 section is not None: - spack.config.CONFIG.print_section(section) - - elif 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) - - else: - tty.die("`spack config get` requires a section argument or an active environment.") + print_configuration(args, blame=False) def config_blame(args): """Print out line-by-line blame of merged YAML.""" - spack.config.CONFIG.print_section(args.section, blame=True) + print_configuration(args, blame=True) def config_edit(args): diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index eff978718c..b661f4dc48 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -699,11 +699,11 @@ class Configuration: """Iterate over scopes in this configuration.""" yield from self.scopes.values() - def print_section(self, section: str, blame: bool = False) -> None: + def print_section(self, section: str, blame: bool = False, *, scope=None) -> None: """Print a configuration to stdout.""" try: data = syaml.syaml_dict() - data[section] = self.get_config(section) + data[section] = self.get_config(section, scope=scope) syaml.dump_config(data, stream=sys.stdout, default_flow_style=False, blame=blame) except (syaml.SpackYAMLError, OSError) as e: raise ConfigError(f"cannot read '{section}' configuration") from e diff --git a/lib/spack/spack/test/cmd/config.py b/lib/spack/spack/test/cmd/config.py index 7247ce9753..bec9db3881 100644 --- a/lib/spack/spack/test/cmd/config.py +++ b/lib/spack/spack/test/cmd/config.py @@ -91,15 +91,10 @@ def test_config_edit(mutable_config, working_env): def test_config_get_gets_spack_yaml(mutable_mock_env_path): - config("get", fail_on_error=False) - assert config.returncode == 1 - with ev.create("test") as env: assert "mpileaks" not in config("get") - env.add("mpileaks") env.write() - assert "mpileaks" in config("get") @@ -122,11 +117,6 @@ def test_config_edit_fails_correctly_with_no_env(mutable_mock_env_path): 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 - - def test_config_list(): output = config("list") assert "compilers" in output @@ -470,7 +460,6 @@ def test_config_add_to_env(mutable_empty_config, mutable_mock_env_path): expected = """ config: dirty: true - """ assert expected in output @@ -497,29 +486,21 @@ spack: # comment config("add", "config:dirty:true") output = config("get") - expected = manifest - expected += """ config: - dirty: true - -""" - assert output == expected + assert "# comment" in output + assert "dirty: true" in output def test_config_remove_from_env(mutable_empty_config, mutable_mock_env_path): env("create", "test") - with ev.read("test"): config("add", "config:dirty:true") + output = config("get") + assert "dirty: true" in output with ev.read("test"): config("rm", "config:dirty") output = config("get") - - expected = ev.default_manifest_yaml() - expected += """ config: {} - -""" - assert output == expected + assert "dirty: true" not in output def test_config_update_config(config_yaml_v015): diff --git a/share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml b/share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml index e1d7561300..0c770ee872 100644 --- a/share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml +++ b/share/spack/gitlab/cloud_pipelines/.gitlab-ci.yml @@ -150,6 +150,13 @@ default: - spack python -c "import os,sys; print(os.path.expandvars(sys.stdin.read()))" < "${SPACK_CI_CONFIG_ROOT}/${PIPELINE_MIRROR_TEMPLATE}" > "${SPACK_CI_CONFIG_ROOT}/mirrors.yaml" - spack config add -f "${SPACK_CI_CONFIG_ROOT}/mirrors.yaml" + - mkdir -p "${CI_PROJECT_DIR}/jobs_scratch_dir" + - spack + --config-scope "${SPACK_CI_CONFIG_ROOT}" + --config-scope "${SPACK_CI_CONFIG_ROOT}/${SPACK_TARGET_PLATFORM}" + --config-scope "${SPACK_CI_CONFIG_ROOT}/${SPACK_TARGET_PLATFORM}/${SPACK_TARGET_ARCH}" + ${CI_STACK_CONFIG_SCOPES} + config blame > "${CI_PROJECT_DIR}/jobs_scratch_dir/spack.yaml.blame" - spack -v --color=always --config-scope "${SPACK_CI_CONFIG_ROOT}" --config-scope "${SPACK_CI_CONFIG_ROOT}/${SPACK_TARGET_PLATFORM}" -- cgit v1.2.3-70-g09d2