Skip to content

Commit

Permalink
Allow querying the contents of settings.yml (and settings_user!) from…
Browse files Browse the repository at this point in the history
… ConfigAPI (conan-io#15151)

* Allow querying the contents of settings.yml (and settings_user!) from the API

* Add tests

* Pretty print settings, make ConfigAPI.builtin_confs a @Property

* Fix tests, ensure null is properly printed

* Remove conan config settings command until it's asked by someone

* Remove unneeded test

* Remove unused import
  • Loading branch information
AbrilRBS authored Nov 24, 2023
1 parent c106acc commit 0d9f52f
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 49 deletions.
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

0 comments on commit 0d9f52f

Please sign in to comment.