Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow querying the contents of settings.yml (and settings_user!) from ConfigAPI #15151

Merged
merged 7 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions conan/api/subapi/config.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import os
import platform
import textwrap

import yaml
from jinja2 import Environment, FileSystemLoader

from conan import conan_version
from conans.client.conf import default_settings_yml
from conan.internal.api import detect_api
from conan.internal.cache.home_paths import HomePaths
from conan.internal.conan_app import ConanApp
from conans.model.conf import ConfDefinition
from conans.errors import ConanException
from conans.model.conf import ConfDefinition, BUILT_IN_CONFS
from conans.model.settings import Settings
from conans.util.files import load, save


Expand Down Expand Up @@ -68,3 +71,47 @@ def global_conf(self):
""")
save(global_conf_path, default_global_conf)
return self._new_config

@property
def builtin_confs(self):
return BUILT_IN_CONFS

@property
def settings_yml(self):
"""Returns {setting: [value, ...]} defining all the possible
settings without values"""
_home_paths = HomePaths(self.conan_api.cache_folder)
settings_path = _home_paths.settings_path
if not os.path.exists(settings_path):
save(settings_path, default_settings_yml)
save(settings_path + ".orig", default_settings_yml) # stores a copy, to check migrations

def _load_settings(path):
try:
return yaml.safe_load(load(path)) or {}
except yaml.YAMLError as ye:
raise ConanException("Invalid settings.yml format: {}".format(ye))

settings = _load_settings(settings_path)
user_settings_file = _home_paths.settings_path_user
if os.path.exists(user_settings_file):
settings_user = _load_settings(user_settings_file)

def appending_recursive_dict_update(d, u):
# Not the same behavior as conandata_update, because this append lists
for k, v in u.items():
if isinstance(v, list):
current = d.get(k) or []
d[k] = current + [value for value in v if value not in current]
elif isinstance(v, dict):
current = d.get(k) or {}
if isinstance(current, list): # convert to dict lists
current = {k: None for k in current}
d[k] = appending_recursive_dict_update(current, v)
else:
d[k] = v
return d

appending_recursive_dict_update(settings, settings_user)

return Settings(settings)
48 changes: 3 additions & 45 deletions conan/api/subapi/profiles.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import os

import yaml

from conan.internal.cache.home_paths import HomePaths
from conans.client.conf import default_settings_yml

from conans.client.loader import load_python_file
from conans.client.profile_loader import ProfileLoader
from conans.errors import ConanException, scoped_traceback
from conans.model.profile import Profile
from conans.model.settings import Settings
from conans.util.files import save, load

DEFAULT_PROFILE_NAME = "default"

Expand Down Expand Up @@ -61,7 +57,7 @@ def get_profiles_from_args(self, args):

global_conf = self._conan_api.config.global_conf
global_conf.validate() # TODO: Remove this from here
cache_settings = self._settings()
cache_settings = self._conan_api.config.settings_yml
profile_plugin = self._load_profile_plugin()
cwd = os.getcwd()
profile_build = self._get_profile(build_profiles, args.settings_build, args.options_build,
Expand All @@ -79,7 +75,7 @@ def get_profile(self, profiles, settings=None, options=None, conf=None, cwd=None
assert isinstance(profiles, list), "Please provide a list of profiles"
global_conf = self._conan_api.config.global_conf
global_conf.validate() # TODO: Remove this from here
cache_settings = self._settings()
cache_settings = self._conan_api.config.settings_yml
profile_plugin = self._load_profile_plugin()

profile = self._get_profile(profiles, settings, options, conf, cwd, cache_settings,
Expand Down Expand Up @@ -150,44 +146,6 @@ def detect():
# good enough at the moment for designing the API interface, but to improve
return profile

def _settings(self):
"""Returns {setting: [value, ...]} defining all the possible
settings without values"""
settings_path = self._home_paths.settings_path
if not os.path.exists(settings_path):
save(settings_path, default_settings_yml)
save(settings_path + ".orig", default_settings_yml) # stores a copy, to check migrations

def _load_settings(path):
try:
return yaml.safe_load(load(path)) or {}
except yaml.YAMLError as ye:
raise ConanException("Invalid settings.yml format: {}".format(ye))

settings = _load_settings(settings_path)
user_settings_file = self._home_paths.settings_path_user
if os.path.exists(user_settings_file):
settings_user = _load_settings(user_settings_file)

def appending_recursive_dict_update(d, u):
# Not the same behavior as conandata_update, because this append lists
for k, v in u.items():
if isinstance(v, list):
current = d.get(k) or []
d[k] = current + [value for value in v if value not in current]
elif isinstance(v, dict):
current = d.get(k) or {}
if isinstance(current, list): # convert to dict lists
current = {k: None for k in current}
d[k] = appending_recursive_dict_update(current, v)
else:
d[k] = v
return d

appending_recursive_dict_update(settings, settings_user)

return Settings(settings)

def _load_profile_plugin(self):
profile_plugin = self._home_paths.profile_plugin_path
if not os.path.exists(profile_plugin):
Expand Down
3 changes: 1 addition & 2 deletions conan/cli/commands/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from conan.api.output import cli_out_write
from conan.cli.command import conan_command, conan_subcommand
from conan.cli.formatters import default_json_formatter
from conans.model.conf import BUILT_IN_CONFS
from conans.util.config_parser import get_bool_from_text


Expand Down Expand Up @@ -65,7 +64,7 @@ def config_list(conan_api, parser, subparser, *args):
Show all the Conan available configurations: core and tools.
"""
parser.parse_args(*args)
return BUILT_IN_CONFS
return conan_api.config.builtin_confs


@conan_subcommand(formatters={"text": list_text_formatter, "json": default_json_formatter})
Expand Down