diff --git a/conan/tools/cmake/cmake.py b/conan/tools/cmake/cmake.py
index 9c97ae92611..9f2a31d14de 100644
--- a/conan/tools/cmake/cmake.py
+++ b/conan/tools/cmake/cmake.py
@@ -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
@@ -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:
@@ -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
@@ -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):
_validate_recipe(conanfile)
# assert generator is None, "'generator' is handled by the toolchain"
@@ -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
@@ -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
diff --git a/conan/tools/gnu/__init__.py b/conan/tools/gnu/__init__.py
index fa783c1b613..32860e46378 100644
--- a/conan/tools/gnu/__init__.py
+++ b/conan/tools/gnu/__init__.py
@@ -1,2 +1 @@
-# noinspection PyUnresolvedReferences
from .make import MakeToolchain
diff --git a/conan/tools/meson/__init__.py b/conan/tools/meson/__init__.py
index 786057b639f..1485ef455ad 100644
--- a/conan/tools/meson/__init__.py
+++ b/conan/tools/meson/__init__.py
@@ -1,3 +1,2 @@
-# noinspection PyUnresolvedReferences
from conan.tools.meson.toolchain import MesonToolchain
from conan.tools.meson.meson import Meson
diff --git a/conan/tools/microsoft/__init__.py b/conan/tools/microsoft/__init__.py
index 2ad4777a326..82a697a3fd6 100644
--- a/conan/tools/microsoft/__init__.py
+++ b/conan/tools/microsoft/__init__.py
@@ -1,4 +1,3 @@
-# noinspection PyUnresolvedReferences
from .toolchain import MSBuildToolchain
from .msbuild import MSBuild
from .msbuilddeps import MSBuildDeps
diff --git a/conan/tools/microsoft/msbuild.py b/conan/tools/microsoft/msbuild.py
index e4aca1e47a5..998f1222798 100644
--- a/conan/tools/microsoft/msbuild.py
+++ b/conan/tools/microsoft/msbuild.py
@@ -1,13 +1,22 @@
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):
+ verbosity = conanfile.conf["tools.microsoft"].msbuild_verbosity
+ if verbosity:
+ if verbosity not in ("Quiet", "Minimal", "Normal", "Detailed", "Diagnostic"):
+ raise ConanException("Unknown msbuild verbosity: {}".format(verbosity))
+ return '/verbosity:{}'.format(verbosity)
class MSBuild(object):
def __init__(self, conanfile):
self._conanfile = conanfile
self.compiler = conanfile.settings.get_safe("compiler")
- self.version = conanfile.settings.get_safe("compiler.base.version") or \
- conanfile.settings.get_safe("compiler.version")
+ self.version = (conanfile.settings.get_safe("compiler.base.version") or
+ conanfile.settings.get_safe("compiler.version"))
self.vcvars_arch = vcvars_arch(conanfile)
self.build_type = conanfile.settings.get_safe("build_type")
msvc_arch = {'x86': 'x86',
@@ -29,9 +38,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):
diff --git a/conan/tools/microsoft/toolchain.py b/conan/tools/microsoft/toolchain.py
index 1a58543a9a1..337a6e38e23 100644
--- a/conan/tools/microsoft/toolchain.py
+++ b/conan/tools/microsoft/toolchain.py
@@ -15,6 +15,7 @@ class MSBuildToolchain(object):
def __init__(self, conanfile):
self._conanfile = conanfile
self.preprocessor_definitions = {}
+ self.compile_options = {}
self.configuration = conanfile.settings.build_type
def _name_condition(self, settings):
@@ -70,7 +71,7 @@ def format_macro(k, value):
{};%(PreprocessorDefinitions)
{}
- {}
+ {}{}
@@ -83,7 +84,14 @@ def format_macro(k, value):
# It is useless to set PlatformToolset in the config file, because the conditional checks it
cppstd = "stdcpp%s" % cppstd if cppstd else ""
toolset = toolset or ""
- config_props = content.format(preprocessor_definitions, runtime_library, cppstd, toolset)
+ compile_options = self._conanfile.conf["tools.microsoft.msbuildtoolchain"].compile_options
+ if compile_options is not None:
+ compile_options = eval(compile_options)
+ self.compile_options.update(compile_options)
+ compile_options = "".join("\n <{k}>{v}{k}>".format(k=k, v=v)
+ for k, v in self.compile_options.items())
+ config_props = content.format(preprocessor_definitions, runtime_library, cppstd,
+ compile_options, toolset)
config_filepath = os.path.abspath(config_filename)
self._conanfile.output.info("MSBuildToolchain created %s" % config_filename)
save(config_filepath, config_props)
diff --git a/conans/client/cache/cache.py b/conans/client/cache/cache.py
index 4a9fb5f8cdf..d6bc12f955a 100644
--- a/conans/client/cache/cache.py
+++ b/conans/client/cache/cache.py
@@ -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
@@ -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")
@@ -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.cfg")
+
+ @property
+ def new_config(self):
+ """ this is the new conan.cfgto replace the old conan.conf that contains
+ 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)
diff --git a/conans/client/conan_api.py b/conans/client/conan_api.py
index 97b7543fd6f..341181d6da0 100644
--- a/conans/client/conan_api.py
+++ b/conans/client/conan_api.py
@@ -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
diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py
index 1c57fc33303..24d46ddb3e4 100644
--- a/conans/client/conf/required_version.py
+++ b/conans/client/conf/required_version.py
@@ -1,3 +1,5 @@
+import six
+
from conans.client.cache.cache import ClientCache
from semver import satisfies
from conans import __version__ as client_version
@@ -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)
diff --git a/conans/client/loader.py b/conans/client/loader.py
index 3bab76cbaaa..64cfdebf85e 100644
--- a/conans/client/loader.py
+++ b/conans/client/loader.py
@@ -175,25 +175,25 @@ 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
+ if conanfile._conan_user is not None:
+ ref_str = "%s/%s@%s/%s" % (conanfile.name, conanfile.version,
+ conanfile._conan_user, conanfile._conan_channel)
+ else:
+ ref_str = "%s/%s" % (conanfile.name, conanfile.version)
if package_settings_values:
# First, try to get a match directly by name (without needing *)
# TODO: Conan 2.0: We probably want to remove this, and leave a pure fnmatch
pkg_settings = package_settings_values.get(conanfile.name)
if pkg_settings is None: # If there is not exact match by package name, do fnmatch
- if conanfile._conan_user is not None:
- ref = "%s/%s@%s/%s" % (conanfile.name, conanfile.version,
- conanfile._conan_user, conanfile._conan_channel)
- else:
- ref = "%s/%s" % (conanfile.name, conanfile.version)
-
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)
+ 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):
@@ -262,6 +262,7 @@ def load_conanfile_txt(self, conan_txt_path, profile_host, ref=None):
def _parse_conan_txt(self, contents, path, display_name, profile):
conanfile = ConanFile(self._output, self._runner, display_name)
conanfile.initialize(Settings(), profile.env_values)
+ conanfile.conf = profile.conf.get_conanfile_conf(None)
# It is necessary to copy the settings, because the above is only a constraint of
# conanfile settings, and a txt doesn't define settings. Necessary for generators,
# as cmake_multi, that check build_type.
@@ -301,6 +302,7 @@ def load_virtual(self, references, profile_host, scope_options=True,
conanfile = ConanFile(self._output, self._runner, display_name="virtual")
conanfile.initialize(profile_host.processed_settings.copy(),
profile_host.env_values)
+ conanfile.conf = profile_host.conf.get_conanfile_conf(None)
conanfile.settings = profile_host.processed_settings.copy_values()
for reference in references:
diff --git a/conans/client/profile_loader.py b/conans/client/profile_loader.py
index eca133f23f6..ffbacf6819f 100644
--- a/conans/client/profile_loader.py
+++ b/conans/client/profile_loader.py
@@ -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
@@ -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)
@@ -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
diff --git a/conans/model/conf.py b/conans/model/conf.py
new file mode 100644
index 00000000000..4aa17bf0bd8
--- /dev/null
+++ b/conans/model/conf.py
@@ -0,0 +1,169 @@
+import fnmatch
+
+from conans.errors import ConanException
+
+
+class _ConfModule(object):
+ """
+ a dictionary of key: values for each config property of a module
+ like "tools.cmake.CMake"
+ """
+ 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):
+ if k != k.lower():
+ raise ConanException("Conf key '{}' must be lowercase".format(k))
+ 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):
+ if module_name != module_name.lower():
+ raise ConanException("Conf module '{}' must be lowercase".format(module_name))
+ self._conf_modules.setdefault(module_name, _ConfModule()).set_value(k, v)
+
+ @property
+ def sha(self):
+ result = []
+ for name, values in sorted(self.items()):
+ for k, v in sorted(values.items()):
+ result.append("{}:{}={}".format(name, k, v))
+ return "\n".join(result)
+
+
+class ConfDefinition(object):
+ def __init__(self):
+ self._pattern_confs = {} # 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, ref_str):
+ result = Conf()
+ for pattern, conf in self._pattern_confs.items():
+ if pattern is None or (ref_str is not None and fnmatch.fnmatch(ref_str, 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.cfg 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 = []
+ # It is necessary to convert the None for sorting
+ for pattern, conf in sorted(self._pattern_confs.items(),
+ key=lambda x: ("", x[1]) if x[0] is None else x):
+ for name, values in sorted(conf.items()):
+ for k, v in sorted(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)
+ value = value.strip()
+ tokens = left.strip().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)
diff --git a/conans/model/info.py b/conans/model/info.py
index e5d492d4cbf..3b6cbfb3e29 100644
--- a/conans/model/info.py
+++ b/conans/model/info.py
@@ -557,6 +557,8 @@ def package_id(self):
result.append(requires_sha)
if self.python_requires:
result.append(self.python_requires.sha)
+ if hasattr(self, "conf"):
+ result.append(self.conf.sha)
package_id = sha1('\n'.join(result).encode())
return package_id
diff --git a/conans/model/profile.py b/conans/model/profile.py
index 23aac387319..14bfdb4b80f 100644
--- a/conans/model/profile.py
+++ b/conans/model/profile.py
@@ -3,6 +3,7 @@
from conans.client import settings_preprocessor
from conans.errors import ConanException
+from conans.model.conf import ConfDefinition
from conans.model.env_info import EnvValues
from conans.model.options import OptionsValues
from conans.model.values import Values
@@ -19,6 +20,7 @@ def __init__(self):
self.env_values = EnvValues()
self.options = OptionsValues()
self.build_requires = OrderedDict() # ref pattern: list of ref
+ self.conf = ConfDefinition()
# Cached processed values
self.processed_settings = None # Settings with values, and smart completion
@@ -81,6 +83,10 @@ def dumps(self):
result.append("[env]")
result.append(self.env_values.dumps())
+ if self.conf:
+ result.append("[conf]")
+ result.append(self.conf.dumps())
+
return "\n".join(result).replace("\n\n", "\n")
def update(self, other):
@@ -92,6 +98,7 @@ def update(self, other):
self.options.update(other.options)
for pattern, req_list in other.build_requires.items():
self.build_requires.setdefault(pattern, []).extend(req_list)
+ self.conf.update_conf_definition(other.conf)
def update_settings(self, new_settings):
"""Mix the specified settings with the current profile.
diff --git a/conans/test/integration/configuration/conf/__init__.py b/conans/test/integration/configuration/conf/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/integration/configuration/conf/test_conf.py b/conans/test/integration/configuration/conf/test_conf.py
new file mode 100644
index 00000000000..300c0b0fcb1
--- /dev/null
+++ b/conans/test/integration/configuration/conf/test_conf.py
@@ -0,0 +1,136 @@
+import textwrap
+
+import pytest
+from mock import patch
+
+from conans.errors import ConanException
+from conans.util.files import save
+from conans.test.utils.tools import TestClient
+
+
+@pytest.fixture
+def client():
+ client = TestClient()
+ conanfile = textwrap.dedent("""
+ from conans import ConanFile
+
+ class Pkg(ConanFile):
+ def generate(self):
+ for k, conf in self.conf.items():
+ for name, value in conf.items():
+ self.output.info("{}${}${}".format(k, name, value))
+ """)
+ client.save({"conanfile.py": conanfile})
+ return client
+
+
+def test_basic_composition(client):
+ profile1 = textwrap.dedent("""\
+ [conf]
+ tools.microsoft.msbuild:verbosity=Quiet
+ tools.microsoft.msbuild:performance=Slow
+ tools.cmake.cmake:verbosity=Extra
+ """)
+ profile2 = textwrap.dedent("""\
+ [conf]
+ tools.microsoft.msbuild:verbosity=Minimal
+ tools.microsoft.msbuild:robustness=High
+ tools.meson.meson:verbosity=Super
+ """)
+ client.save({"profile1": profile1,
+ "profile2": profile2})
+ client.run("install . -pr=profile1")
+ assert "tools.microsoft.msbuild$verbosity$Quiet" in client.out
+ assert "tools.microsoft.msbuild$performance$Slow" in client.out
+ assert "tools.cmake.cmake$verbosity$Extra" in client.out
+
+ client.run("install . -pr=profile1 -pr=profile2")
+ assert "tools.microsoft.msbuild$verbosity$Minimal" in client.out
+ assert "tools.microsoft.msbuild$performance$Slow" in client.out
+ assert "tools.microsoft.msbuild$robustness$High" in client.out
+ assert "tools.cmake.cmake$verbosity$Extra" in client.out
+ assert "tools.meson.meson$verbosity$Super" in client.out
+
+ client.run("install . -pr=profile2 -pr=profile1")
+ assert "tools.microsoft.msbuild$verbosity$Quiet" in client.out
+ assert "tools.microsoft.msbuild$performance$Slow" in client.out
+ assert "tools.microsoft.msbuild$robustness$High" in client.out
+ assert "tools.cmake.cmake$verbosity$Extra" in client.out
+ assert "tools.meson.meson$verbosity$Super" in client.out
+
+
+def test_basic_inclusion(client):
+ profile1 = textwrap.dedent("""\
+ [conf]
+ tools.microsoft.msbuild:verbosity=Quiet
+ tools.microsoft.msbuild:performance=Slow
+ tools.cmake.cmake:verbosity=Extra
+ """)
+ profile2 = textwrap.dedent("""\
+ include(profile1)
+ [conf]
+ tools.microsoft.msbuild:verbosity=Minimal
+ tools.microsoft.msbuild:robustness=High
+ tools.meson.meson:verbosity=Super
+ """)
+ client.save({"profile1": profile1,
+ "profile2": profile2})
+
+ client.run("install . -pr=profile2")
+ assert "tools.microsoft.msbuild$verbosity$Minimal" in client.out
+ assert "tools.microsoft.msbuild$performance$Slow" in client.out
+ assert "tools.microsoft.msbuild$robustness$High" in client.out
+ assert "tools.cmake.cmake$verbosity$Extra" in client.out
+ assert "tools.meson.meson$verbosity$Super" in client.out
+
+
+def test_composition_conan_conf(client):
+ conf = textwrap.dedent("""\
+ tools.microsoft.msbuild:verbosity=Quiet
+ tools.microsoft.msbuild:performance=Slow
+ tools.cmake.cmake:verbosity=Extra
+ """)
+ save(client.cache.new_config_path, conf)
+ profile = textwrap.dedent("""\
+ [conf]
+ tools.microsoft.msbuild:verbosity=Minimal
+ tools.microsoft.msbuild:robustness=High
+ tools.meson.meson:verbosity=Super
+ """)
+ client.save({"profile": profile})
+ client.run("install . -pr=profile")
+ assert "tools.microsoft.msbuild$verbosity$Minimal" in client.out
+ assert "tools.microsoft.msbuild$performance$Slow" in client.out
+ assert "tools.microsoft.msbuild$robustness$High" in client.out
+ assert "tools.cmake.cmake$verbosity$Extra" in client.out
+ assert "tools.meson.meson$verbosity$Super" in client.out
+
+
+def test_new_config_file(client):
+ conf = textwrap.dedent("""\
+ tools.microsoft.msbuild:verbosity=Minimal
+ user.mycompany.myhelper:myconfig=myvalue
+ *:tools.cmake.cmake:generator=X
+ cache:no_locks=True
+ cache:read_only=True
+ """)
+ save(client.cache.new_config_path, conf)
+ client.run("install .")
+ assert "tools.microsoft.msbuild$verbosity$Minimal" in client.out
+ assert "user.mycompany.myhelper$myconfig$myvalue" in client.out
+ assert "tools.cmake.cmake$generator$X" in client.out
+ assert "no_locks" not in client.out
+ assert "read_only" not in client.out
+
+
+@patch("conans.client.conf.required_version.client_version", "1.26.0")
+def test_new_config_file_required_version():
+ client = TestClient()
+ conf = textwrap.dedent("""\
+ core:required_conan_version=>=2.0
+ """)
+ save(client.cache.new_config_path, conf)
+ with pytest.raises(ConanException) as excinfo:
+ client.run("install .")
+ assert ("Current Conan version (1.26.0) does not satisfy the defined one (>=2.0)"
+ in str(excinfo.value))
diff --git a/conans/test/integration/configuration/conf/test_conf_package_id.py b/conans/test/integration/configuration/conf/test_conf_package_id.py
new file mode 100644
index 00000000000..b8b0b27a647
--- /dev/null
+++ b/conans/test/integration/configuration/conf/test_conf_package_id.py
@@ -0,0 +1,34 @@
+import textwrap
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+
+
+@pytest.fixture
+def client():
+ client = TestClient()
+ conanfile = textwrap.dedent("""
+ from conans import ConanFile
+
+ class Pkg(ConanFile):
+ def package_id(self):
+ self.info.conf = self.conf
+ """)
+ client.save({"conanfile.py": conanfile})
+ return client
+
+
+def test_package_id(client):
+ profile1 = textwrap.dedent("""\
+ [conf]
+ tools.microsoft:msbuild_verbosity=Quiet""")
+ profile2 = textwrap.dedent("""\
+ [conf]
+ tools.microsoft:msbuild_verbosity=Minimal""")
+ client.save({"profile1": profile1,
+ "profile2": profile2})
+ client.run("create . pkg/0.1@ -pr=profile1")
+ assert "pkg/0.1:b40df771e875672867408f9edf54bec0c2c361a7 - Build" in client.out
+ client.run("create . pkg/0.1@ -pr=profile2")
+ assert "pkg/0.1:017c055fc7833bf6a7836211a26727533237071d - Build" in client.out
diff --git a/conans/test/integration/configuration/conf/test_conf_profile.py b/conans/test/integration/configuration/conf/test_conf_profile.py
new file mode 100644
index 00000000000..1d286cd8e84
--- /dev/null
+++ b/conans/test/integration/configuration/conf/test_conf_profile.py
@@ -0,0 +1,163 @@
+import textwrap
+
+import pytest
+
+from conans.test.utils.tools import TestClient
+
+
+@pytest.fixture
+def client():
+ client = TestClient()
+ conanfile = textwrap.dedent("""
+ from conans import ConanFile
+ from conan.tools.cmake import CMake
+
+ class Pkg(ConanFile):
+ settings = "os", "arch", "compiler", "build_type"
+
+ def run(self, cmd): # INTERCEPTOR of running
+ self.output.info("RECIPE-RUN: {}".format(cmd))
+
+ def build(self):
+ cmake = CMake(self)
+ cmake.build()
+ """)
+ client.save({"conanfile.py": conanfile})
+ return client
+
+
+def test_cmake_no_config(client):
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ """)
+ client.save({"myprofile": profile})
+ client.run("create . pkg/0.1@ -pr=myprofile")
+ assert "/verbosity" not in client.out
+
+
+def test_cmake_config(client):
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ [conf]
+ tools.microsoft:msbuild_verbosity=Minimal
+ """)
+ client.save({"myprofile": profile})
+ client.run("create . pkg/0.1@ -pr=myprofile")
+ assert "/verbosity:Minimal" in client.out
+
+
+def test_cmake_config_error(client):
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ [conf]
+ tools.microsoft:msbuild_verbosity=non-existing
+ """)
+ client.save({"myprofile": profile})
+ client.run("create . pkg/0.1@ -pr=myprofile", assert_error=True)
+ assert "Unknown msbuild verbosity: non-existing" in client.out
+
+
+def test_cmake_config_package(client):
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ [conf]
+ dep*:tools.microsoft:msbuild_verbosity=Minimal
+ """)
+ client.save({"myprofile": profile})
+ client.run("create . pkg/0.1@ -pr=myprofile")
+ assert "/verbosity" not in client.out
+ client.run("create . dep/0.1@ -pr=myprofile")
+ assert "/verbosity:Minimal" in client.out
+
+
+def test_config_profile_forbidden(client):
+ profile = textwrap.dedent("""\
+ [conf]
+ cache:verbosity=Minimal
+ """)
+ client.save({"myprofile": profile})
+ client.run("install . pkg/0.1@ -pr=myprofile", assert_error=True)
+ assert ("ERROR: Error reading 'myprofile' profile: [conf] "
+ "'cache:verbosity=Minimal' not allowed in profiles" in client.out)
+
+
+@pytest.mark.tool_visual_studio
+def test_msbuild_config():
+ client = TestClient()
+ conanfile = textwrap.dedent("""
+ from conans import ConanFile
+ from conan.tools.microsoft import MSBuild
+
+ class Pkg(ConanFile):
+ settings = "os", "arch", "compiler", "build_type"
+ def build(self):
+ ms = MSBuild(self)
+ self.output.info(ms.command("Project.sln"))
+ """)
+ client.save({"conanfile.py": conanfile})
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ [conf]
+ tools.microsoft:msbuild_verbosity=Minimal
+ """)
+ client.save({"myprofile": profile})
+ client.run("create . pkg/0.1@ -pr=myprofile")
+ assert "/verbosity:Minimal" in client.out
+
+
+def test_msbuild_compile_options():
+ client = TestClient()
+ conanfile = textwrap.dedent("""
+ from conans import ConanFile
+
+ class Pkg(ConanFile):
+ settings = "os", "arch", "compiler", "build_type"
+ generators = "MSBuildToolchain"
+ """)
+ client.save({"conanfile.py": conanfile})
+
+ profile = textwrap.dedent("""\
+ [settings]
+ os=Windows
+ arch=x86_64
+ compiler=Visual Studio
+ compiler.version=15
+ compiler.runtime=MD
+ build_type=Release
+ [conf]
+ tools.microsoft.msbuildtoolchain:compile_options={"ExceptionHandling": "Async"}
+ """)
+ client.save({"myprofile": profile})
+ client.run("install . -pr=myprofile")
+ msbuild_tool = client.load("conantoolchain_release_x64.props")
+ assert "Async" in msbuild_tool
diff --git a/conans/test/unittests/model/test_conf.py b/conans/test/unittests/model/test_conf.py
new file mode 100644
index 00000000000..285362a71a7
--- /dev/null
+++ b/conans/test/unittests/model/test_conf.py
@@ -0,0 +1,86 @@
+import textwrap
+
+import pytest
+
+from conans.errors import ConanException
+from conans.model.conf import ConfDefinition
+
+
+@pytest.fixture()
+def conf_definition():
+ text = textwrap.dedent("""\
+ tools.microsoft.msbuild:verbosity=minimal
+ user.company.toolchain:flags=someflags""")
+ c = ConfDefinition()
+ c.loads(text)
+ return c, text
+
+
+def test_conf_definition(conf_definition):
+ c, text = conf_definition
+ # Round trip
+ assert c.dumps() == text
+ # access
+ assert c["tools.microsoft.msbuild"].verbosity == "minimal"
+ assert c["user.company.toolchain"].flags == "someflags"
+ assert c["tools.microsoft.msbuild"].nonexist is None
+ assert c["nonexist"].nonexist is None
+ # bool
+ assert bool(c)
+ assert not bool(ConfDefinition())
+
+
+def test_conf_update(conf_definition):
+ c, _ = conf_definition
+ text = textwrap.dedent("""\
+ user.company.toolchain:flags=newvalue
+ another.something:key=value""")
+ c2 = ConfDefinition()
+ c2.loads(text)
+ c.update_conf_definition(c2)
+ result = textwrap.dedent("""\
+ another.something:key=value
+ tools.microsoft.msbuild:verbosity=minimal
+ user.company.toolchain:flags=newvalue""")
+ assert c.dumps() == result
+
+
+def test_conf_rebase(conf_definition):
+ c, _ = conf_definition
+ text = textwrap.dedent("""\
+ user.company.toolchain:flags=newvalue
+ another.something:key=value""")
+ c2 = ConfDefinition()
+ c2.loads(text)
+ c.rebase_conf_definition(c2)
+ # The c profile will have precedence, and "
+ result = textwrap.dedent("""\
+ tools.microsoft.msbuild:verbosity=minimal
+ user.company.toolchain:flags=someflags""")
+ assert c.dumps() == result
+
+
+def test_conf_error_per_package():
+ text = "*:core:verbosity=minimal"
+ c = ConfDefinition()
+ with pytest.raises(ConanException,
+ match=r"Conf '\*:core:verbosity=minimal' cannot have a package pattern"):
+ c.loads(text)
+
+
+def test_conf_error_uppercase():
+ text = "tools.something:Verbosity=minimal"
+ c = ConfDefinition()
+ with pytest.raises(ConanException, match=r"Conf key 'Verbosity' must be lowercase"):
+ c.loads(text)
+ text = "tools.Something:verbosity=minimal"
+ c = ConfDefinition()
+ with pytest.raises(ConanException, match=r"Conf module 'tools.Something' must be lowercase"):
+ c.loads(text)
+
+
+def test_parse_spaces():
+ text = "core:verbosity = minimal"
+ c = ConfDefinition()
+ c.loads(text)
+ assert c["core"].verbosity == "minimal"