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

New "conf" configuration mechanism #8266

Merged
merged 29 commits into from
Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f1b3e68
working on config
memsharded Dec 23, 2020
fee01dc
working
memsharded Dec 27, 2020
3c6c059
working
memsharded Dec 27, 2020
a641daa
marking tests as visual
memsharded Dec 27, 2020
f69fef1
add command line
memsharded Dec 27, 2020
71418ed
Merge branch 'develop' into poc/config
memsharded Dec 28, 2020
5908f92
removed command line support
memsharded Dec 28, 2020
df76450
Merge branch 'develop' into poc/config
memsharded Dec 28, 2020
bbd8644
updates for composition and inclusion working
memsharded Dec 29, 2020
a32ec10
trying delimiters
memsharded Dec 29, 2020
8fc1ac4
removed unrelated refactor
memsharded Dec 29, 2020
9e1dee8
proposing config not in profile
memsharded Dec 29, 2020
4242f1d
Merge branch 'develop' into poc/config
memsharded Dec 29, 2020
6483c49
working
memsharded Dec 30, 2020
614d4d3
working
memsharded Dec 30, 2020
b575e18
working
memsharded Dec 30, 2020
8e8596c
user vs global config
memsharded Dec 30, 2020
6dbc141
working
memsharded Dec 30, 2020
43dcbab
merged develop
memsharded Dec 30, 2020
3774403
fix Py2 error
memsharded Dec 30, 2020
054da0e
more general paackage pattern match
memsharded Dec 31, 2020
888ec72
adding MSBuildToolchain compile_options conf example
memsharded Jan 3, 2021
a70ed07
added some package_id capabilities
memsharded Jan 3, 2021
62a5311
added assignment of conf for virtual and txt
memsharded Jan 3, 2021
e2167f0
more tests
memsharded Jan 10, 2021
0e2664a
merged develop
memsharded Jan 10, 2021
ae63225
merged develop
memsharded Jan 11, 2021
d2c39f8
review
memsharded Jan 11, 2021
8d497a8
changed file name conan.cfg
memsharded Jan 11, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 8 additions & 10 deletions conan/tools/cmake/cmake.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from conan.tools.cmake.base import CMakeToolchainBase
from conan.tools.cmake.utils import get_generator, is_multi_configuration
from conan.tools.microsoft.msbuild import msbuild_verbosity_cmd_line_arg
from conans.client import tools
from conans.client.build import join_arguments
from conans.client.tools.files import chdir
Expand All @@ -20,7 +21,7 @@ def _validate_recipe(conanfile):
" or 'cmake_find_package_multi' generators")


def _cmake_cmd_line_args(conanfile, generator, parallel, msbuild_verbosity):
def _cmake_cmd_line_args(conanfile, generator, parallel):
args = []
compiler_version = conanfile.settings.get_safe("compiler.version")
if generator and parallel:
Expand All @@ -30,9 +31,10 @@ def _cmake_cmd_line_args(conanfile, generator, parallel, msbuild_verbosity):
# Parallel for building projects in the solution
args.append("/m:%i" % cpu_count(output=conanfile.output))

if generator and msbuild_verbosity:
if "Visual Studio" in generator and compiler_version and Version(compiler_version) >= "10":
args.append("/verbosity:%s" % msbuild_verbosity)
if generator and "Visual Studio" in generator:
verbosity = msbuild_verbosity_cmd_line_arg(conanfile)
if verbosity:
args.append(verbosity)

return args

Expand All @@ -44,9 +46,7 @@ class CMake(object):
are passed to the command line, plus the ``--config Release`` for builds in multi-config
"""

def __init__(self, conanfile, generator=None, build_folder=None, parallel=True,
msbuild_verbosity="minimal"):

def __init__(self, conanfile, generator=None, build_folder=None, parallel=True):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in the "toolchain" build_helper version we drop that msbuild_verbosity parameter, right? (just double checking)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally, both in the CMake and MSBuild helpers, that parameter is dropped. I don't see any value (actually a bit of noise) of specifying that in recipes

Copy link
Contributor

@madebr madebr Jan 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why dropping?
I would assume the configuration file provides a new default.
The search order of a value should be (from higher priority to lower priority):

  1. explicitly set as argument
  2. set in [conf] section of profile
  3. set in [conf] section of global conan_conf.txt
  4. default value, calculated by some implementation

Arguments like these are often useful for debugging purposes.
It would be illogical having to modify a global configuration/profile to add debugging info to some recipe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it very annoying, not to say impossible to edit the recipe (when building dependencies) to change something like the verbosity? So what is really wanted is the possibility to define in command line, for example, like -c tools.microsoft:msbuild_verbosity=Diagnostic, but not editing the recipe.

Plus, defining it as an argument in the recipe, doesn't have that strong priority for all users. Many of them will still want to be able to change that from the downstream consumer, even if defined in code (so this would be in fact the lowest priority). So this is what we are priorizing now, the case of the real conf definition, and we plan to add it as argument when there is a stronger evidence of the use case and after discussing and agreeing on the priority.

_validate_recipe(conanfile)

# assert generator is None, "'generator' is handled by the toolchain"
Expand All @@ -56,7 +56,6 @@ def __init__(self, conanfile, generator=None, build_folder=None, parallel=True,
# Store a reference to useful data
self._conanfile = conanfile
self._parallel = parallel
self._msbuild_verbosity = msbuild_verbosity

self._build_folder = build_folder
self._cmake_program = "cmake" # Path to CMake should be handled by environment
Expand Down Expand Up @@ -115,8 +114,7 @@ def _build(self, build_type=None, target=None):
if target is not None:
args = ["--target", target]

cmd_line_args = _cmake_cmd_line_args(self._conanfile, self._generator, self._parallel,
self._msbuild_verbosity)
cmd_line_args = _cmake_cmd_line_args(self._conanfile, self._generator, self._parallel)
if cmd_line_args:
args += ['--'] + cmd_line_args

Expand Down
17 changes: 16 additions & 1 deletion conan/tools/microsoft/msbuild.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from conan.tools.microsoft.visual import vcvars_arch, vcvars_command
from conans.client.tools import intel_compilervars_command
from conans.errors import ConanException


def msbuild_verbosity_cmd_line_arg(conanfile):
conf = conanfile.conf["tools.microsoft.MSBuild"]
verbosity = conf.verbosity
if verbosity:
if verbosity not in ("Quiet", "Minimal", "Normal", "Detailed", "Diagnostic"):
raise ConanException("Uknown MSBuild verbosity: {}".format(verbosity))
return '/verbosity:{}'.format(verbosity)
return


class MSBuild(object):
Expand Down Expand Up @@ -29,9 +40,13 @@ def command(self, sln):
cvars = vcvars_command(self.version, architecture=self.vcvars_arch,
platform_type=None, winsdk_version=None,
vcvars_ver=None)
cmd = ('%s && msbuild "%s" /p:Configuration=%s /p:Platform=%s '
cmd = ('%s && msbuild "%s" /p:Configuration=%s /p:Platform=%s'
% (cvars, sln, self.build_type, self.platform))

verbosity = msbuild_verbosity_cmd_line_arg(self._conanfile)
if verbosity:
cmd += " {}".format(verbosity)

return cmd

def build(self, sln):
Expand Down
18 changes: 18 additions & 0 deletions conans/client/cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from conans.client.profile_loader import read_profile
from conans.client.store.localdb import LocalDB
from conans.errors import ConanException
from conans.model.conf import ConfDefinition
from conans.model.profile import Profile
from conans.model.ref import ConanFileReference
from conans.model.settings import Settings
Expand Down Expand Up @@ -77,6 +78,7 @@ def __init__(self, cache_folder, output):
# Caching
self._no_lock = None
self._config = None
self._new_config = None
self.editable_packages = EditablePackages(self.cache_folder)
# paths
self._store_folder = self.config.storage_path or os.path.join(self.cache_folder, "data")
Expand Down Expand Up @@ -155,6 +157,22 @@ def config(self):
self._config = ConanClientConfigParser(self.conan_conf_path)
return self._config

@property
def new_config_path(self):
return os.path.join(self.cache_folder, "conan_conf.txt")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to be decoupled from the cache folder.
otherwise, we introduce a vicious circle here: config has a path to the cache folder, but the config path is relative to the cache folder. it's super confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not new, the current config (and absolutely everything from the cache) is already loaded this way, because assuming it is in the cache folder, you need the cache folder to locate it. It cannot be decoupled, but maybe I don't understand the suggestion.

Copy link
Contributor

@SSE4 SSE4 Jan 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say it's not always a valid argument "it was always like that" when designing a brand new thing, especially if the approach is already known to be problematic.
first, it's logically inconsistent, as the configuration file doesn't belong to cache idiomatically (we are not caching it, we caching binaries and sources).
IMO it belongs to the config directory (which might be the same or might not be the same as cache).
second, if we want conan to configure to use a different cache folder, we can't, as config file is within cache folder, so cache folder needs to be known before loading the config, which obviously, introduces a vicious circle.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not an argument of "it was always like that". It is maybe a slightly incorrect naming. Lets rename the cache_folder to home_folder which is what it actually is. Conan codebase is referring to the Conan cache and the Conan home folder equivalently, because 97% of the home folder is devoted to the package cache. Basically, the Conan home folder contains the settings.yml, the remotes.json, the conan.db, the profiles plus the recipes and packages cache.

If we agree with that, I am very fine with renaming the Conan codebase from "cache" => "home" when it applies. But the logic implemented in this new_config_path() is correct and in the right place.


@property
def new_config(self):
""" this is the new conan_conf.txt to replace the old conan.conf that contains
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer .conf extension. .txt is generic, conf can be interpreted as .ini, even for code editors it can used for highlight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a temporary name, it might be possible that everything become python files.
conan_conf.conf would be ugly, conan_conf.ini in the meantime?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, temporary name, to be changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also prefer a different extension to indicate it's a config file, not a generic text file. something like .cfg, .conf or .ini will work.

configuration defined with the new syntax as in profiles, this config will be composed
to the profile ones and passed to the conanfiles.conf, which can be passed to collaborators
"""
if self._new_config is None:
self._new_config = ConfDefinition()
if os.path.exists(self.new_config_path):
self._new_config.loads(load(self.new_config_path))
return self._new_config

@property
def localdb(self):
localdb_filename = os.path.join(self.cache_folder, LOCALDB)
Expand Down
7 changes: 7 additions & 0 deletions conans/client/conan_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,13 @@ def get_graph_info(profile_host, profile_build, cwd, install_folder, cache, outp
root_ref = ConanFileReference(name, version, user, channel, validate=False)
graph_info = GraphInfo(profile_host=phost, profile_build=pbuild, root_ref=root_ref)
# Preprocess settings and convert to real settings

# Apply the new_config to the profiles the global one, so recipes get it too
# TODO: This means lockfiles contain whole copy of the config here?
# FIXME: Apply to locked graph-info as well
graph_info.profile_host.conf.rebase_conf_definition(cache.new_config)
if graph_info.profile_build is not None:
graph_info.profile_build.conf.rebase_conf_definition(cache.new_config)
return graph_info


Expand Down
7 changes: 7 additions & 0 deletions conans/client/conf/required_version.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import six

from conans.client.cache.cache import ClientCache
from semver import satisfies
from conans import __version__ as client_version
Expand Down Expand Up @@ -28,3 +30,8 @@ def check_required_conan_version(cache_folder, out):
if required_range:
validate_conan_version(required_range)

required_range_new = cache.new_config["core"].required_conan_version
if required_range_new:
if six.PY2 and not isinstance(required_range_new, str):
required_range_new = required_range_new.encode()
validate_conan_version(required_range_new)
9 changes: 5 additions & 4 deletions conans/client/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,21 @@ def _initialize_conanfile(conanfile, profile):
# Mixing the global settings with the specified for that name if exist
tmp_settings = profile.processed_settings.copy()
package_settings_values = profile.package_settings_values
ref_str = "%s/%s@%s/%s" % (conanfile.name, conanfile.version,
memsharded marked this conversation as resolved.
Show resolved Hide resolved
conanfile._conan_user, conanfile._conan_channel)
if package_settings_values:
pkg_settings = package_settings_values.get(conanfile.name)
if pkg_settings is None:
# FIXME: This seems broken for packages without user/channel
ref = "%s/%s@%s/%s" % (conanfile.name, conanfile.version,
conanfile._conan_user, conanfile._conan_channel)
for pattern, settings in package_settings_values.items():
if fnmatch.fnmatchcase(ref, pattern):
if fnmatch.fnmatchcase(ref_str, pattern):
pkg_settings = settings
break
if pkg_settings:
tmp_settings.update_values(pkg_settings)

conanfile.initialize(tmp_settings, profile.env_values)
# FIXME: Name of the package, full name?
conanfile.conf = profile.conf.get_conanfile_conf(ref_str)

def load_consumer(self, conanfile_path, profile_host, name=None, version=None, user=None,
channel=None, lock_python_requires=None):
Expand Down
8 changes: 7 additions & 1 deletion conans/client/profile_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from collections import OrderedDict, defaultdict

from conans.errors import ConanException, ConanV2Exception
from conans.model.conf import ConfDefinition
from conans.model.env_info import EnvValues, unquote
from conans.model.options import OptionsValues
from conans.model.profile import Profile
Expand Down Expand Up @@ -148,7 +149,7 @@ def _load_profile(text, profile_path, default_folder):

# Current profile before update with parents (but parent variables already applied)
doc = ConfigParser(profile_parser.profile_text,
allowed_fields=["build_requires", "settings", "env", "options"])
allowed_fields=["build_requires", "settings", "env", "options", "conf"])

# Merge the inherited profile with the readed from current profile
_apply_inner_profile(doc, inherited_profile)
Expand Down Expand Up @@ -218,6 +219,11 @@ def get_package_name_value(item):
current_env_values.update(base_profile.env_values)
base_profile.env_values = current_env_values

if doc.conf:
new_prof = ConfDefinition()
new_prof.loads(doc.conf, profile=True)
base_profile.conf.update_conf_definition(new_prof)


def profile_from_args(profiles, settings, options, env, cwd, cache):
""" Return a Profile object, as the result of merging a potentially existing Profile
Expand Down
152 changes: 152 additions & 0 deletions conans/model/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import fnmatch
from collections import OrderedDict

from conans.errors import ConanException


class _ConfModule(object):
memsharded marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self):
self._confs = {} # Component => dict {config-name: value}

def __getattr__(self, item):
return self._confs.get(item)

def update(self, other):
"""
:type other: _ConfModule
"""
self._confs.update(other._confs)

def set_value(self, k, v):
self._confs[k] = v

def __repr__(self):
return "_ConfModule: " + repr(self._confs)

def items(self):
return self._confs.items()


def _is_profile_module(module_name):
# These are the modules that are propagated to profiles and user recipes
_user_modules = "tools.", "user."
return any(module_name.startswith(user_module) for user_module in _user_modules)


class Conf(object):

def __init__(self):
self._conf_modules = {} # module_name => _ConfModule

def __getitem__(self, module_name):
return self._conf_modules.get(module_name, _ConfModule())

def __repr__(self):
return "Conf: " + repr(self._conf_modules)

def items(self):
return self._conf_modules.items()

def filter_user_modules(self):
result = Conf()
for k, v in self._conf_modules.items():
if _is_profile_module(k):
result._conf_modules[k] = v
return result

def update(self, other):
"""
:type other: Conf
"""
for module_name, module_conf in other._conf_modules.items():
existing = self._conf_modules.get(module_name)
if existing:
existing.update(module_conf)
else:
self._conf_modules[module_name] = module_conf

def set_value(self, module_name, k, v):
self._conf_modules.setdefault(module_name, _ConfModule()).set_value(k, v)


class ConfDefinition(object):
def __init__(self):
self._pattern_confs = OrderedDict() # pattern (including None) => Conf

def __bool__(self):
return bool(self._pattern_confs)

def __repr__(self):
return "ConfDefinition: " + repr(self._pattern_confs)

__nonzero__ = __bool__

def __getitem__(self, module_name):
""" if a module name is requested for this, always goes to the None-Global config
"""
return self._pattern_confs.get(None, Conf())[module_name]

def get_conanfile_conf(self, name):
result = Conf()
for pattern, conf in self._pattern_confs.items():
# TODO: standardize this package-pattern matching
if pattern is None or fnmatch.fnmatch(name, pattern):
result.update(conf)
return result

def update_conf_definition(self, other):
"""
This is used for composition of profiles [conf] section
:type other: ConfDefinition
"""
for k, v in other._pattern_confs.items():
existing = self._pattern_confs.get(k)
if existing:
existing.update(v)
else:
self._pattern_confs[k] = v

def rebase_conf_definition(self, other):
"""
for taking the new conan_conf.txt and composing with the profile [conf]
:type other: ConfDefinition
"""
for k, v in other._pattern_confs.items():
new_v = v.filter_user_modules() # Creates a copy, filtered
existing = self._pattern_confs.get(k)
if existing:
new_v.update(existing)
self._pattern_confs[k] = new_v

def dumps(self):
result = []
for pattern, conf in self._pattern_confs.items():
for name, values in conf.items():
for k, v in values.items():
if pattern:
result.append("{}:{}:{}={}".format(pattern, name, k, v))
else:
result.append("{}:{}={}".format(name, k, v))
return "\n".join(result)

def loads(self, text, profile=False):
self._pattern_confs = {}
for line in text.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
left, value = line.split("=", 1)
tokens = left.split(":", 2)
if len(tokens) == 3:
pattern, conf_module, name = tokens
else:
assert len(tokens) == 2
conf_module, name = tokens
pattern = None
if not _is_profile_module(conf_module):
if profile:
raise ConanException("[conf] '{}' not allowed in profiles".format(line))
if pattern is not None:
raise ConanException("Conf '{}' cannot have a package pattern".format(line))
conf = self._pattern_confs.setdefault(pattern, Conf())
conf.set_value(conf_module, name, value)