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.
This commit is contained in:
Massimiliano Culpo 2023-12-19 10:26:53 +01:00 committed by GitHub
parent e5e767b300
commit 2ef8d09fc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 42 deletions

View File

@ -5,6 +5,7 @@
import collections import collections
import os import os
import shutil import shutil
import sys
from typing import List from typing import List
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
@ -48,6 +49,7 @@ def setup_parser(subparser):
blame_parser.add_argument( blame_parser.add_argument(
"section", "section",
help="configuration section to print\n\noptions: %(choices)s", help="configuration section to print\n\noptions: %(choices)s",
nargs="?",
metavar="section", metavar="section",
choices=spack.config.SECTION_SCHEMAS, choices=spack.config.SECTION_SCHEMAS,
) )
@ -131,32 +133,50 @@ def _get_scope_and_section(args):
return scope, section 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): def config_get(args):
"""Dump merged YAML configuration for a specific section. """Dump merged YAML configuration for a specific section.
With no arguments and an active environment, print the contents of With no arguments and an active environment, print the contents of
the environment's manifest file (spack.yaml). the environment's manifest file (spack.yaml).
""" """
scope, section = _get_scope_and_section(args) print_configuration(args, blame=False)
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.")
def config_blame(args): def config_blame(args):
"""Print out line-by-line blame of merged YAML.""" """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): def config_edit(args):

View File

@ -699,11 +699,11 @@ def __iter__(self):
"""Iterate over scopes in this configuration.""" """Iterate over scopes in this configuration."""
yield from self.scopes.values() 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.""" """Print a configuration to stdout."""
try: try:
data = syaml.syaml_dict() 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) syaml.dump_config(data, stream=sys.stdout, default_flow_style=False, blame=blame)
except (syaml.SpackYAMLError, OSError) as e: except (syaml.SpackYAMLError, OSError) as e:
raise ConfigError(f"cannot read '{section}' configuration") from e raise ConfigError(f"cannot read '{section}' configuration") from e

View File

@ -91,15 +91,10 @@ def test_config_edit(mutable_config, working_env):
def test_config_get_gets_spack_yaml(mutable_mock_env_path): 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: with ev.create("test") as env:
assert "mpileaks" not in config("get") assert "mpileaks" not in config("get")
env.add("mpileaks") env.add("mpileaks")
env.write() env.write()
assert "mpileaks" in config("get") 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 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(): def test_config_list():
output = config("list") output = config("list")
assert "compilers" in output assert "compilers" in output
@ -470,7 +460,6 @@ def test_config_add_to_env(mutable_empty_config, mutable_mock_env_path):
expected = """ config: expected = """ config:
dirty: true dirty: true
""" """
assert expected in output assert expected in output
@ -497,29 +486,21 @@ def test_config_add_to_env_preserve_comments(mutable_empty_config, mutable_mock_
config("add", "config:dirty:true") config("add", "config:dirty:true")
output = config("get") output = config("get")
expected = manifest assert "# comment" in output
expected += """ config: assert "dirty: true" in output
dirty: true
"""
assert output == expected
def test_config_remove_from_env(mutable_empty_config, mutable_mock_env_path): def test_config_remove_from_env(mutable_empty_config, mutable_mock_env_path):
env("create", "test") env("create", "test")
with ev.read("test"): with ev.read("test"):
config("add", "config:dirty:true") config("add", "config:dirty:true")
output = config("get")
assert "dirty: true" in output
with ev.read("test"): with ev.read("test"):
config("rm", "config:dirty") config("rm", "config:dirty")
output = config("get") output = config("get")
assert "dirty: true" not in output
expected = ev.default_manifest_yaml()
expected += """ config: {}
"""
assert output == expected
def test_config_update_config(config_yaml_v015): def test_config_update_config(config_yaml_v015):

View File

@ -150,6 +150,13 @@ default:
- spack python -c "import os,sys; print(os.path.expandvars(sys.stdin.read()))" - 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_CI_CONFIG_ROOT}/${PIPELINE_MIRROR_TEMPLATE}" > "${SPACK_CI_CONFIG_ROOT}/mirrors.yaml"
- spack config add -f "${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 - spack -v --color=always
--config-scope "${SPACK_CI_CONFIG_ROOT}" --config-scope "${SPACK_CI_CONFIG_ROOT}"
--config-scope "${SPACK_CI_CONFIG_ROOT}/${SPACK_TARGET_PLATFORM}" --config-scope "${SPACK_CI_CONFIG_ROOT}/${SPACK_TARGET_PLATFORM}"