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}".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"