Repository: conan-io/conan
Pull request: Feature/improve settings migration
Changelog: omit
Docs: omit

Close #5038

Problem statement:
Improve migration of settings.yml
**Current behavior**

We are not currently "patching" the `settings.yml` file nor modifying it. Only if we detect that the current yml on disk is the same as the previous version one, we replace it with the new one and store the old one in a `.old` file or similar. 
This behavior is like this because people don't like us to change a customized `settings.yml` file.

**Several issues**

- It is not easy for contributors to understand that the "yml" text in the `migrations.py` shouldn't be modified when a new setting is introduced in the default one for that release.
- It is easy to forget to introduce the corresponding migration in a release.
- It is almost impossible to automate a test that fails if the previous yml setting migration is not created.
- It is ugly to have there the code of the `.yml`.

**How to improve it**

- Add a python module with variables following the `settings_x_y_z`. E.g: `settings_1_14_4`
- Even if there are no changes between the releases we should do: `settings_1_14_4 = settings_1_14_3`
- Add a test to check that at least there is a variable `settings_1_14_*`, this is not perfect because we don't really know the previous version but it is not likely to change the settings between minors.
- The migrations algorithm will do always the same, and automatically: "if I came from version x_y_z and my settings are equal that the settings in the variable `settings_x_y_z` then I replace them with the new settings yaml". "If there is no variable for my version, then skip any change"

Hints from issue discussion:

Commit message: Test

Patch to review:
diff --git a/conans/client/migrations.py b/conans/client/migrations.py
index db5a91efb42..c7ddbcfd501 100644
--- a/conans/client/migrations.py
+++ b/conans/client/migrations.py
@@ -2,6 +2,7 @@
 import shutil
 
 from conans import DEFAULT_REVISION_V1
+from conans.client import migrations_settings
 from conans.client.cache.cache import CONAN_CONF, PROFILES_FOLDER
 from conans.client.conf.config_installer import _ConfigOrigin, _save_configs
 from conans.client.tools import replace_in_file
@@ -25,7 +26,8 @@ def __init__(self, cache, current_version, out):
         super(ClientMigrator, self).__init__(cache.conan_folder, cache.store,
                                              current_version, out)
 
-    def _update_settings_yml(self, old_settings):
+    def _update_settings_yml(self, old_version):
+
         from conans.client.conf import default_settings_yml
         settings_path = self.cache.settings_path
         if not os.path.exists(settings_path):
@@ -33,18 +35,30 @@ def _update_settings_yml(self, old_settings):
             self.out.warn("Nothing to migrate here, settings will be generated automatically")
             return
 
-        current_settings = load(self.cache.settings_path)
-        if current_settings != default_settings_yml:
-            self.out.warn("Migration: Updating settings.yml")
-            if current_settings != old_settings:
-                new_path = self.cache.settings_path + ".new"
-                save(new_path, default_settings_yml)
-                self.out.warn("*" * 40)
-                self.out.warn("settings.yml is locally modified, can't be updated")
-                self.out.warn("The new settings.yml has been stored in: %s" % new_path)
-                self.out.warn("*" * 40)
+        var_name = "settings_{}".format(old_version.replace(".", "_"))
+
+        def save_new():
+            new_path = self.cache.settings_path + ".new"
+            save(new_path, default_settings_yml)
+            self.out.warn("*" * 40)
+            self.out.warn("settings.yml is locally modified, can't be updated")
+            self.out.warn("The new settings.yml has been stored in: %s" % new_path)
+            self.out.warn("*" * 40)
+
+        self.out.warn("Migration: Updating settings.yml")
+        if hasattr(migrations_settings, var_name):
+            version_default_contents = getattr(migrations_settings, var_name)
+            if version_default_contents != default_settings_yml:
+                current_settings = load(self.cache.settings_path)
+                if current_settings != version_default_contents:
+                    save_new()
+                else:
+                    save(self.cache.settings_path, default_settings_yml)
             else:
-                save(self.cache.settings_path, default_settings_yml)
+                self.out.warn("Migration: Settings already up to date")
+        else:
+            # We don't have the value for that version, so don't override
+            save_new()
 
     def _make_migrations(self, old_version):
         # ############### FILL THIS METHOD WITH THE REQUIRED ACTIONS ##############
@@ -52,6 +66,9 @@ def _make_migrations(self, old_version):
         if old_version is None:
             return
 
+        # Migrate the settings if they were the default for that version
+        self._update_settings_yml(old_version)
+
         if old_version < Version("0.25"):
             from conans.paths import DEFAULT_PROFILE_NAME
             default_profile_path = os.path.join(self.cache.conan_folder, PROFILES_FOLDER,
@@ -73,75 +90,6 @@ def _make_migrations(self, old_version):
             migrate_plugins_to_hooks(self.cache)
 
         if old_version < Version("1.13.0"):
-            old_settings = """
-# Only for cross building, 'os_build/arch_build' is the system that runs Conan
-os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
-arch_build: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
-
-# Only for building cross compilation tools, 'os_target/arch_target' is the system for
-# which the tools generate code
-os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
-arch_target: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
-
-# Rest of the settings are "host" settings:
-# - For native building/cross building: Where the library/program will run.
-# - For building cross compilation tools: Where the cross compiler will run.
-os:
-    Windows:
-        subsystem: [None, cygwin, msys, msys2, wsl]
-    WindowsStore:
-        version: ["8.1", "10.0"]
-    Linux:
-    Macos:
-        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
-    Android:
-        api_level: ANY
-    iOS:
-        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
-    watchOS:
-        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1"]
-    tvOS:
-        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
-    FreeBSD:
-    SunOS:
-    Arduino:
-        board: ANY
-arch: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
-compiler:
-    sun-cc:
-        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
-        threads: [None, posix]
-        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
-    gcc:
-        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
-                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
-                  "6", "6.1", "6.2", "6.3", "6.4",
-                  "7", "7.1", "7.2", "7.3",
-                  "8", "8.1", "8.2"]
-        libcxx: [libstdc++, libstdc++11]
-        threads: [None, posix, win32] #  Windows MinGW
-        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
-    Visual Studio:
-        runtime: [MD, MT, MTd, MDd]
-        version: ["8", "9", "10", "11", "12", "14", "15"]
-        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
-                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
-                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
-                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2]
-    clang:
-        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
-                  "5.0", "6.0", "7.0",
-                  "8"]
-        libcxx: [libstdc++, libstdc++11, libc++]
-    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"]
-        libcxx: [libstdc++, libc++]
-
-build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
-cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
-"""
-            self._update_settings_yml(old_settings)
-
             # MIGRATE LOCAL CACHE TO GENERATE MISSING METADATA.json
             _migrate_create_metadata(self.cache, self.out)
 
diff --git a/conans/client/migrations_settings.py b/conans/client/migrations_settings.py
new file mode 100644
index 00000000000..b96bdbc186a
--- /dev/null
+++ b/conans/client/migrations_settings.py
@@ -0,0 +1,290 @@
+settings_1_9_0 = """
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
+arch_build: [x86, x86_64, ppc64le, ppc64, armv6, armv7, armv7hf, armv8, sparc, sparcv9, mips, mips64, avr, armv7s, armv7k]
+
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
+# which the tools generate code
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
+arch_target: [x86, x86_64, ppc64le, ppc64, armv6, armv7, armv7hf, armv8, sparc, sparcv9, mips, mips64, avr, armv7s, armv7k]
+
+# Rest of the settings are "host" settings:
+# - For native building/cross building: Where the library/program will run.
+# - For building cross compilation tools: Where the cross compiler will run.
+os:
+    Windows:
+        subsystem: [None, cygwin, msys, msys2, wsl]
+    WindowsStore:
+        version: ["8.1", "10.0"]
+    Linux:
+    Macos:
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
+    Android:
+        api_level: ANY
+    iOS:
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0"]
+    watchOS:
+        version: ["4.0"]
+    tvOS:
+        version: ["11.0"]
+    FreeBSD:
+    SunOS:
+    Arduino:
+        board: ANY
+arch: [x86, x86_64, ppc64le, ppc64, armv6, armv7, armv7hf, armv8, sparc, sparcv9, mips, mips64, avr, armv7s, armv7k]
+compiler:
+    sun-cc:
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
+        threads: [None, posix]
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
+    gcc:
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
+                  "6", "6.1", "6.2", "6.3", "6.4",
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1", "8.2"]
+        libcxx: [libstdc++, libstdc++11]
+        threads: [None, posix, win32] #  Windows MinGW
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+    Visual Studio:
+        runtime: [MD, MT, MTd, MDd]
+        version: ["8", "9", "10", "11", "12", "14", "15"]
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2]
+    clang:
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
+                  "5.0", "6.0", "7.0",
+                  "8"]
+        libcxx: [libstdc++, libstdc++11, libc++]
+    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"]
+        libcxx: [libstdc++, libc++]
+
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+"""
+
+settings_1_9_1 = settings_1_9_0
+settings_1_9_2 = settings_1_9_1
+settings_1_10_0 = settings_1_9_2
+settings_1_10_1 = settings_1_10_0
+settings_1_10_2 = settings_1_10_1
+settings_1_11_0 = settings_1_10_2
+settings_1_11_1 = settings_1_11_0
+settings_1_11_2 = settings_1_11_1
+settings_1_12_0 = """
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
+arch_build: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
+# which the tools generate code
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
+arch_target: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+
+# Rest of the settings are "host" settings:
+# - For native building/cross building: Where the library/program will run.
+# - For building cross compilation tools: Where the cross compiler will run.
+os:
+    Windows:
+        subsystem: [None, cygwin, msys, msys2, wsl]
+    WindowsStore:
+        version: ["8.1", "10.0"]
+    Linux:
+    Macos:
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
+    Android:
+        api_level: ANY
+    iOS:
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    watchOS:
+        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1"]
+    tvOS:
+        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    FreeBSD:
+    SunOS:
+    Arduino:
+        board: ANY
+arch: [x86, x86_64, ppc32, ppc64le, ppc64, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+compiler:
+    sun-cc:
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
+        threads: [None, posix]
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
+    gcc:
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
+                  "6", "6.1", "6.2", "6.3", "6.4",
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1", "8.2"]
+        libcxx: [libstdc++, libstdc++11]
+        threads: [None, posix, win32] #  Windows MinGW
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+    Visual Studio:
+        runtime: [MD, MT, MTd, MDd]
+        version: ["8", "9", "10", "11", "12", "14", "15"]
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2]
+    clang:
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
+                  "5.0", "6.0", "7.0",
+                  "8"]
+        libcxx: [libstdc++, libstdc++11, libc++]
+    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"]
+        libcxx: [libstdc++, libc++]
+
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+"""
+
+settings_1_12_1 = settings_1_12_0
+settings_1_12_2 = settings_1_12_1
+settings_1_12_3 = settings_1_12_2
+settings_1_13_0 = """
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
+arch_build: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
+# which the tools generate code
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
+arch_target: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+
+# Rest of the settings are "host" settings:
+# - For native building/cross building: Where the library/program will run.
+# - For building cross compilation tools: Where the cross compiler will run.
+os:
+    Windows:
+        subsystem: [None, cygwin, msys, msys2, wsl]
+    WindowsStore:
+        version: ["8.1", "10.0"]
+    Linux:
+    Macos:
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
+    Android:
+        api_level: ANY
+    iOS:
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    watchOS:
+        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1"]
+    tvOS:
+        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    FreeBSD:
+    SunOS:
+    Arduino:
+        board: ANY
+arch: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr]
+compiler:
+    sun-cc:
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
+        threads: [None, posix]
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
+    gcc:
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
+                  "6", "6.1", "6.2", "6.3", "6.4",
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1", "8.2"]
+        libcxx: [libstdc++, libstdc++11]
+        threads: [None, posix, win32] #  Windows MinGW
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+    Visual Studio:
+        runtime: [MD, MT, MTd, MDd]
+        version: ["8", "9", "10", "11", "12", "14", "15", "16"]
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142]
+    clang:
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
+                  "5.0", "6.0", "7.0",
+                  "8"]
+        libcxx: [libstdc++, libstdc++11, libc++]
+    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"]
+        libcxx: [libstdc++, libc++]
+
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+"""
+
+settings_1_13_1 = settings_1_13_0
+settings_1_13_2 = settings_1_13_1
+settings_1_13_3 = settings_1_13_2
+settings_1_14_0 = """
+# Only for cross building, 'os_build/arch_build' is the system that runs Conan
+os_build: [Windows, WindowsStore, Linux, Macos, FreeBSD, SunOS]
+arch_build: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+
+# Only for building cross compilation tools, 'os_target/arch_target' is the system for
+# which the tools generate code
+os_target: [Windows, Linux, Macos, Android, iOS, watchOS, tvOS, FreeBSD, SunOS, Arduino]
+arch_target: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+
+# Rest of the settings are "host" settings:
+# - For native building/cross building: Where the library/program will run.
+# - For building cross compilation tools: Where the cross compiler will run.
+os:
+    Windows:
+        subsystem: [None, cygwin, msys, msys2, wsl]
+    WindowsStore:
+        version: ["8.1", "10.0"]
+    Linux:
+    Macos:
+        version: [None, "10.6", "10.7", "10.8", "10.9", "10.10", "10.11", "10.12", "10.13", "10.14"]
+    Android:
+        api_level: ANY
+    iOS:
+        version: ["7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    watchOS:
+        version: ["4.0", "4.1", "4.2", "4.3", "5.0", "5.1"]
+    tvOS:
+        version: ["11.0", "11.1", "11.2", "11.3", "11.4", "12.0", "12.1"]
+    FreeBSD:
+    SunOS:
+    Arduino:
+        board: ANY
+arch: [x86, x86_64, ppc32, ppc64le, ppc64, armv5el, armv5hf, armv6, armv7, armv7hf, armv7s, armv7k, armv8, armv8_32, armv8.3, sparc, sparcv9, mips, mips64, avr, s390, s390x]
+compiler:
+    sun-cc:
+        version: ["5.10", "5.11", "5.12", "5.13", "5.14"]
+        threads: [None, posix]
+        libcxx: [libCstd, libstdcxx, libstlport, libstdc++]
+    gcc:
+        version: ["4.1", "4.4", "4.5", "4.6", "4.7", "4.8", "4.9",
+                  "5", "5.1", "5.2", "5.3", "5.4", "5.5",
+                  "6", "6.1", "6.2", "6.3", "6.4",
+                  "7", "7.1", "7.2", "7.3",
+                  "8", "8.1", "8.2"]
+        libcxx: [libstdc++, libstdc++11]
+        threads: [None, posix, win32] #  Windows MinGW
+        exception: [None, dwarf2, sjlj, seh] # Windows MinGW
+    Visual Studio:
+        runtime: [MD, MT, MTd, MDd]
+        version: ["8", "9", "10", "11", "12", "14", "15", "16"]
+        toolset: [None, v90, v100, v110, v110_xp, v120, v120_xp,
+                  v140, v140_xp, v140_clang_c2, LLVM-vs2012, LLVM-vs2012_xp,
+                  LLVM-vs2013, LLVM-vs2013_xp, LLVM-vs2014, LLVM-vs2014_xp,
+                  LLVM-vs2017, LLVM-vs2017_xp, v141, v141_xp, v141_clang_c2, v142]
+    clang:
+        version: ["3.3", "3.4", "3.5", "3.6", "3.7", "3.8", "3.9", "4.0",
+                  "5.0", "6.0", "7.0",
+                  "8"]
+        libcxx: [libstdc++, libstdc++11, libc++]
+    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"]
+        libcxx: [libstdc++, libc++]
+
+build_type: [None, Debug, Release, RelWithDebInfo, MinSizeRel]
+cppstd: [None, 98, gnu98, 11, gnu11, 14, gnu14, 17, gnu17, 20, gnu20]
+"""
+
+settings_1_14_1 = settings_1_14_0
+settings_1_14_2 = settings_1_14_1
+settings_1_14_3 = settings_1_14_2
+settings_1_14_4 = settings_1_14_3
diff --git a/conans/test/functional/old/test_migrations.py b/conans/test/functional/old/test_migrations.py
index bbb08d95049..99208c1285c 100644
--- a/conans/test/functional/old/test_migrations.py
+++ b/conans/test/functional/old/test_migrations.py
@@ -2,11 +2,13 @@
 import os
 import unittest
 
+from nose.plugins.skip import Skip
 from six import StringIO
 
 from conans import __version__
 from conans.client.migrations import migrate_plugins_to_hooks, migrate_to_default_profile
 from conans.client.output import ConanOutput
+from conans.client.tools.version import Version
 from conans.migrations import CONAN_VERSION
 from conans.model.ref import ConanFileReference
 from conans.test.utils.conanfile import TestConanFile
@@ -17,6 +19,20 @@
 
 class TestMigrations(unittest.TestCase):
 
+    def is_there_var_for_settings_previous_version_test(self):
+        from conans import __version__ as current_version
+
+        tmp = Version(current_version)
+        if tmp.minor == 0:
+            return unittest.skip("2.0, this will make sense for 2.1")
+
+        previous_version = "{}.{}".format(tmp.major, int(tmp.minor) - 1)
+
+        from conans.client import migrations_settings
+        var_name = "settings_{}".format(previous_version.replace(".", "_"))
+        self.assertTrue(any([i for i in dir(migrations_settings) if i.startswith(var_name)]),
+                        "Introduce the previous settings.yml file in the 'migrations_settings.yml")
+
     def test_migrate_revision_metadata(self):
         # https://github.com/conan-io/conan/issues/4898
         client = TestClient()


Code review comments:
diff_hunk: @@ -17,6 +19,20 @@
 
 class TestMigrations(unittest.TestCase):
 
+    def is_there_var_for_settings_previous_version_test(self):
+        from conans import __version__ as current_version
+
+        tmp = Version(current_version)
+        if tmp.minor == 0:
+            return unittest.skip("2.0, this will make sense for 2.1")
+
+        previous_version = "{}.{}".format(tmp.major, int(tmp.minor) - 1)
path: conans/test/functional/old/test_migrations.py
text: @author:
Todo: improve with detection of previous patch too when patch > 0

diff_hunk: @@ -25,33 +26,49 @@ def __init__(self, cache, current_version, out):
         super(ClientMigrator, self).__init__(cache.conan_folder, cache.store,
                                              current_version, out)
 
-    def _update_settings_yml(self, old_settings):
+    def _update_settings_yml(self, old_version):
+
         from conans.client.conf import default_settings_yml
         settings_path = self.cache.settings_path
         if not os.path.exists(settings_path):
             self.out.warn("Migration: This conan installation doesn't have settings yet")
             self.out.warn("Nothing to migrate here, settings will be generated automatically")
             return
 
-        current_settings = load(self.cache.settings_path)
-        if current_settings != default_settings_yml:
-            self.out.warn("Migration: Updating settings.yml")
-            if current_settings != old_settings:
-                new_path = self.cache.settings_path + ".new"
-                save(new_path, default_settings_yml)
-                self.out.warn("*" * 40)
-                self.out.warn("settings.yml is locally modified, can't be updated")
-                self.out.warn("The new settings.yml has been stored in: %s" % new_path)
-                self.out.warn("*" * 40)
+        var_name = "settings_{}".format(old_version.replace(".", "_"))
+
+        def save_new():
+            new_path = self.cache.settings_path + ".new"
+            save(new_path, default_settings_yml)
+            self.out.warn("*" * 40)
+            self.out.warn("settings.yml is locally modified, can't be updated")
+            self.out.warn("The new settings.yml has been stored in: %s" % new_path)
+            self.out.warn("*" * 40)
+
+        self.out.warn("Migration: Updating settings.yml")
+        if hasattr(migrations_settings, var_name):
+            version_default_contents = getattr(migrations_settings, var_name)
+            if version_default_contents != default_settings_yml:
+                current_settings = load(self.cache.settings_path)
+                if current_settings != version_default_contents:
+                    save_new()
+                else:
+                    save(self.cache.settings_path, default_settings_yml)
             else:
-                save(self.cache.settings_path, default_settings_yml)
+                self.out.warn("Migration: Settings already up to date")
path: conans/client/migrations.py
text: @user1:
Why a warning?