Repository: conan-io/conan
Pull request #18083 (2025-04-04 19:21:07)

Check types in ``conf_info``
Changelog: Feature: Raise error early if ``conf_info`` is assigned with raw settings/options etc
Docs: Omit

Close https://github.com/conan-io/conan/issues/14831


Patch:
diff --git a/conan/api/subapi/cache.py b/conan/api/subapi/cache.py
index b42715070..c188f5fad 100644
--- a/conan/api/subapi/cache.py
+++ b/conan/api/subapi/cache.py
@@ -24,7 +24,7 @@ class CacheAPI:
 
     def export_path(self, ref: RecipeReference):
         cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
-        ref.revision = None if ref.revision == "latest" else ref.revision
+        ref = _resolve_latest_ref(cache, ref)
         ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "export", ref_layout.export())
 
@@ -36,13 +36,13 @@ class CacheAPI:
 
     def export_source_path(self, ref: RecipeReference):
         cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
-        ref.revision = None if ref.revision == "latest" else ref.revision
+        ref = _resolve_latest_ref(cache, ref)
         ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "export_sources", ref_layout.export_sources())
 
     def source_path(self, ref: RecipeReference):
         cache = PkgCache(self.conan_api.cache_folder, self.conan_api.config.global_conf)
-        ref.revision = None if ref.revision == "latest" else ref.revision
+        ref = _resolve_latest_ref(cache, ref)
         ref_layout = cache.recipe_layout(ref)
         return _check_folder_existence(ref, "source", ref_layout.source())
 
diff --git a/conan/internal/api/uploader.py b/conan/internal/api/uploader.py
index ce057893e..8a014732e 100644
--- a/conan/internal/api/uploader.py
+++ b/conan/internal/api/uploader.py
@@ -82,9 +82,9 @@ class PackagePreparator:
         self._app = app
         self._global_conf = global_conf
 
-    def prepare(self, upload_bundle, enabled_remotes):
+    def prepare(self, pkg_list, enabled_remotes):
         local_url = self._global_conf.get("core.scm:local_url", choices=["allow", "block"])
-        for ref, bundle in upload_bundle.refs().items():
+        for ref, bundle in pkg_list.refs().items():
             layout = self._app.cache.recipe_layout(ref)
             conanfile_path = layout.conanfile()
             conanfile = self._app.loader.load_basic(conanfile_path)
@@ -97,7 +97,7 @@ class PackagePreparator:
 
             if bundle.get("upload"):
                 self._prepare_recipe(ref, bundle, conanfile, enabled_remotes)
-            for pref, prev_bundle in upload_bundle.prefs(ref, bundle).items():
+            for pref, prev_bundle in pkg_list.prefs(ref, bundle).items():
                 if prev_bundle.get("upload"):
                     self._prepare_package(pref, prev_bundle)
 
diff --git a/conan/internal/cache/cache.py b/conan/internal/cache/cache.py
index 8363cc4f5..bf7d64d2b 100644
--- a/conan/internal/cache/cache.py
+++ b/conan/internal/cache/cache.py
@@ -111,10 +111,8 @@ class PkgCache:
     def recipe_layout(self, ref: RecipeReference):
         """ the revision must exists, the folder must exist
         """
-        if ref.revision is None:  # Latest one
-            ref_data = self._db.get_latest_recipe(ref)
-        else:
-            ref_data = self._db.get_recipe(ref)
+        assert ref.revision is not None
+        ref_data = self._db.get_recipe(ref)
         ref_path = ref_data.get("path")
         ref = ref_data.get("ref")  # new revision with timestamp
         return RecipeLayout(ref, os.path.join(self._base_folder, ref_path))
diff --git a/conan/internal/cache/integrity_check.py b/conan/internal/cache/integrity_check.py
index 58dec88ea..b1148e612 100644
--- a/conan/internal/cache/integrity_check.py
+++ b/conan/internal/cache/integrity_check.py
@@ -20,11 +20,11 @@ class IntegrityChecker:
     def __init__(self, cache):
         self._cache = cache
 
-    def check(self, upload_data):
+    def check(self, pkg_list):
         corrupted = False
-        for ref, recipe_bundle in upload_data.refs().items():
+        for ref, recipe_bundle in pkg_list.refs().items():
             corrupted = self._recipe_corrupted(ref) or corrupted
-            for pref, prev_bundle in upload_data.prefs(ref, recipe_bundle).items():
+            for pref, prev_bundle in pkg_list.prefs(ref, recipe_bundle).items():
                 corrupted = self._package_corrupted(pref) or corrupted
         if corrupted:
             raise ConanException("There are corrupted artifacts, check the error logs")
diff --git a/conan/internal/default_settings.py b/conan/internal/default_settings.py
index 51b2b5d55..0be97dce2 100644
--- a/conan/internal/default_settings.py
+++ b/conan/internal/default_settings.py
@@ -146,7 +146,8 @@ compiler:
         cstd: [null, 99, gnu99, 11, gnu11, 17, gnu17, 23, gnu23]
     intel-cc:
         version: ["2021.1", "2021.2", "2021.3", "2021.4", "2022.1", "2022.2",
-                  "2022.3", "2023.0", "2023.1", "2023.2", "2024.0",]
+                  "2022.3", "2023.0", "2023.1", "2023.2", "2024.0", "2024.1",
+                  "2025.0", "2025.1"]
         update: [null, ANY]
         mode: ["icx", "classic", "dpcpp"]
         libcxx: [null, libstdc++, libstdc++11, libc++]
diff --git a/conan/internal/model/conf.py b/conan/internal/model/conf.py
index 3e3e95959..4cb9d7c29 100644
--- a/conan/internal/model/conf.py
+++ b/conan/internal/model/conf.py
@@ -7,7 +7,9 @@ import fnmatch
 from collections import OrderedDict
 
 from conan.errors import ConanException
+from conan.internal.model.options import _PackageOption
 from conan.internal.model.recipe_ref import ref_matches
+from conan.internal.model.settings import SettingsItem
 
 BUILT_IN_CONFS = {
     "core:required_conan_version": "Raise if current version does not match the defined range.",
@@ -155,7 +157,7 @@ class _ConfVarPlaceHolder:
     pass
 
 
-class _ConfValue(object):
+class _ConfValue:
 
     def __init__(self, name, value, path=False, update=None):
         if name != name.lower():
@@ -163,6 +165,8 @@ class _ConfValue(object):
         self._name = name
         self._value = value
         self._value_type = type(value)
+        if isinstance(value, (_PackageOption, SettingsItem)):
+            raise ConanException(f"Invalid 'conf' type, please use Python types (int, str, ...)")
         self._path = path
         self._update = update
 
@@ -220,6 +224,8 @@ class _ConfValue(object):
         if isinstance(value, list):
             self._value.extend(value)
         else:
+            if isinstance(value, (_PackageOption, SettingsItem)):
+                raise ConanException(f"Invalid 'conf' type, please use Python types (int, str, ...)")
             self._value.append(value)
 
     def prepend(self, value):
@@ -229,6 +235,8 @@ class _ConfValue(object):
         if isinstance(value, list):
             self._value = value + self._value
         else:
+            if isinstance(value, (_PackageOption, SettingsItem)):
+                raise ConanException(f"Invalid 'conf' type, please use Python types (int, str, ...)")
             self._value.insert(0, value)
 
     def compose_conf_value(self, other):
diff --git a/conan/internal/model/layout.py b/conan/internal/model/layout.py
index 5ddacf543..f03c42efd 100644
--- a/conan/internal/model/layout.py
+++ b/conan/internal/model/layout.py
@@ -32,7 +32,7 @@ class Layouts(object):
         self.package = PartialLayout()
 
 
-class Folders(object):
+class Folders:
 
     def __init__(self):
         self._base_source = None
@@ -60,9 +60,6 @@ class Folders(object):
         self.subproject = None
         self.build_folder_vars = None
 
-    def __repr__(self):
-        return str(self.__dict__)
-
     def set_base_folders(self, conanfile_folder, output_folder):
         """ this methods can be used for defining all the base folders in the
         local flow (conan install, source, build), where only the current conanfile location
diff --git a/conans/client/graph/proxy.py b/conans/client/graph/proxy.py
index 72b927516..c2d416437 100644
--- a/conans/client/graph/proxy.py
+++ b/conans/client/graph/proxy.py
@@ -38,7 +38,9 @@ class ConanProxy:
 
         # check if it there's any revision of this recipe in the local cache
         try:
-            recipe_layout = self._cache.recipe_layout(reference)
+            cache_ref = self._cache.get_latest_recipe_reference(reference) \
+                if reference.revision is None else reference
+            recipe_layout = self._cache.recipe_layout(cache_ref)
             ref = recipe_layout.reference  # latest revision if it was not defined
         except ConanException:
             # NOT in disk, must be retrieved from remotes


Test patch:
diff --git a/conan/test/utils/tools.py b/conan/test/utils/tools.py
index 27fdb204c..ac819dcf5 100644
--- a/conan/test/utils/tools.py
+++ b/conan/test/utils/tools.py
@@ -700,6 +700,8 @@ class TestClient:
 
     def get_latest_ref_layout(self, ref) -> RecipeLayout:
         """Get the latest RecipeLayout given a file reference"""
+        if not ref.revision:
+            ref = self.cache.get_latest_recipe_reference(ref)
         ref_layout = self.cache.recipe_layout(ref)
         return ref_layout
 
diff --git a/test/functional/toolchains/cmake/cmakedeps/test_link_order.py b/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
index 61f7332e9..16f1cb4f1 100644
--- a/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
+++ b/test/functional/toolchains/cmake/cmakedeps/test_link_order.py
@@ -216,8 +216,8 @@ def _validate_link_order(libs):
     assert libs.index(prefix + 'Z2' + ext) < libs.index('system_lib' + ext_system)
 
     if platform.system() == "Darwin":
-       assert libs.index('liblibz.a') < libs.index('Carbon')
-       assert libs.index('libZ2.a') < libs.index('Carbon')
+        assert libs.index('liblibz.a') < libs.index('Carbon')
+        assert libs.index('libZ2.a') < libs.index('Carbon')
 
 
 def _get_link_order_from_cmake(content):
@@ -263,12 +263,13 @@ def _get_link_order_from_xcode(content):
 
     # Find the right Release block in the XCode file
     results = re.finditer(r'/\* Release \*/ = {', content)
+    header_found = False
     for r in results:
         release_section = content[r.start():].split("name = Release;", 1)[0]
         if "-headerpad_max_install_names" in release_section:
+            header_found = True
             break
-    else:
-        raise Exception("Cannot find the Release block linking the expected libraries")
+    assert header_found, "Cannot find the Release block linking the expected libraries"
 
     start_key = '-Wl,-headerpad_max_install_names'
     end_key = ');'
diff --git a/test/integration/configuration/conf/test_conf_from_br.py b/test/integration/configuration/conf/test_conf_from_br.py
index 4b9aafe9e..a0d067526 100644
--- a/test/integration/configuration/conf/test_conf_from_br.py
+++ b/test/integration/configuration/conf/test_conf_from_br.py
@@ -1,5 +1,7 @@
 import textwrap
 
+import pytest
+
 from conan.test.assets.genconanfile import GenConanfile
 from conan.test.utils.tools import TestClient
 
@@ -266,3 +268,28 @@ def test_conf_both_build_and_host():
     client.run("install app --build=missing -s:h build_type=Debug -s:b build_type=Release")
     assert "myprotobuf/0.1: MYCONF build: Release" in client.out
     assert "myprotobuf/0.1: MYCONF host: Debug" in client.out
+
+
+@pytest.mark.parametrize("conf", ['define("user.myorg:myconf", self.options.myopt)',
+                                  'define("user.myorg:myconf", self.settings.os)',
+                                  'append("user.myorg:myconf", self.settings.os)',
+                                  'prepend("user.myorg:myconf", self.settings.os)'])
+def test_error_bad_types(conf):
+    c = TestClient(light=True)
+    conanfile = textwrap.dedent(f"""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            name = "tool"
+            version = "0.1"
+            settings = "os"
+            options = {{"myopt": [True, False]}}
+            default_options = {{"myopt": True}}
+            def package_info(self):
+                self.conf_info.{conf}
+        """)
+    c.save({"conanfile.py": conanfile})
+    c.run("create .", assert_error=True)
+    assert "ERROR: tool/0.1: Error in package_info() method, line 10"
+    assert f'self.conf_info.{conf}' in c.out
+    assert "Invalid 'conf' type, please use Python types (int, str, ...)" in c.out
+
diff --git a/test/unittests/client/build/cpp_std_flags_test.py b/test/unittests/client/build/cpp_std_flags_test.py
index f4f54c6d5..b90cfa8a4 100644
--- a/test/unittests/client/build/cpp_std_flags_test.py
+++ b/test/unittests/client/build/cpp_std_flags_test.py
@@ -304,11 +304,15 @@ class CompilerFlagsTest(unittest.TestCase):
         self.assertEqual(_make_cppstd_default("msvc", "193"), "14")
 
     def test_intel_cppstd_flag(self):
-        self.assertEqual(_make_cppstd_flag("intel-cc", "19.1", "gnu98"), '-std=gnu++98')
-        self.assertEqual(_make_cppstd_flag("intel-cc", "19.1", "11"), '-std=c++11')
-        self.assertEqual(_make_cppstd_flag("intel-cc", "19.1", "14"), '-std=c++14')
-        self.assertEqual(_make_cppstd_flag("intel-cc", "19.1", "17"), '-std=c++17')
-        self.assertEqual(_make_cppstd_flag("intel-cc", "19.1", "20"), '-std=c++20')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2024.1", "gnu98"), '-std=gnu++98')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2024.1", "11"), '-std=c++11')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2024.1", "14"), '-std=c++14')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2024.1", "17"), '-std=c++17')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2024.1", "20"), '-std=c++20')
+
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2025.1", "17"), '-std=c++17')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2025.1", "20"), '-std=c++20')
+        self.assertEqual(_make_cppstd_flag("intel-cc", "2025.1", "23"), '-std=c++2b')
 
     def test_mcst_lcc_cppstd_defaults(self):
         self.assertEqual(_make_cppstd_default("mcst-lcc", "1.19"), "gnu98")
diff --git a/test/unittests/model/test_conf.py b/test/unittests/model/test_conf.py
index a20b132b0..9afa00248 100644
--- a/test/unittests/model/test_conf.py
+++ b/test/unittests/model/test_conf.py
@@ -298,7 +298,7 @@ def test_conf_get_check_type_and_default():
     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)
-        assert "[conf] user.company.cpu:jobs must be a list-like object." in str(exc_info.value)
+    assert "[conf] user.company.cpu:jobs must be a list-like object." in str(exc_info.value)
     # Check type does not affect to default value
     assert c.get("user.non:existing", default=0, check_type=dict) == 0
     assert c.get("zlib:user.company.check:shared") is None  # unset value
@@ -315,16 +315,16 @@ def test_conf_get_check_type_and_default():
             "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)
+    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)
+    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)
+    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)
+    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():
diff --git a/test/unittests/tools/cmake/test_cmaketoolchain.py b/test/unittests/tools/cmake/test_cmaketoolchain.py
index 26169dece..2850f1fb4 100644
--- a/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -644,7 +644,7 @@ def test_apple_cmake_osx_sysroot_sdk_mandatory(os, arch, expected_sdk):
 
     with pytest.raises(ConanException) as excinfo:
         CMakeToolchain(c).content()
-        assert "Please, specify a suitable value for os.sdk." % expected_sdk in str(excinfo.value)
+    assert "Please, specify a suitable value for os.sdk." in str(excinfo.value)
 
 
 def test_compilers_block(conanfile):