Repository: conan-io/conan
Pull request #12379 (2022-10-23 22:05:10)

[develop2] Improve ``settings.rm_safe()``
Close https://github.com/conan-io/conan/issues/12370


Patch:
diff --git a/conans/model/settings.py b/conans/model/settings.py
index 9e58c13d8..84acd42a4 100644
--- a/conans/model/settings.py
+++ b/conans/model/settings.py
@@ -160,6 +160,15 @@ class SettingsItem(object):
             ret[key] = value.get_definition()
         return ret
 
+    def rm_safe(self, name):
+        """ Iterates all possible subsettings, calling rm_safe() for all of them. If removing
+        "compiler.cppstd", this will iterate msvc, gcc, clang, etc, calling rm_safe(cppstd) for
+        all of them"""
+        if isinstance(self._definition, list):
+            return
+        for subsetting in self._definition.values():
+            subsetting.rm_safe(name)
+
 
 class Settings(object):
     def __init__(self, definition=None, name="settings", parent_value="settings"):
@@ -199,17 +208,18 @@ class Settings(object):
         return default
 
     def rm_safe(self, name):
-        try:
-            tmp = self
-            attr_ = name
-            if "." in name:
-                fields = name.split(".")
-                attr_ = fields.pop()
-                for prop in fields:
-                    tmp = getattr(tmp, prop)
-            delattr(tmp, attr_)
-        except ConanException:
-            pass
+        """ Removes the setting or subsetting from the definition. For example,
+        rm_safe("compiler.cppstd") remove all "cppstd" subsetting from all compilers, irrespective
+        of the current value of the "compiler"
+        """
+        if "." in name:
+            setting, remainder = name.split(".", 1)  # setting=compiler, remainder = cppstd
+            try:
+                self._data[setting].rm_safe(remainder)  # call rm_safe("cppstd") for the "compiler"
+            except KeyError:
+                pass
+        else:
+            self._data.pop(name, None)
 
     def copy(self):
         """ deepcopy, recursive


Test patch:
diff --git a/conans/test/unittests/model/settings_test.py b/conans/test/unittests/model/settings_test.py
index a2abe6474..54f331b62 100644
--- a/conans/test/unittests/model/settings_test.py
+++ b/conans/test/unittests/model/settings_test.py
@@ -415,3 +415,23 @@ os: [Windows, Linux]
         sot = settings.get_definition()
         assert [None, "cygwin", "msys", "msys2", "wsl"] == sot["os"]["Windows"]["subsystem"]
 
+
+def test_rm_safe():
+    settings = Settings.loads(default_settings_yml)
+    settings.rm_safe("compiler.cppstd")
+    settings.rm_safe("compiler.libcxx")
+    settings.compiler = "gcc"
+    with pytest.raises(Exception) as e:
+        settings.compiler.cppstd = "14"
+    assert "'settings.compiler.cppstd' doesn't exist for 'gcc'" in str(e.value)
+    with pytest.raises(Exception) as e:
+        settings.compiler.libcxx = "libstdc++"
+    assert "'settings.compiler.libcxx' doesn't exist for 'gcc'" in str(e.value)
+
+    settings.compiler = "clang"
+    with pytest.raises(Exception) as e:
+        settings.compiler.cppstd = "14"
+    assert "'settings.compiler.cppstd' doesn't exist for 'clang'" in str(e.value)
+    with pytest.raises(Exception) as e:
+        settings.compiler.libcxx = "libstdc++"
+    assert "'settings.compiler.libcxx' doesn't exist for 'clang'" in str(e.value)