Repository: conan-io/conan
Pull request #16105 (2024-04-18 15:08:12)

Allow wildcard in `rm_safe` call
Changelog: Feature: Allow `*` wildcard as subsetting in in `rm_safe`
Docs: TODO

To check with the team, came up in a conversation with @czoido when trying to debug the openjdk permission issues

Note that this is just a draft, because this is pretty similar to doing
```
 for subsetting in self.settings.compiler.fields:
     self.settings.compiler.rm_safe(subsetting)
```

so we might now want to have two ways of doing the same thing, even if the current syntaxt is a bit odd (There's something to say from me just realizing about the `fields` property)


Patch:
diff --git a/conan/internal/api/detect_api.py b/conan/internal/api/detect_api.py
index bafffade5..8bdd5843e 100644
--- a/conan/internal/api/detect_api.py
+++ b/conan/internal/api/detect_api.py
@@ -361,39 +361,38 @@ def detect_default_compiler():
         output.info("CC and CXX: %s, %s " % (cc or "None", cxx or "None"))
         command = cc or cxx
         if "clang" in command.lower():
-            return _clang_compiler(command)
+            return detect_clang_compiler(command)
         if "gnu-cc" in command or "gcc" in command or "g++" in command or "c++" in command:
-            gcc, gcc_version, compiler_exe = _gcc_compiler(command)
+            gcc, gcc_version, compiler_exe = detect_gcc_compiler(command)
             if platform.system() == "Darwin" and gcc is None:
                 output.error("%s detected as a frontend using apple-clang. "
                              "Compiler not supported" % command)
             return gcc, gcc_version, compiler_exe
         if "icpx" in command or "icx" in command:
-            intel, intel_version, compiler_exe = _intel_compiler(command)
+            intel, intel_version, compiler_exe = detect_intel_compiler(command)
             return intel, intel_version, compiler_exe
         if platform.system() == "SunOS" and command.lower() == "cc":
-            return _sun_cc_compiler(command)
+            return detect_suncc_compiler(command)
         if (platform.system() == "Windows" and command.rstrip('"').endswith(("cl", "cl.exe"))
                 and "clang" not in command):
-            return _msvc_cl_compiler(command)
+            return detect_cl_compiler(command)
 
         # I am not able to find its version
         output.error("Not able to automatically detect '%s' version" % command)
         return None, None, None
 
     if platform.system() == "Windows":
-        version = _detect_vs_ide_version()
-        version = {"17": "193", "16": "192", "15": "191"}.get(str(version))  # Map to compiler
-        if version:
-            return 'msvc', Version(version), None
+        compiler, version, compiler_exe = detect_msvc_compiler()
+        if compiler:
+            return compiler, version, compiler_exe
 
     if platform.system() == "SunOS":
-        sun_cc, sun_cc_version, compiler_exe = _sun_cc_compiler()
+        sun_cc, sun_cc_version, compiler_exe = detect_suncc_compiler()
         if sun_cc:
             return sun_cc, sun_cc_version, compiler_exe
 
     if platform.system() in ["Darwin", "FreeBSD"]:
-        clang, clang_version, compiler_exe = _clang_compiler()  # prioritize clang
+        clang, clang_version, compiler_exe = detect_clang_compiler()  # prioritize clang
         if clang:
             return clang, clang_version, compiler_exe
         return None, None, None
@@ -401,10 +400,10 @@ def detect_default_compiler():
         compiler, compiler_version, compiler_exe = _cc_compiler()
         if compiler:
             return compiler, compiler_version, compiler_exe
-        gcc, gcc_version, compiler_exe = _gcc_compiler()
+        gcc, gcc_version, compiler_exe = detect_gcc_compiler()
         if gcc:
             return gcc, gcc_version, compiler_exe
-        return _clang_compiler()
+        return detect_clang_compiler()
 
 
 def default_msvc_ide_version(version):
@@ -441,7 +440,7 @@ def _cc_compiler():
         return None, None, None
 
 
-def _gcc_compiler(compiler_exe="gcc"):
+def detect_gcc_compiler(compiler_exe="gcc"):
     try:
         if platform.system() == "Darwin":
             # In Mac OS X check if gcc is a fronted using apple-clang
@@ -471,7 +470,7 @@ def detect_compiler():
     return compiler, version
 
 
-def _intel_compiler(compiler_exe="icx"):
+def detect_intel_compiler(compiler_exe="icx"):
     try:
         ret, out = detect_runner("%s --version" % compiler_exe)
         if ret != 0:
@@ -485,7 +484,7 @@ def _intel_compiler(compiler_exe="icx"):
         return None, None, None
 
 
-def _sun_cc_compiler(compiler_exe="cc"):
+def detect_suncc_compiler(compiler_exe="cc"):
     try:
         _, out = detect_runner('%s -V' % compiler_exe)
         compiler = "sun-cc"
@@ -501,7 +500,7 @@ def _sun_cc_compiler(compiler_exe="cc"):
         return None, None, None
 
 
-def _clang_compiler(compiler_exe="clang"):
+def detect_clang_compiler(compiler_exe="clang"):
     try:
         ret, out = detect_runner('%s --version' % compiler_exe)
         if ret != 0:
@@ -520,7 +519,15 @@ def _clang_compiler(compiler_exe="clang"):
         return None, None, None
 
 
-def _msvc_cl_compiler(compiler_exe="cl"):
+def detect_msvc_compiler():
+    version = _detect_vs_ide_version()
+    version = {"17": "193", "16": "192", "15": "191"}.get(str(version))  # Map to compiler
+    if version:
+        return 'msvc', Version(version), None
+    return None, None, None
+
+
+def detect_cl_compiler(compiler_exe="cl"):
     """ only if CC/CXX env-vars are defined pointing to cl.exe, and the VS environment must
     be active to have them in the path
     """
diff --git a/conans/model/settings.py b/conans/model/settings.py
index 8ebe924b4..347ab40a5 100644
--- a/conans/model/settings.py
+++ b/conans/model/settings.py
@@ -205,7 +205,7 @@ class Settings(object):
 
     def get_safe(self, name, default=None):
         """
-        Get the setting value avoiding
+        Get the setting value avoiding throwing if it does not exist or has been removed
         :param name:
         :param default:
         :return:
@@ -232,7 +232,10 @@ class Settings(object):
             except KeyError:
                 pass
         else:
-            self._data.pop(name, None)
+            if name == "*":
+                self.clear()
+            else:
+                self._data.pop(name, None)
 
     def copy(self):
         """ deepcopy, recursive


Test patch:
diff --git a/conans/test/functional/test_profile_detect_api.py b/conans/test/functional/test_profile_detect_api.py
index 8efa5e7a6..820e41599 100644
--- a/conans/test/functional/test_profile_detect_api.py
+++ b/conans/test/functional/test_profile_detect_api.py
@@ -45,12 +45,14 @@ class TestProfileDetectAPI:
 
     @pytest.mark.skipif(platform.system() != "Linux", reason="Only linux")
     def test_profile_detect_libc(self):
-
         client = TestClient()
         tpl1 = textwrap.dedent("""
+            {% set compiler, version, _ = detect_api.detect_gcc_compiler() %}
             {% set libc, libc_version = detect_api.detect_libc() %}
             [settings]
             os=Linux
+            compiler={{compiler}}
+            compiler.version={{version}}
             [conf]
             user.confvar:libc={{libc}}
             user.confvar:libc_version={{libc_version}}
@@ -61,9 +63,12 @@ class TestProfileDetectAPI:
         libc_name, libc_version = detect_api.detect_libc()
         assert libc_name is not None
         assert libc_version is not None
+        _, version, _ = detect_api.detect_gcc_compiler()
         expected = textwrap.dedent(f"""\
             Host profile:
             [settings]
+            compiler=gcc
+            compiler.version={version}
             os=Linux
             [conf]
             user.confvar:libc={libc_name}
diff --git a/conans/test/integration/configuration/test_profile_jinja.py b/conans/test/integration/configuration/test_profile_jinja.py
index b8dd5d331..4972a5233 100644
--- a/conans/test/integration/configuration/test_profile_jinja.py
+++ b/conans/test/integration/configuration/test_profile_jinja.py
@@ -198,3 +198,10 @@ class TestProfileDetectAPI:
             os={the_os}
             """)
         assert expected in client.out
+
+
+def test_profile_jinja_error():
+    c = TestClient(light=True)
+    c.save({"profile1": "{% set kk = other() %}"})
+    c.run("profile show -pr=profile1", assert_error=True)
+    assert "ERROR: Error while rendering the profile template file" in c.out
diff --git a/conans/test/unittests/client/conf/detect/test_gcc_compiler.py b/conans/test/unittests/client/conf/detect/test_gcc_compiler.py
index e88743832..dd8cc5e80 100644
--- a/conans/test/unittests/client/conf/detect/test_gcc_compiler.py
+++ b/conans/test/unittests/client/conf/detect/test_gcc_compiler.py
@@ -4,7 +4,7 @@ import mock
 from parameterized import parameterized
 
 
-from conan.internal.api.detect_api import _gcc_compiler
+from conan.internal.api.detect_api import detect_gcc_compiler
 
 
 class GCCCompilerTestCase(unittest.TestCase):
@@ -13,7 +13,7 @@ class GCCCompilerTestCase(unittest.TestCase):
     def test_detect_gcc_10(self, version):
         with mock.patch("platform.system", return_value="Linux"):
             with mock.patch("conan.internal.api.detect_api.detect_runner", return_value=(0, version)):
-                compiler, installed_version, compiler_exe = _gcc_compiler()
+                compiler, installed_version, compiler_exe = detect_gcc_compiler()
         self.assertEqual(compiler, 'gcc')
         self.assertEqual(installed_version, version)
         self.assertEqual(compiler_exe, 'gcc')
diff --git a/conans/test/unittests/model/settings_test.py b/conans/test/unittests/model/settings_test.py
index 94d4a4467..63960772d 100644
--- a/conans/test/unittests/model/settings_test.py
+++ b/conans/test/unittests/model/settings_test.py
@@ -465,6 +465,17 @@ def test_rm_safe():
     assert "'settings.compiler.libcxx' doesn't exist for 'clang'" in str(e.value)
 
 
+def test_rm_safe_wildcard():
+    settings = Settings.loads(default_settings_yml)
+    settings.compiler = "gcc"
+    settings.compiler.version = "4.8"
+    settings.compiler.libcxx = "libstdc++"
+    settings.rm_safe("compiler.*")
+    assert settings.compiler == "gcc"
+    assert settings.get_safe("compiler.version") is None
+    assert settings.get_safe("compiler.libcxx") is None
+
+
 def test_settings_intel_cppstd_03():
     settings = Settings.loads(default_settings_yml)
     settings.compiler = "intel-cc"
diff --git a/conans/test/unittests/util/detect_test.py b/conans/test/unittests/util/detect_test.py
index c86bff14e..c5272a005 100644
--- a/conans/test/unittests/util/detect_test.py
+++ b/conans/test/unittests/util/detect_test.py
@@ -48,7 +48,7 @@ class DetectTest(unittest.TestCase):
             result = dict(result)
             self.assertEqual(expected_arch, result['arch'])
 
-    @mock.patch("conan.internal.api.detect_api._clang_compiler",
+    @mock.patch("conan.internal.api.detect_api.detect_clang_compiler",
                 return_value=("clang", Version("9"), "clang"))
     def test_detect_clang_gcc_toolchain(self, _):
         output = RedirectedTestOutput()