Repository: conan-io/conan
Pull request #16976 (2024-09-11 11:10:42)

Make conf.get with a check_type=bool raise exception if invalid value
Changelog: Bugfix: raise error if invalid value passed to conf.get(check_type=bool)
Docs: Omit

- [ ] Refer to the issue that supports this Pull Request.
- [x] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop2/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one.



Patch:
diff --git a/conan/tools/build/flags.py b/conan/tools/build/flags.py
index aecaa0931..9805622f4 100644
--- a/conan/tools/build/flags.py
+++ b/conan/tools/build/flags.py
@@ -264,7 +264,10 @@ def _cppstd_apple_clang(clang_version, cppstd):
         v20 = "c++2a"
         vgnu20 = "gnu++2a"
 
-    if clang_version >= "13.0":
+    if clang_version >= "16.0":
+        v23 = "c++23"
+        vgnu23 = "gnu++23"
+    elif clang_version >= "13.0":
         v23 = "c++2b"
         vgnu23 = "gnu++2b"
 
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 43739a955..b7e616150 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -5,7 +5,7 @@ import textwrap
 
 from conan.api.output import ConanOutput, Color
 from conan.tools.cmake.layout import get_build_folder_custom_vars
-from conan.tools.cmake.toolchain.blocks import GenericSystemBlock, CompilersBlock
+from conan.tools.cmake.toolchain.blocks import GenericSystemBlock
 from conan.tools.cmake.utils import is_multi_configuration
 from conan.tools.build import build_jobs
 from conan.tools.microsoft import is_msvc
@@ -158,13 +158,6 @@ class _CMakePresets:
                     "strategy": "external"
                 }
 
-        # Set the compiler like in the toolchain. Some IDEs like VS or VSCode require the compiler
-        # being set to cl.exe in order to activate the environment using vcvarsall.bat according to
-        # the toolset and architecture settings.
-        compilers = CompilersBlock.get_compilers(conanfile)
-        for lang, compiler in compilers.items():
-            ret["cacheVariables"][f"CMAKE_{lang}_COMPILER"] = compiler.replace("\\", "/")
-
         ret["toolchainFile"] = toolchain_file
         if conanfile.build_folder:
             # If we are installing a ref: "conan install <ref>", we don't have build_folder, because
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 5549fdf1d..cc24c45c4 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -865,13 +865,9 @@ class CompilersBlock(Block):
     """)
 
     def context(self):
-        return {"compilers": self.get_compilers(self._conanfile)}
-
-    @staticmethod
-    def get_compilers(conanfile):
         # Reading configuration from "tools.build:compiler_executables" -> {"C": "/usr/bin/gcc"}
-        compilers_by_conf = conanfile.conf.get("tools.build:compiler_executables", default={},
-                                               check_type=dict)
+        compilers_by_conf = self._conanfile.conf.get("tools.build:compiler_executables", default={},
+                                                     check_type=dict)
         # Map the possible languages
         compilers = {}
         # Allowed <LANG> variables (and <LANG>_LAUNCHER)
@@ -882,7 +878,7 @@ class CompilersBlock(Block):
             # To set CMAKE_<LANG>_COMPILER
             if comp in compilers_by_conf:
                 compilers[lang] = compilers_by_conf[comp]
-        return compilers
+        return {"compilers": compilers}
 
 
 class GenericSystemBlock(Block):
diff --git a/conans/client/conf/__init__.py b/conans/client/conf/__init__.py
index d7c643407..28f89d142 100644
--- a/conans/client/conf/__init__.py
+++ b/conans/client/conf/__init__.py
@@ -124,7 +124,7 @@ compiler:
         cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
     apple-clang:
         version: ["5.0", "5.1", "6.0", "6.1", "7.0", "7.3", "8.0", "8.1", "9.0", "9.1",
-                  "10.0", "11.0", "12.0", "13", "13.0", "13.1", "14", "14.0", "15", "15.0"]
+                  "10.0", "11.0", "12.0", "13", "13.0", "13.1", "14", "14.0", "15", "15.0", "16", "16.0"]
         libcxx: [libstdc++, libc++]
         cppstd: [null, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20, 23, gnu23]
         cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
diff --git a/conans/model/conf.py b/conans/model/conf.py
index c04e48d95..f8e1924e5 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -272,6 +272,7 @@ class _ConfValue(object):
 class Conf:
     # Putting some default expressions to check that any value could be false
     boolean_false_expressions = ("0", '"0"', "false", '"false"', "off")
+    boolean_true_expressions = ("1", '"1"', "true", '"true"', "on")
 
     def __init__(self):
         # It being ordered allows for Windows case-insensitive composition
@@ -321,8 +322,12 @@ class Conf:
                 raise ConanException(f"Unknown value '{v}' for '{conf_name}'")
             # Some smart conversions
             if check_type is bool and not isinstance(v, bool):
-                # Perhaps, user has introduced a "false", "0" or even "off"
-                return str(v).lower() not in Conf.boolean_false_expressions
+                if str(v).lower() in Conf.boolean_false_expressions:
+                    return False
+                if str(v).lower() in Conf.boolean_true_expressions:
+                    return True
+                raise ConanException(f"[conf] {conf_name} must be a boolean-like object "
+                                     f"(true/false, 1/0, on/off) and value '{v}' does not match it.")
             elif check_type is str and not isinstance(v, str):
                 return str(v)
             elif v is None:  # value was unset


Test patch:
diff --git a/test/functional/toolchains/cmake/test_cmake_toolchain.py b/test/functional/toolchains/cmake/test_cmake_toolchain.py
index 2bff99412..b3173091b 100644
--- a/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -14,6 +14,7 @@ from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.test_files import temp_folder
 from conan.test.utils.tools import TestClient, TurboTestClient
 from conans.util.files import save, load, rmdir
+from test.conftest import tools_locations
 
 
 @pytest.mark.skipif(platform.system() != "Windows", reason="Only for windows")
@@ -2041,3 +2042,73 @@ def test_cxx_version_not_overriden_if_hardcoded():
     tc.run("create . -s=compiler.cppstd=17")
     assert "Conan toolchain: C++ Standard 17 with extensions OFF" in tc.out
     assert "Warning: Standard CMAKE_CXX_STANDARD value defined in conan_toolchain.cmake to 17 has been modified to 17" not in tc.out
+
+
+@pytest.mark.tool("cmake", "3.23")  # Android complains if <3.19
+@pytest.mark.tool("android_ndk")
+@pytest.mark.skipif(platform.system() != "Darwin", reason="NDK only installed on MAC")
+def test_cmake_toolchain_crossbuild_set_cmake_compiler():
+    # To reproduce https://github.com/conan-io/conan/issues/16960
+    # the issue happens when you set CMAKE_CXX_COMPILER and CMAKE_C_COMPILER as cache variables
+    # and then cross-build
+    c = TestClient()
+
+    ndk_path = tools_locations["android_ndk"]["system"]["path"][platform.system()]
+    bin_path = ndk_path + (
+        "/toolchains/llvm/prebuilt/darwin-x86_64/bin" if platform.machine() == "x86_64" else
+        "/toolchains/llvm/prebuilt/darwin-arm64/bin"
+    )
+
+    android = textwrap.dedent(f"""
+       [settings]
+       os=Android
+       os.api_level=23
+       arch=x86_64
+       compiler=clang
+       compiler.version=12
+       compiler.libcxx=c++_shared
+       build_type=Release
+       [conf]
+       tools.android:ndk_path={ndk_path}
+       tools.build:compiler_executables = {{"c": "{bin_path}/x86_64-linux-android23-clang", "cpp": "{bin_path}/x86_64-linux-android23-clang++"}}
+       """)
+
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
+        from conan.tools.build import check_min_cppstd
+        class SDKConan(ConanFile):
+            name = "sdk"
+            version = "1.0"
+            generators = "CMakeDeps", "VirtualBuildEnv"
+            settings = "os", "compiler", "arch", "build_type"
+            exports_sources = "CMakeLists.txt"
+            def layout(self):
+                cmake_layout(self)
+            def generate(self):
+                tc = CMakeToolchain(self)
+                tc.cache_variables["SDK_VERSION"] = str(self.version)
+                tc.generate()
+            def build(self):
+                cmake = CMake(self)
+                cmake.configure()
+                cmake.build()
+    """)
+
+    cmake = textwrap.dedent(f"""
+        cmake_minimum_required(VERSION 3.23)
+        project(sdk VERSION ${{SDK_VERSION}}.0)
+        message("sdk: ${{SDK_VERSION}}.0")
+    """)
+
+    c.save({"android": android,
+            "conanfile.py": conanfile,
+            "CMakeLists.txt": cmake,})
+    # first run works ok
+    c.run('build . --profile:host=android')
+    assert 'sdk: 1.0.0' in c.out
+    # in second run CMake says that you have changed variables that require your cache to be deleted.
+    # and deletes the cache and fails
+    c.run('build . --profile:host=android')
+    assert 'VERSION ".0" format invalid.' not in c.out
+    assert 'sdk: 1.0.0' in c.out
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index a6f1bb75f..a1ed3da24 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1046,30 +1046,6 @@ def test_set_cmake_lang_compilers_and_launchers():
     assert 'set(CMAKE_RC_COMPILER "C:/local/rc.exe")' in toolchain
 
 
-def test_cmake_presets_compiler():
-    profile = textwrap.dedent(r"""
-    [settings]
-    os=Windows
-    arch=x86_64
-    compiler=msvc
-    compiler.version=193
-    compiler.runtime=dynamic
-    [conf]
-    tools.build:compiler_executables={"c": "cl", "cpp": "cl.exe", "rc": "C:\\local\\rc.exe"}
-    """)
-    client = TestClient()
-    conanfile = GenConanfile().with_settings("os", "arch", "compiler")\
-        .with_generator("CMakeToolchain")
-    client.save({"conanfile.py": conanfile,
-                 "profile": profile})
-    client.run("install . -pr:b profile -pr:h profile")
-    presets = json.loads(client.load("CMakePresets.json"))
-    cache_variables = presets["configurePresets"][0]["cacheVariables"]
-    assert cache_variables["CMAKE_C_COMPILER"] == "cl"
-    assert cache_variables["CMAKE_CXX_COMPILER"] == "cl.exe"
-    assert cache_variables["CMAKE_RC_COMPILER"] == "C:/local/rc.exe"
-
-
 def test_cmake_layout_toolchain_folder():
     """ in single-config generators, the toolchain is a different file per configuration
     https://github.com/conan-io/conan/issues/12827
diff --git a/test/unittests/model/test_conf.py b/test/unittests/model/test_conf.py
index 042f74463..e73870a45 100644
--- a/test/unittests/model/test_conf.py
+++ b/test/unittests/model/test_conf.py
@@ -221,10 +221,15 @@ def test_conf_get_check_type_and_default():
         zlib:user.company.check:static_str=off
         user.company.list:newnames+=myname
         core.download:parallel=True
+        user:bad_value_0=Fasle
+        user:bad_value_1=ture
+        user:bad_value_2=10
+        user:bad_value_3='00'
     """)
     c = ConfDefinition()
     c.loads(text)
     assert c.get("user.company.cpu:jobs", check_type=int) == 5
+    assert c.get("user.company.cpu:jobs", check_type=None) == 5
     assert c.get("user.company.cpu:jobs", check_type=str) == "5"  # smart conversion
     with pytest.raises(ConanException) as exc_info:
         c.get("user.company.cpu:jobs", check_type=list)
@@ -243,6 +248,18 @@ def test_conf_get_check_type_and_default():
         c.get("core.download:parallel", check_type=int)
     assert ("[conf] core.download:parallel must be a int-like object. "
             "The value 'True' introduced is a bool object") in str(exc_info.value)
+    with pytest.raises(ConanException) as exc_info:
+        c.get("user:bad_value_0", check_type=bool)
+    assert ("[conf] user:bad_value_0 must be a boolean-like object (true/false, 1/0, on/off) and value 'Fasle' does not match it.") in str(exc_info.value)
+    with pytest.raises(ConanException) as exc_info:
+        c.get("user:bad_value_1", check_type=bool)
+    assert ("[conf] user:bad_value_1 must be a boolean-like object (true/false, 1/0, on/off) and value 'ture' does not match it.") in str(exc_info.value)
+    with pytest.raises(ConanException) as exc_info:
+        c.get("user:bad_value_2", check_type=bool)
+    assert ("[conf] user:bad_value_2 must be a boolean-like object (true/false, 1/0, on/off) and value '10' does not match it.") in str(exc_info.value)
+    with pytest.raises(ConanException) as exc_info:
+        c.get("user:bad_value_3", check_type=bool)
+    assert ("[conf] user:bad_value_3 must be a boolean-like object (true/false, 1/0, on/off) and value '\'00\'' does not match it.") in str(exc_info.value)
 
 
 def test_conf_pop():