Repository: conan-io/conan
Pull request #12870 (2023-01-09 11:27:02)

AutotoolsToolchain: pass valid GNU triplet when cross-building
Changelog: Bugfix: Fix bug to pass a valid GNU triplet when using AutotoolsToolchain and cross-building on Windows.
Docs: Omit

Close: https://github.com/conan-io/conan/issues/7460

### Notes:
This targets the 1.x develop branch - the public `tools.get_gnu_triplet` from the legacy tools remains untouched, while this PR only modifies the private implementation used by the AutotoolsToolchain.

### Context:
The current implementation of the _private_ `_get_gnu_triplet` returns `x86_64-windows-msvc`. This results in errors when we are cross-building on Windows, since this is not a valid GNU triplet:
```
config.sub x86-64-windows-msvc    
Invalid configuration `x86-64-windows-msvc': OS `msvc' not recognized
```

Specifically for Autotools, the triplet system type is of the format specified [here](https://www.gnu.org/software/autoconf/manual/autoconf-2.68/html_node/System-Type.html#System-Type), where we can have:
* `cpu-company-os`, or
* `cpu-company-kernel-os`

In this context, `msvc` is not a valid OS. There is no current convention in GNU for a proper target when the OS is Windows, and the C/C++ runtime are provided by Visual C++, this PR falls back to a generic `{arch}-unknown-windows`, where windows is a valid OS. This is the a very generic fallback - while  `x86-64-w64-mingw32` may be preferred in some contexts, we don't have enough context to assume it (but could be done in a separate flag), and when Visual C++ with the Microsoft Visual C++ runtime is used, the binaries produced would not strictly be mingw32 binaries.






Patch:
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index dacf021ef..11a443a62 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -10,16 +10,24 @@ from conans.util.files import save, load
 
 
 def _build_preset(conanfile, multiconfig):
+    return _common_build_and_test_preset_fields(conanfile, multiconfig)
+
+
+def _test_preset(conanfile, multiconfig):
+    return _common_build_and_test_preset_fields(conanfile, multiconfig)
+
+
+def _common_build_and_test_preset_fields(conanfile, multiconfig):
     build_type = conanfile.settings.get_safe("build_type")
     configure_preset_name = _configure_preset_name(conanfile, multiconfig)
-    ret = {"name": _build_preset_name(conanfile),
+    ret = {"name": _build_and_test_preset_name(conanfile),
            "configurePreset": configure_preset_name}
     if multiconfig:
         ret["configuration"] = build_type
     return ret
 
 
-def _build_preset_name(conanfile):
+def _build_and_test_preset_name(conanfile):
     build_type = conanfile.settings.get_safe("build_type")
     custom_conf, user_defined_build = get_build_folder_custom_vars(conanfile)
     if user_defined_build:
@@ -108,6 +116,14 @@ def _configure_preset(conanfile, generator, cache_variables, toolchain_file, mul
     return ret
 
 
+def _insert_preset(data, preset_name, preset):
+    position = _get_already_existing_preset_index(preset["name"], data.setdefault(preset_name, []))
+    if position is not None:
+        data[preset_name][position] = preset
+    else:
+        data[preset_name].append(preset)
+
+
 def _forced_schema_2(conanfile):
     version = conanfile.conf.get("tools.cmake.cmaketoolchain.presets:max_schema_version",
                                  check_type=int)
@@ -143,6 +159,7 @@ def _contents(conanfile, toolchain_file, cache_variables, generator):
           }
     multiconfig = is_multi_configuration(generator)
     ret["buildPresets"].append(_build_preset(conanfile, multiconfig))
+    ret["testPresets"].append(_test_preset(conanfile, multiconfig))
     _conf = _configure_preset(conanfile, generator, cache_variables, toolchain_file, multiconfig)
     ret["configurePresets"].append(_conf)
     return ret
@@ -174,20 +191,14 @@ def write_cmake_presets(conanfile, toolchain_file, generator, cache_variables,
     if os.path.exists(preset_path):
         data = json.loads(load(preset_path))
         build_preset = _build_preset(conanfile, multiconfig)
-        position = _get_already_existing_preset_index(build_preset["name"], data["buildPresets"])
-        if position is not None:
-            data["buildPresets"][position] = build_preset
-        else:
-            data["buildPresets"].append(build_preset)
+        _insert_preset(data, "buildPresets", build_preset)
+
+        test_preset = _test_preset(conanfile, multiconfig)
+        _insert_preset(data, "testPresets", test_preset)
 
         configure_preset = _configure_preset(conanfile, generator, cache_variables, toolchain_file,
                                              multiconfig)
-        position = _get_already_existing_preset_index(configure_preset["name"],
-                                                      data["configurePresets"])
-        if position is not None:
-            data["configurePresets"][position] = configure_preset
-        else:
-            data["configurePresets"].append(configure_preset)
+        _insert_preset(data, "configurePresets", configure_preset)
     else:
         data = _contents(conanfile, toolchain_file, cache_variables, generator)
 
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 5846f0097..60d9e5759 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -215,6 +215,21 @@ class ArchitectureBlock(Block):
         return {"arch_flag": arch_flag}
 
 
+class LinkerScriptsBlock(Block):
+    template = textwrap.dedent("""
+        string(APPEND CONAN_EXE_LINKER_FLAGS {{ linker_script_flags }})
+        """)
+
+    def context(self):
+        linker_scripts = self._conanfile.conf.get(
+            "tools.build:linker_scripts", check_type=list, default=[])
+        if not linker_scripts:
+            return
+        linker_scripts = [linker_script.replace('\\', '/') for linker_script in linker_scripts]
+        linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts]
+        return {"linker_script_flags": " ".join(linker_script_flags)}
+
+
 class CppStdBlock(Block):
     template = textwrap.dedent("""
         message(STATUS "Conan toolchain: C++ Standard {{ cppstd }} with extensions {{ cppstd_extensions }}")
diff --git a/conan/tools/cmake/toolchain/toolchain.py b/conan/tools/cmake/toolchain/toolchain.py
index 2a0e181f8..0bfb04bff 100644
--- a/conan/tools/cmake/toolchain/toolchain.py
+++ b/conan/tools/cmake/toolchain/toolchain.py
@@ -12,7 +12,7 @@ from conan.tools.cmake.toolchain import CONAN_TOOLCHAIN_FILENAME
 from conan.tools.cmake.toolchain.blocks import ToolchainBlocks, UserToolchain, GenericSystemBlock, \
     AndroidSystemBlock, AppleSystemBlock, FPicBlock, ArchitectureBlock, GLibCXXBlock, VSRuntimeBlock, \
     CppStdBlock, ParallelBlock, CMakeFlagsInitBlock, TryCompileBlock, FindFiles, PkgConfigBlock, \
-    SkipRPath, SharedLibBock, OutputDirsBlock, ExtraFlagsBlock, CompilersBlock
+    SkipRPath, SharedLibBock, OutputDirsBlock, ExtraFlagsBlock, CompilersBlock, LinkerScriptsBlock
 from conan.tools.intel import IntelCC
 from conan.tools.microsoft import VCVars
 from conan.tools.microsoft.visual import vs_ide_version
@@ -133,6 +133,7 @@ class CMakeToolchain(object):
                                        ("apple_system", AppleSystemBlock),
                                        ("fpic", FPicBlock),
                                        ("arch_flags", ArchitectureBlock),
+                                       ("linker_scripts", LinkerScriptsBlock),
                                        ("libcxx", GLibCXXBlock),
                                        ("vs_runtime", VSRuntimeBlock),
                                        ("cppstd", CppStdBlock),
diff --git a/conan/tools/env/environment.py b/conan/tools/env/environment.py
index edc229083..1745ecc55 100644
--- a/conan/tools/env/environment.py
+++ b/conan/tools/env/environment.py
@@ -302,22 +302,22 @@ class EnvVars:
             os.environ.update(old_env)
 
     def save_bat(self, file_location, generate_deactivate=True):
-        filepath, filename = os.path.split(file_location)
-        deactivate_file = os.path.join(filepath, "deactivate_{}".format(filename))
+        _, filename = os.path.split(file_location)
+        deactivate_file = "deactivate_{}".format(filename)
         deactivate = textwrap.dedent("""\
             setlocal
-            echo @echo off > "{deactivate_file}"
-            echo echo Restoring environment >> "{deactivate_file}"
+            echo @echo off > "%~dp0/{deactivate_file}"
+            echo echo Restoring environment >> "%~dp0/{deactivate_file}"
             for %%v in ({vars}) do (
                 set foundenvvar=
                 for /f "delims== tokens=1,2" %%a in ('set') do (
                     if /I "%%a" == "%%v" (
-                        echo set "%%a=%%b">> "{deactivate_file}"
+                        echo set "%%a=%%b">> "%~dp0/{deactivate_file}"
                         set foundenvvar=1
                     )
                 )
                 if not defined foundenvvar (
-                    echo set %%v=>> "{deactivate_file}"
+                    echo set %%v=>> "%~dp0/{deactivate_file}"
                 )
             )
             endlocal
@@ -335,9 +335,10 @@ class EnvVars:
         save(file_location, content)
 
     def save_ps1(self, file_location, generate_deactivate=True,):
-        filepath, filename = os.path.split(file_location)
-        deactivate_file = os.path.join(filepath, "deactivate_{}".format(filename))
+        _, filename = os.path.split(file_location)
+        deactivate_file = "deactivate_{}".format(filename)
         deactivate = textwrap.dedent("""\
+            Push-Location $PSScriptRoot
             "echo `"Restoring environment`"" | Out-File -FilePath "{deactivate_file}"
             $vars = (Get-ChildItem env:*).name
             $updated_vars = @({vars})
@@ -354,6 +355,7 @@ class EnvVars:
                     Add-Content "{deactivate_file}" "`nif (Test-Path env:$var) {{ Remove-Item env:$var }}"
                 }}
             }}
+            Pop-Location
         """).format(
             deactivate_file=deactivate_file,
             vars=",".join(['"{}"'.format(var) for var in self._values.keys()])
diff --git a/conan/tools/files/symlinks/symlinks.py b/conan/tools/files/symlinks/symlinks.py
index 98c4b2040..7700e8713 100644
--- a/conan/tools/files/symlinks/symlinks.py
+++ b/conan/tools/files/symlinks/symlinks.py
@@ -37,7 +37,7 @@ def remove_external_symlinks(conanfile, base_folder=None):
     for fullpath in get_symlinks(base_folder):
         link_target = os.readlink(fullpath)
         if not os.path.isabs(link_target):
-            link_target = os.path.join(base_folder, link_target)
+            link_target = os.path.join(os.path.dirname(fullpath), link_target)
         if not _path_inside(base_folder, link_target):
             os.unlink(fullpath)
 
@@ -47,6 +47,6 @@ def remove_broken_symlinks(conanfile, base_folder=None):
     for fullpath in get_symlinks(base_folder):
         link_target = os.readlink(fullpath)
         if not os.path.isabs(link_target):
-            link_target = os.path.join(base_folder, link_target)
+            link_target = os.path.join(os.path.dirname(fullpath), link_target)
         if not os.path.exists(link_target):
             os.unlink(fullpath)
diff --git a/conan/tools/gnu/autotoolstoolchain.py b/conan/tools/gnu/autotoolstoolchain.py
index 58115a607..0a8914df4 100644
--- a/conan/tools/gnu/autotoolstoolchain.py
+++ b/conan/tools/gnu/autotoolstoolchain.py
@@ -113,6 +113,8 @@ class AutotoolsToolchain:
                                               check_type=list)
         conf_flags.extend(self._conanfile.conf.get("tools.build:exelinkflags", default=[],
                                                    check_type=list))
+        linker_scripts = self._conanfile.conf.get("tools.build:linker_scripts", default=[], check_type=list)
+        conf_flags.extend(["-T'" + linker_script + "'" for linker_script in linker_scripts])
         ret = ret + apple_flags + conf_flags + self.build_type_link_flags + self.extra_ldflags
         return self._filter_list_empty_fields(ret)
 
diff --git a/conan/tools/gnu/get_gnu_triplet.py b/conan/tools/gnu/get_gnu_triplet.py
index c082f3007..d25e8c506 100644
--- a/conan/tools/gnu/get_gnu_triplet.py
+++ b/conan/tools/gnu/get_gnu_triplet.py
@@ -68,7 +68,7 @@ def _get_gnu_triplet(os_, arch, compiler=None):
     if compiler == "gcc":
         windows_op = "w64-mingw32"
     elif compiler == "Visual Studio":
-        windows_op = "windows-msvc"
+        windows_op = "unknown-windows"
     else:
         windows_op = "windows"
 
diff --git a/conan/tools/meson/toolchain.py b/conan/tools/meson/toolchain.py
index 17f553242..be0a53593 100644
--- a/conan/tools/meson/toolchain.py
+++ b/conan/tools/meson/toolchain.py
@@ -291,10 +291,12 @@ class MesonToolchain(object):
         cflags = self._conanfile.conf.get("tools.build:cflags", default=[], check_type=list)
         sharedlinkflags = self._conanfile.conf.get("tools.build:sharedlinkflags", default=[], check_type=list)
         exelinkflags = self._conanfile.conf.get("tools.build:exelinkflags", default=[], check_type=list)
+        linker_scripts = self._conanfile.conf.get("tools.build:linker_scripts", default=[], check_type=list)
+        linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts]
         return {
             "cxxflags": cxxflags,
             "cflags": cflags,
-            "ldflags": sharedlinkflags + exelinkflags
+            "ldflags": sharedlinkflags + exelinkflags + linker_script_flags
         }
 
     @staticmethod
diff --git a/conan/tools/microsoft/visual.py b/conan/tools/microsoft/visual.py
index 882b867e5..5050ee8b8 100644
--- a/conan/tools/microsoft/visual.py
+++ b/conan/tools/microsoft/visual.py
@@ -8,7 +8,7 @@ from conans.errors import ConanException, ConanInvalidConfiguration
 CONAN_VCVARS_FILE = "conanvcvars.bat"
 
 
-def check_min_vs(conanfile, version):
+def check_min_vs(conanfile, version, raise_invalid=True):
     """ this is a helper method to allow the migration of 1.X->2.0 and VisualStudio->msvc settings
     withoug breaking recipes
     The legacy "Visual Studio" with different toolset is not managed, not worth the complexity
@@ -30,9 +30,13 @@ def check_min_vs(conanfile, version):
             compiler_version += ".{}".format(compiler_update)
 
     if compiler_version and Version(compiler_version) < version:
-        msg = "This package doesn't work with VS compiler version '{}'" \
-              ", it requires at least '{}'".format(compiler_version, version)
-        raise ConanInvalidConfiguration(msg)
+        if raise_invalid:
+            msg = f"This package doesn't work with VS compiler version '{compiler_version}'" \
+                  f", it requires at least '{version}'"
+            raise ConanInvalidConfiguration(msg)
+        else:
+            return False
+    return True
 
 
 def msvc_version_to_vs_ide_version(version):
@@ -72,6 +76,10 @@ class VCVars:
         if compiler not in ("Visual Studio", "msvc", "clang"):
             return
 
+        vs_install_path = conanfile.conf.get("tools.microsoft.msbuild:installation_path")
+        if vs_install_path == "":  # Empty string means "disable"
+            return
+
         if compiler == "clang":
             # The vcvars only needed for LLVM/Clang and VS ClangCL, who define runtime
             if not conanfile.settings.get_safe("compiler.runtime"):
@@ -93,7 +101,6 @@ class VCVars:
             vcvars_ver = _vcvars_vers(conanfile, compiler, vs_version)
         vcvarsarch = vcvars_arch(conanfile)
 
-        vs_install_path = conanfile.conf.get("tools.microsoft.msbuild:installation_path")
         # The vs_install_path is like
         # C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
         # C:\Program Files (x86)\Microsoft Visual Studio\2017\Community
diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py
index dad8cdc36..3413e98d0 100644
--- a/conans/client/generators/__init__.py
+++ b/conans/client/generators/__init__.py
@@ -291,12 +291,16 @@ def _generate_aggregated_env(conanfile):
         ps1s = []
         for env_script in env_scripts:
             path = os.path.join(conanfile.generators_folder, env_script)
+            # Only the .bat and .ps1 are made relative to current script
             if env_script.endswith(".bat"):
-                bats.append(path)
+                path = os.path.relpath(path, conanfile.generators_folder)
+                bats.append("%~dp0/"+path)
             elif env_script.endswith(".sh"):
                 shs.append(subsystem_path(subsystem, path))
             elif env_script.endswith(".ps1"):
-                ps1s.append(path)
+                path = os.path.relpath(path, conanfile.generators_folder)
+                # This $PSScriptRoot uses the current script directory
+                ps1s.append("$PSScriptRoot/"+path)
         if shs:
             def sh_content(files):
                 return ". " + " && . ".join('"{}"'.format(s) for s in files)
diff --git a/conans/model/conf.py b/conans/model/conf.py
index 62f91da8d..fcad44d96 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -35,7 +35,7 @@ BUILT_IN_CONFS = {
     "tools.microsoft.msbuild:verbosity": "Verbosity level for MSBuild: 'Quiet', 'Minimal', 'Normal', 'Detailed', 'Diagnostic'",
     "tools.microsoft.msbuild:vs_version": "Defines the IDE version when using the new msvc compiler",
     "tools.microsoft.msbuild:max_cpu_count": "Argument for the /m when running msvc to build parallel projects",
-    "tools.microsoft.msbuild:installation_path": "VS install path, to avoid auto-detect via vswhere, like C:/Program Files (x86)/Microsoft Visual Studio/2019/Community",
+    "tools.microsoft.msbuild:installation_path": "VS install path, to avoid auto-detect via vswhere, like C:/Program Files (x86)/Microsoft Visual Studio/2019/Community. Use empty string to disable",
     "tools.microsoft.msbuilddeps:exclude_code_analysis": "Suppress MSBuild code analysis for patterns",
     "tools.microsoft.msbuildtoolchain:compile_options": "Dictionary with MSBuild compiler options",
     "tools.intel:installation_path": "Defines the Intel oneAPI installation root path",
@@ -54,6 +54,7 @@ BUILT_IN_CONFS = {
     "tools.build:sharedlinkflags": "List of extra flags used by CMakeToolchain for CMAKE_SHARED_LINKER_FLAGS_INIT variable",
     "tools.build:exelinkflags": "List of extra flags used by CMakeToolchain for CMAKE_EXE_LINKER_FLAGS_INIT variable",
     "tools.build:compiler_executables": "Defines a Python dict-like with the compilers path to be used. Allowed keys {'c', 'cpp', 'cuda', 'objc', 'objcpp', 'rc', 'fortran', 'asm', 'hip', 'ispc'}",
+    "tools.build:linker_scripts": "List of linker script files to pass to the linker used by different toolchains like CMakeToolchain, AutotoolsToolchain, and MesonToolchain",
     "tools.microsoft.bash:subsystem": "Set subsystem to use for Windows. Possible values: 'msys2', 'msys', 'cygwin', 'wsl' and 'sfu'",
     "tools.microsoft.bash:path": "Path to the shell executable. Default: 'bash'",
     "tools.apple:sdk_path": "Path for the sdk location. This value will be passed as SDKROOT or -isysroot depending on the generator used",


Test patch:
diff --git a/conans/test/functional/command/test_command_test.py b/conans/test/functional/command/test_command_test.py
index 970e50aaf..c34fddfff 100644
--- a/conans/test/functional/command/test_command_test.py
+++ b/conans/test/functional/command/test_command_test.py
@@ -1,4 +1,5 @@
 import os
+import platform
 import time
 import unittest
 
@@ -26,5 +27,6 @@ class ConanTestTest(unittest.TestCase):
         client.run("test test_package hello/0.1@lasote/stable -s hello:build_type=Debug "
                    "--build missing")
         self.assertIn('hello/0.1: Hello World Debug!', client.out)
+        subfolder = "Release" if platform.system() != "Windows" else ""
         assert os.path.exists(os.path.join(client.current_folder, "test_package",
-                                           "build", "generators", "conaninfo.txt"))
+                                           "build", subfolder, "generators", "conaninfo.txt"))
diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
index dbd70681e..e172d3927 100644
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py
@@ -594,9 +594,12 @@ def test_cmake_presets_multiple_settings_single_config():
     presets = json.loads(load(user_presets["include"][0]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
     assert presets["buildPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
     assert presets["buildPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
+    assert presets["testPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
+    assert presets["testPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
 
     # If we create the "Debug" one, it has the same toolchain and preset file, that is
     # always multiconfig
@@ -608,12 +611,17 @@ def test_cmake_presets_multiple_settings_single_config():
     presets = json.loads(load(user_presets["include"][0]))
     assert len(presets["configurePresets"]) == 2
     assert len(presets["buildPresets"]) == 2
+    assert len(presets["testPresets"]) == 2
     assert presets["configurePresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
     assert presets["configurePresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
     assert presets["buildPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
     assert presets["buildPresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
     assert presets["buildPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
     assert presets["buildPresets"][1]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["testPresets"][0]["name"] == "apple-clang-12.0-gnu17-release"
+    assert presets["testPresets"][1]["name"] == "apple-clang-12.0-gnu17-debug"
+    assert presets["testPresets"][0]["configurePreset"] == "apple-clang-12.0-gnu17-release"
+    assert presets["testPresets"][1]["configurePreset"] == "apple-clang-12.0-gnu17-debug"
 
     # But If we change, for example, the cppstd and the compiler version, the toolchain
     # and presets will be different, but it will be appended to the UserPresets.json
@@ -629,26 +637,32 @@ def test_cmake_presets_multiple_settings_single_config():
     presets = json.loads(load(user_presets["include"][1]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "apple-clang-13-gnu20-release"
     assert presets["buildPresets"][0]["name"] == "apple-clang-13-gnu20-release"
     assert presets["buildPresets"][0]["configurePreset"] == "apple-clang-13-gnu20-release"
+    assert presets["testPresets"][0]["name"] == "apple-clang-13-gnu20-release"
+    assert presets["testPresets"][0]["configurePreset"] == "apple-clang-13-gnu20-release"
 
     # We can build with cmake manually
     if platform.system() == "Darwin":
         client.run_command("cmake . --preset apple-clang-12.0-gnu17-release")
         client.run_command("cmake --build --preset apple-clang-12.0-gnu17-release")
+        client.run_command("ctest --preset apple-clang-12.0-gnu17-release")
         client.run_command("./build/apple-clang-12.0-gnu17/Release/hello")
         assert "Hello World Release!" in client.out
         assert "__cplusplus2017" in client.out
 
         client.run_command("cmake . --preset apple-clang-12.0-gnu17-debug")
         client.run_command("cmake --build --preset apple-clang-12.0-gnu17-debug")
+        client.run_command("ctest --preset apple-clang-12.0-gnu17-debug")
         client.run_command("./build/apple-clang-12.0-gnu17/Debug/hello")
         assert "Hello World Debug!" in client.out
         assert "__cplusplus2017" in client.out
 
         client.run_command("cmake . --preset apple-clang-13-gnu20-release")
         client.run_command("cmake --build --preset apple-clang-13-gnu20-release")
+        client.run_command("ctest --preset apple-clang-13-gnu20-release")
         client.run_command("./build/apple-clang-13-gnu20/Release/hello")
         assert "Hello World Release!" in client.out
         assert "__cplusplus2020" in client.out
@@ -672,6 +686,7 @@ def test_cmake_presets_duplicated_install(multiconfig):
     assert os.path.exists(presets_path)
     contents = json.loads(load(presets_path))
     assert len(contents["buildPresets"]) == 1
+    assert len(contents["testPresets"]) == 1
 
 
 def test_remove_missing_presets():
@@ -736,6 +751,7 @@ def test_cmake_presets_options_single_config():
             shared_str = "shared_true" if shared else "shared_false"
             client.run_command("cmake . --preset apple-clang-{}-release".format(shared_str))
             client.run_command("cmake --build --preset apple-clang-{}-release".format(shared_str))
+            client.run_command("ctest --preset apple-clang-{}-release".format(shared_str))
             the_lib = "libhello.a" if not shared else "libhello.dylib"
             path = os.path.join(client.current_folder,
                                 "build", "apple-clang-{}".format(shared_str), "release", the_lib)
@@ -763,9 +779,12 @@ def test_cmake_presets_multiple_settings_multi_config():
     presets = json.loads(load(user_presets["include"][0]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "dynamic-14"
     assert presets["buildPresets"][0]["name"] == "dynamic-14-release"
     assert presets["buildPresets"][0]["configurePreset"] == "dynamic-14"
+    assert presets["testPresets"][0]["name"] == "dynamic-14-release"
+    assert presets["testPresets"][0]["configurePreset"] == "dynamic-14"
 
     # If we create the "Debug" one, it has the same toolchain and preset file, that is
     # always multiconfig
@@ -777,11 +796,16 @@ def test_cmake_presets_multiple_settings_multi_config():
     presets = json.loads(load(user_presets["include"][0]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 2
+    assert len(presets["testPresets"]) == 2
     assert presets["configurePresets"][0]["name"] == "dynamic-14"
     assert presets["buildPresets"][0]["name"] == "dynamic-14-release"
     assert presets["buildPresets"][1]["name"] == "dynamic-14-debug"
     assert presets["buildPresets"][0]["configurePreset"] == "dynamic-14"
     assert presets["buildPresets"][1]["configurePreset"] == "dynamic-14"
+    assert presets["testPresets"][0]["name"] == "dynamic-14-release"
+    assert presets["testPresets"][1]["name"] == "dynamic-14-debug"
+    assert presets["testPresets"][0]["configurePreset"] == "dynamic-14"
+    assert presets["testPresets"][1]["configurePreset"] == "dynamic-14"
 
     # But If we change, for example, the cppstd and the compiler version, the toolchain
     # and presets will be different, but it will be appended to the UserPresets.json
@@ -796,19 +820,24 @@ def test_cmake_presets_multiple_settings_multi_config():
     presets = json.loads(load(user_presets["include"][1]))
     assert len(presets["configurePresets"]) == 1
     assert len(presets["buildPresets"]) == 1
+    assert len(presets["testPresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "static-17"
     assert presets["buildPresets"][0]["name"] == "static-17-release"
     assert presets["buildPresets"][0]["configurePreset"] == "static-17"
+    assert presets["testPresets"][0]["name"] == "static-17-release"
+    assert presets["testPresets"][0]["configurePreset"] == "static-17"
 
     # We can build with cmake manually
     client.run_command("cmake . --preset dynamic-14")
 
     client.run_command("cmake --build --preset dynamic-14-release")
+    client.run_command("ctest --preset dynamic-14-release")
     client.run_command("build\\dynamic-14\\Release\\hello")
     assert "Hello World Release!" in client.out
     assert "MSVC_LANG2014" in client.out
 
     client.run_command("cmake --build --preset dynamic-14-debug")
+    client.run_command("ctest --preset dynamic-14-debug")
     client.run_command("build\\dynamic-14\\Debug\\hello")
     assert "Hello World Debug!" in client.out
     assert "MSVC_LANG2014" in client.out
@@ -816,6 +845,7 @@ def test_cmake_presets_multiple_settings_multi_config():
     client.run_command("cmake . --preset static-17")
 
     client.run_command("cmake --build --preset static-17-release")
+    client.run_command("ctest --preset static-17-release")
     client.run_command("build\\static-17\\Release\\hello")
     assert "Hello World Release!" in client.out
     assert "MSVC_LANG2017" in client.out
@@ -843,10 +873,12 @@ def test_user_presets_version2():
     if platform.system() == "Windows":
         client.run_command("cmake . --preset 14")
         client.run_command("cmake --build --preset 14-release")
+        client.run_command("ctest --preset 14-release")
         client.run_command(r"build\14\Release\hello.exe")
     else:
         client.run_command("cmake . --preset 14-release")
         client.run_command("cmake --build --preset 14-release")
+        client.run_command("ctest --preset 14-release")
         client.run_command("./build/14/Release/hello")
 
     assert "Hello World Release!" in client.out
@@ -859,10 +891,12 @@ def test_user_presets_version2():
     if platform.system() == "Windows":
         client.run_command("cmake . --preset 17")
         client.run_command("cmake --build --preset 17-release")
+        client.run_command("ctest --preset 17-release")
         client.run_command(r"build\17\Release\hello.exe")
     else:
         client.run_command("cmake . --preset 17-release")
         client.run_command("cmake --build --preset 17-release")
+        client.run_command("ctest --preset 17-release")
         client.run_command("./build/17/Release/hello")
 
     assert "Hello World Release!" in client.out
@@ -949,10 +983,12 @@ def test_cmake_presets_with_conanfile_txt():
     if platform.system() != "Windows":
         c.run_command("cmake --preset debug")
         c.run_command("cmake --build --preset debug")
+        c.run_command("ctest --preset debug")
         c.run_command("./build/Debug/foo")
     else:
         c.run_command("cmake --preset default")
         c.run_command("cmake --build --preset debug")
+        c.run_command("ctest --preset debug")
         c.run_command("build\\Debug\\foo")
 
     assert "Hello World Debug!" in c.out
@@ -960,9 +996,11 @@ def test_cmake_presets_with_conanfile_txt():
     if platform.system() != "Windows":
         c.run_command("cmake --preset release")
         c.run_command("cmake --build --preset release")
+        c.run_command("ctest --preset release")
         c.run_command("./build/Release/foo")
     else:
         c.run_command("cmake --build --preset release")
+        c.run_command("ctest --preset release")
         c.run_command("build\\Release\\foo")
 
     assert "Hello World Release!" in c.out
diff --git a/conans/test/integration/cache/test_home_special_char.py b/conans/test/integration/cache/test_home_special_char.py
new file mode 100644
index 000000000..d958edda3
--- /dev/null
+++ b/conans/test/integration/cache/test_home_special_char.py
@@ -0,0 +1,58 @@
+import os
+import platform
+
+from conans.test.utils.test_files import temp_folder
+from conans.test.utils.tools import TestClient
+
+import textwrap
+
+
+def test_home_special_chars():
+    """ the path with special characters is creating a conanbuild.bat that fails
+    """
+    path_chars = "päthñç$"
+    cache_folder = os.path.join(temp_folder(), path_chars)
+    current_folder = os.path.join(temp_folder(), path_chars)
+    c = TestClient(cache_folder, current_folder)
+
+    tool = textwrap.dedent(r"""
+        import os
+        from conan import ConanFile
+        from conan.tools.files import save, chdir
+        class Pkg(ConanFile):
+            name = "mytool"
+            version = "1.0"
+            def package(self):
+                with chdir(self, self.package_folder):
+                    echo = "@echo off\necho MYTOOL WORKS!!"
+                    save(self, "bin/mytool.bat", echo)
+                    save(self, "bin/mytool.sh", echo)
+                    os.chmod("bin/mytool.sh", 0o777)
+            """)
+    c.save({"conanfile.py": tool})
+    c.run("create .")
+
+    conan_file = textwrap.dedent("""
+        import platform
+        from conan import ConanFile
+
+        class App(ConanFile):
+            name="failure"
+            version="0.1"
+            settings = 'os', 'arch', 'compiler', 'build_type'
+            generators = "VirtualBuildEnv"
+            tool_requires = "mytool/1.0"
+            def build(self):
+                mycmd = "mytool.bat" if platform.system() == "Windows" else "mytool.sh"
+                self.run(mycmd)
+        """)
+
+    c.save({"conanfile.py": conan_file})
+    # Need the 2 profile to work correctly buildenv
+    c.run("create . -s:b build_type=Release")
+    assert path_chars in c.out
+    assert "MYTOOL WORKS!!" in c.out
+    if platform.system() == "Windows":
+        c.run("create . -s:b build_type=Release -c tools.env.virtualenv:powershell=True")
+        assert path_chars in c.out
+        assert "MYTOOL WORKS!!" in c.out
diff --git a/conans/test/integration/configuration/conf/test_conf_profile.py b/conans/test/integration/configuration/conf/test_conf_profile.py
index c2c208665..0f55ad7eb 100644
--- a/conans/test/integration/configuration/conf/test_conf_profile.py
+++ b/conans/test/integration/configuration/conf/test_conf_profile.py
@@ -164,3 +164,36 @@ def test_msbuild_compile_options():
     client.run("install . -pr=myprofile")
     msbuild_tool = client.load("conantoolchain_release_x64.props")
     assert "<ExceptionHandling>Async</ExceptionHandling>" in msbuild_tool
+
+
+def test_config_package_append(client):
+    profile1 = textwrap.dedent("""\
+        [conf]
+        user.myteam:myconf=["a", "b", "c"]
+        """)
+    profile2 = textwrap.dedent("""\
+        include(profile1)
+        [conf]
+        mypkg*:user.myteam:myconf+=["d"]
+        mydep*:user.myteam:myconf=+["e"]
+        """)
+    conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        class Pkg(ConanFile):
+            def generate(self):
+                self.output.info(f"MYCONF: {self.conf.get('user.myteam:myconf')}")
+            def build(self):
+                self.output.info(f"MYCONFBUILD: {self.conf.get('user.myteam:myconf')}")
+            """)
+    client.save({"profile1": profile1,
+                 "profile2": profile2,
+                 "conanfile.py": conanfile})
+    client.run("install . mypkg/0.1@ -pr=profile2")
+    assert "conanfile.py (mypkg/0.1): MYCONF: ['a', 'b', 'c', 'd']" in client.out
+    client.run("install . mydep/0.1@ -pr=profile2")
+    assert "conanfile.py (mydep/0.1): MYCONF: ['e', 'a', 'b', 'c']" in client.out
+
+    client.run("create . mypkg/0.1@ -pr=profile2")
+    assert "mypkg/0.1: MYCONFBUILD: ['a', 'b', 'c', 'd']" in client.out
+    client.run("create . mydep/0.1@ -pr=profile2")
+    assert "mydep/0.1: MYCONFBUILD: ['e', 'a', 'b', 'c']" in client.out
diff --git a/conans/test/integration/py_requires/python_requires_test.py b/conans/test/integration/py_requires/python_requires_test.py
index c2d5bab40..174f92ad1 100644
--- a/conans/test/integration/py_requires/python_requires_test.py
+++ b/conans/test/integration/py_requires/python_requires_test.py
@@ -1020,3 +1020,103 @@ def test_multiple_reuse():
     c.run("install consumer")
     assert "VALUE COMMON: 42!!!" in c.out
     assert "VALUE TOOL: 42!!!" in c.out
+
+
+class TestTransitiveExtend:
+    # https://github.com/conan-io/conan/issues/10511
+    # https://github.com/conan-io/conan/issues/10565
+    def test_transitive_extend(self):
+        client = TestClient()
+        company = textwrap.dedent("""
+            from conan import ConanFile
+            class CompanyConanFile(ConanFile):
+                name = "company"
+                version = "1.0"
+
+                def msg1(self):
+                    return "company"
+                def msg2(self):
+                    return "company"
+            """)
+        project = textwrap.dedent("""
+            from conan import ConanFile
+            class ProjectBaseConanFile(ConanFile):
+                name = "project"
+                version = "1.0"
+
+                python_requires = "company/1.0"
+                python_requires_extend = "company.CompanyConanFile"
+
+                def msg1(self):
+                    return "project"
+            """)
+        consumer = textwrap.dedent("""
+            from conan import ConanFile
+            class Base(ConanFile):
+                name = "consumer"
+                version = "1.0"
+                python_requires = "project/1.0"
+                python_requires_extend = "project.ProjectBaseConanFile"
+                def generate(self):
+                    self.output.info("Msg1:{}!!!".format(self.msg1()))
+                    self.output.info("Msg2:{}!!!".format(self.msg2()))
+                """)
+        client.save({"company/conanfile.py": company,
+                     "project/conanfile.py": project,
+                     "consumer/conanfile.py": consumer})
+        client.run("export company")
+        client.run("export project")
+        client.run("install consumer")
+        assert "conanfile.py (consumer/1.0): Msg1:project!!!" in client.out
+        assert "conanfile.py (consumer/1.0): Msg2:company!!!" in client.out
+
+    def test_transitive_extend2(self):
+        client = TestClient()
+        company = textwrap.dedent("""
+            from conan import ConanFile
+            class CompanyConanFile(ConanFile):
+                name = "company"
+                version = "1.0"
+
+            class CompanyBase:
+                def msg1(self):
+                    return "company"
+                def msg2(self):
+                    return "company"
+            """)
+        project = textwrap.dedent("""
+            from conan import ConanFile
+            class ProjectBase:
+                def msg1(self):
+                    return "project"
+
+            class ProjectBaseConanFile(ConanFile):
+                name = "project"
+                version = "1.0"
+                python_requires = "company/1.0"
+
+                def init(self):
+                    pkg_name, base_class_name = "company", "CompanyBase"
+                    base_class = getattr(self.python_requires[pkg_name].module, base_class_name)
+                    global ProjectBase
+                    ProjectBase = type('ProjectBase', (ProjectBase, base_class, object), {})
+            """)
+        consumer = textwrap.dedent("""
+            from conan import ConanFile
+            class Base(ConanFile):
+                name = "consumer"
+                version = "1.0"
+                python_requires = "project/1.0"
+                python_requires_extend = "project.ProjectBase"
+                def generate(self):
+                    self.output.info("Msg1:{}!!!".format(self.msg1()))
+                    self.output.info("Msg2:{}!!!".format(self.msg2()))
+                """)
+        client.save({"company/conanfile.py": company,
+                     "project/conanfile.py": project,
+                     "consumer/conanfile.py": consumer})
+        client.run("export company")
+        client.run("export project")
+        client.run("install consumer")
+        assert "conanfile.py (consumer/1.0): Msg1:project!!!" in client.out
+        assert "conanfile.py (consumer/1.0): Msg2:company!!!" in client.out
diff --git a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index 38a793f42..7f2bac7fb 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -401,12 +401,17 @@ def test_cmake_presets_multiconfig():
     presets = json.loads(client.load("CMakePresets.json"))
     assert len(presets["buildPresets"]) == 1
     assert presets["buildPresets"][0]["configuration"] == "Release"
+    assert len(presets["testPresets"]) == 1
+    assert presets["testPresets"][0]["configuration"] == "Release"
 
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
     assert len(presets["buildPresets"]) == 2
     assert presets["buildPresets"][0]["configuration"] == "Release"
     assert presets["buildPresets"][1]["configuration"] == "Debug"
+    assert len(presets["testPresets"]) == 2
+    assert presets["testPresets"][0]["configuration"] == "Release"
+    assert presets["testPresets"][1]["configuration"] == "Debug"
 
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=RelWithDebInfo "
                "--profile:h=profile")
@@ -417,6 +422,11 @@ def test_cmake_presets_multiconfig():
     assert presets["buildPresets"][1]["configuration"] == "Debug"
     assert presets["buildPresets"][2]["configuration"] == "RelWithDebInfo"
     assert presets["buildPresets"][3]["configuration"] == "MinSizeRel"
+    assert len(presets["testPresets"]) == 4
+    assert presets["testPresets"][0]["configuration"] == "Release"
+    assert presets["testPresets"][1]["configuration"] == "Debug"
+    assert presets["testPresets"][2]["configuration"] == "RelWithDebInfo"
+    assert presets["testPresets"][3]["configuration"] == "MinSizeRel"
 
     # Repeat one
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
@@ -428,6 +438,12 @@ def test_cmake_presets_multiconfig():
     assert presets["buildPresets"][2]["configuration"] == "RelWithDebInfo"
     assert presets["buildPresets"][3]["configuration"] == "MinSizeRel"
 
+    assert len(presets["testPresets"]) == 4
+    assert presets["testPresets"][0]["configuration"] == "Release"
+    assert presets["testPresets"][1]["configuration"] == "Debug"
+    assert presets["testPresets"][2]["configuration"] == "RelWithDebInfo"
+    assert presets["testPresets"][3]["configuration"] == "MinSizeRel"
+
     assert len(presets["configurePresets"]) == 1
     assert presets["configurePresets"][0]["name"] == "default"
 
@@ -453,6 +469,9 @@ def test_cmake_presets_singleconfig():
     assert len(presets["buildPresets"]) == 1
     assert presets["buildPresets"][0]["configurePreset"] == "release"
 
+    assert len(presets["testPresets"]) == 1
+    assert presets["testPresets"][0]["configurePreset"] == "release"
+
     # Now two configurePreset, but named correctly
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
@@ -462,6 +481,9 @@ def test_cmake_presets_singleconfig():
     assert len(presets["buildPresets"]) == 2
     assert presets["buildPresets"][1]["configurePreset"] == "debug"
 
+    assert len(presets["testPresets"]) == 2
+    assert presets["testPresets"][1]["configurePreset"] == "debug"
+
     # Repeat configuration, it shouldn't add a new one
     client.run("install mylib/1.0@ -g CMakeToolchain -s build_type=Debug --profile:h=profile")
     presets = json.loads(client.load("CMakePresets.json"))
@@ -876,6 +898,59 @@ def test_cmake_layout_toolchain_folder():
     c.run("install . -s os=Linux -s compiler=gcc -s compiler.version=7 -s build_type=Debug "
           "-s compiler.libcxx=libstdc++11 -s arch=x86 "
           "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.arch\", \"settings.build_type\"]'")
-    print(c.out)
     assert os.path.exists(os.path.join(c.current_folder,
                                        "build/x86-debug/generators/conan_toolchain.cmake"))
+
+
+def test_set_linker_scripts():
+    profile = textwrap.dedent(r"""
+    [settings]
+    os=Windows
+    arch=x86_64
+    compiler=clang
+    compiler.version=15
+    compiler.libcxx=libstdc++11
+    [conf]
+    tools.build:linker_scripts=["/usr/local/src/flash.ld", "C:\\local\\extra_data.ld"]
+    """)
+    client = TestClient(path_with_spaces=False)
+    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")
+    toolchain = client.load("conan_toolchain.cmake")
+    assert 'string(APPEND CONAN_EXE_LINKER_FLAGS -T"/usr/local/src/flash.ld" -T"C:/local/extra_data.ld")' in toolchain
+
+
+def test_test_package_layout():
+    """
+    test that the ``test_package`` folder also follows the cmake_layout and the
+    build_folder_vars
+    """
+    client = TestClient()
+    test_conanfile = textwrap.dedent("""
+        from conan import ConanFile
+        from conan.tools.cmake import cmake_layout
+
+        class Conan(ConanFile):
+            settings = "os", "arch", "compiler", "build_type"
+            generators = "CMakeToolchain"
+            test_type = "explicit"
+
+            def requirements(self):
+                self.requires(self.tested_reference_str)
+
+            def layout(self):
+                cmake_layout(self)
+
+            def test(self):
+                pass
+    """)
+    client.save({"conanfile.py": GenConanfile("pkg", "0.1"),
+                 "test_package/conanfile.py": test_conanfile})
+    config = "-c tools.cmake.cmake_layout:build_folder_vars='[\"settings.compiler.cppstd\"]'"
+    client.run(f"create . {config} -s compiler.cppstd=14")
+    client.run(f"create . {config} -s compiler.cppstd=17")
+    assert os.path.exists(os.path.join(client.current_folder, "test_package/build/14"))
+    assert os.path.exists(os.path.join(client.current_folder, "test_package/build/17"))
diff --git a/conans/test/integration/toolchains/env/test_virtualenv_powershell.py b/conans/test/integration/toolchains/env/test_virtualenv_powershell.py
index 4aa80ef0e..00f36f7fa 100644
--- a/conans/test/integration/toolchains/env/test_virtualenv_powershell.py
+++ b/conans/test/integration/toolchains/env/test_virtualenv_powershell.py
@@ -14,6 +14,7 @@ from conans.tools import save
 def client():
     # We use special characters and spaces, to check everything works
     # https://github.com/conan-io/conan/issues/12648
+    # FIXME: This path still fails the creation of the deactivation script
     cache_folder = os.path.join(temp_folder(), "[sub] folder")
     client = TestClient(cache_folder)
     conanfile = str(GenConanfile("pkg", "0.1"))
@@ -40,6 +41,7 @@ def test_virtualenv(client):
             name = "app"
             version = "0.1"
             requires = "pkg/0.1"
+            apply_env = False
 
             def build(self):
                 self.output.info("----------BUILD----------------")
diff --git a/conans/test/integration/toolchains/gnu/test_autotoolstoolchain.py b/conans/test/integration/toolchains/gnu/test_autotoolstoolchain.py
index 9c91d3c8e..4bc28f2fe 100644
--- a/conans/test/integration/toolchains/gnu/test_autotoolstoolchain.py
+++ b/conans/test/integration/toolchains/gnu/test_autotoolstoolchain.py
@@ -45,6 +45,36 @@ def test_extra_flags_via_conf():
         assert 'export LDFLAGS="$LDFLAGS --flag5 --flag6"' in toolchain
 
 
+def test_linker_scripts_via_conf():
+    os_ = platform.system()
+    os_ = "Macos" if os_ == "Darwin" else os_
+
+    profile = textwrap.dedent("""
+        [settings]
+        os=%s
+        compiler=gcc
+        compiler.version=6
+        compiler.libcxx=libstdc++11
+        arch=armv8
+        build_type=Release
+
+        [conf]
+        tools.build:sharedlinkflags+=["--flag5"]
+        tools.build:exelinkflags+=["--flag6"]
+        tools.build:linker_scripts+=["/linker/scripts/flash.ld", "/linker/scripts/extra_data.ld"]
+        """ % os_)
+    client = TestClient()
+    conanfile = GenConanfile().with_settings("os", "arch", "compiler", "build_type")\
+        .with_generator("AutotoolsToolchain")
+    client.save({"conanfile.py": conanfile,
+                "profile": profile})
+    client.run("install . --profile:build=profile --profile:host=profile")
+    toolchain = client.load("conanautotoolstoolchain{}".format('.bat' if os_ == "Windows" else '.sh'))
+    if os_ == "Windows":
+        assert 'set "LDFLAGS=%LDFLAGS% --flag5 --flag6 -T\'/linker/scripts/flash.ld\' -T\'/linker/scripts/extra_data.ld\'"' in toolchain
+    else:
+        assert 'export LDFLAGS="$LDFLAGS --flag5 --flag6 -T\'/linker/scripts/flash.ld\' -T\'/linker/scripts/extra_data.ld\'"' in toolchain
+
 def test_not_none_values():
 
     conanfile = textwrap.dedent("""
diff --git a/conans/test/integration/toolchains/meson/test_mesontoolchain.py b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
index 7a1932fb9..d360c1706 100644
--- a/conans/test/integration/toolchains/meson/test_mesontoolchain.py
+++ b/conans/test/integration/toolchains/meson/test_mesontoolchain.py
@@ -92,6 +92,35 @@ def test_extra_flags_via_conf():
     assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6']" in content
 
 
+def test_linker_scripts_via_conf():
+    profile = textwrap.dedent("""
+        [settings]
+        os=Windows
+        arch=x86_64
+        compiler=gcc
+        compiler.version=9
+        compiler.cppstd=17
+        compiler.libcxx=libstdc++
+        build_type=Release
+
+        [buildenv]
+        LDFLAGS=-flag0 -other=val
+
+        [conf]
+        tools.build:sharedlinkflags+=["-flag5"]
+        tools.build:exelinkflags+=["-flag6"]
+        tools.build:linker_scripts+=["/linker/scripts/flash.ld", "/linker/scripts/extra_data.ld"]
+   """)
+    t = TestClient()
+    t.save({"conanfile.txt": "[generators]\nMesonToolchain",
+            "profile": profile})
+
+    t.run("install . -pr=profile")
+    content = t.load(MesonToolchain.native_filename)
+    assert "c_link_args = ['-flag0', '-other=val', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
+    assert "cpp_link_args = ['-flag0', '-other=val', '-flag5', '-flag6', '-T\"/linker/scripts/flash.ld\"', '-T\"/linker/scripts/extra_data.ld\"']" in content
+
+
 def test_correct_quotes():
     profile = textwrap.dedent("""
        [settings]
diff --git a/conans/test/integration/toolchains/microsoft/vcvars_test.py b/conans/test/integration/toolchains/microsoft/vcvars_test.py
index 27db87bbd..fb6779ab8 100644
--- a/conans/test/integration/toolchains/microsoft/vcvars_test.py
+++ b/conans/test/integration/toolchains/microsoft/vcvars_test.py
@@ -4,6 +4,7 @@ import os
 
 import pytest
 
+from conans.test.assets.genconanfile import GenConanfile
 from conans.test.utils.tools import TestClient
 
 
@@ -36,6 +37,23 @@ def test_vcvars_generator(scope):
         assert not os.path.exists(os.path.join(client.current_folder, "conanbuild.bat"))
 
 
+@pytest.mark.skipif(platform.system() not in ["Windows"], reason="Requires Windows")
+def test_vcvars_generator_skip():
+    """
+    tools.microsoft.msbuild:installation_path=disabled avoids creation of conanvcvars.bat
+    """
+    client = TestClient()
+    client.save({"conanfile.py": GenConanfile().with_generator("VCVars")
+                                               .with_settings("os", "compiler",
+                                                              "arch", "build_type"),
+                 "profile": 'include(default)\n[conf]\ntools.microsoft.msbuild:installation_path='})
+
+    client.run('install . -c tools.microsoft.msbuild:installation_path=""')
+    assert not os.path.exists(os.path.join(client.current_folder, "conanvcvars.bat"))
+    client.run('install . -pr=profile')
+    assert not os.path.exists(os.path.join(client.current_folder, "conanvcvars.bat"))
+
+
 @pytest.mark.skipif(platform.system() not in ["Windows"], reason="Requires Windows")
 def test_vcvars_generator_string():
     client = TestClient(path_with_spaces=False)
diff --git a/conans/test/unittests/tools/cmake/test_cmaketoolchain.py b/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
index d66be8732..25776873e 100644
--- a/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/conans/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -505,3 +505,10 @@ def test_compilers_block(conanfile):
     content = toolchain.content
     for compiler, lang in cmake_mapping.items():
         assert f'set(CMAKE_{lang}_COMPILER "path_to_{compiler}")' in content
+
+
+def test_linker_scripts_block(conanfile):
+    conanfile.conf.define("tools.build:linker_scripts", ["path_to_first_linker_script", "path_to_second_linker_script"])
+    toolchain = CMakeToolchain(conanfile)
+    content = toolchain.content
+    assert f'string(APPEND CONAN_EXE_LINKER_FLAGS -T"path_to_first_linker_script" -T"path_to_second_linker_script")' in content
diff --git a/conans/test/unittests/tools/files/test_symlinks.py b/conans/test/unittests/tools/files/test_symlinks.py
index 83433228a..3a2fd174f 100644
--- a/conans/test/unittests/tools/files/test_symlinks.py
+++ b/conans/test/unittests/tools/files/test_symlinks.py
@@ -17,7 +17,7 @@ def folders():
         (os.path.join(tmp, "foo/var/file.txt"), "foo/var/other/absolute.txt"),  # Absolute link
         (os.path.join(tmp, "foo/var"), "foo/var/other/other/myfolder"),  # Absolute link folder
         (os.path.join(tmp, "foo/var/file.txt"), "foo/absolute.txt"),  # Absolute link
-        ("foo/var/file.txt", "foo/var/other/relative.txt"),  # Relative link
+        ("../file.txt", "foo/var/other/relative.txt"),  # Relative link
         ("missing.txt", "foo/var/other/broken.txt"),  # Broken link
         (outside_folder, "foo/var/other/absolute_outside"),  # Absolute folder outside the folder
         ("../../../../../outside", "foo/absolute_outside"),  # Relative folder outside the folder
@@ -54,7 +54,7 @@ def test_absolute_to_relative_symlinks(folders):
     assert linked_to == "var/file.txt"
 
     linked_to = os.readlink(os.path.join(folder, "foo/var/other/relative.txt")).replace("\\", "/")
-    assert linked_to == "foo/var/file.txt"
+    assert linked_to == "../file.txt"
 
     linked_to = os.readlink(os.path.join(folder, "foo/var/other/broken.txt"))
     assert linked_to == "missing.txt"
@@ -81,7 +81,7 @@ def test_remove_external_symlinks(folders):
     assert linked_to == os.path.join(folder, "foo/var/file.txt")
 
     linked_to = os.readlink(os.path.join(folder, "foo/var/other/relative.txt"))
-    assert linked_to == "foo/var/file.txt"
+    assert linked_to == "../file.txt"
 
     linked_to = os.readlink(os.path.join(folder, "foo/var/other/broken.txt"))
     assert linked_to == "missing.txt"
@@ -112,7 +112,7 @@ def test_remove_broken_symlinks(folders):
     assert linked_to == os.path.join(folder, "foo/var/file.txt")
 
     linked_to = os.readlink(os.path.join(folder, "foo/var/other/relative.txt"))
-    assert linked_to == "foo/var/file.txt"
+    assert linked_to == "../file.txt"
 
     # This one is removed
     assert not os.path.islink(os.path.join(folder, "foo/var/other/broken.txt"))
diff --git a/conans/test/unittests/tools/gnu/autotoolschain_test.py b/conans/test/unittests/tools/gnu/autotoolschain_test.py
index 93b7d31ec..b8f8a4d08 100644
--- a/conans/test/unittests/tools/gnu/autotoolschain_test.py
+++ b/conans/test/unittests/tools/gnu/autotoolschain_test.py
@@ -125,3 +125,17 @@ def test_compilers_mapping():
     env = autotoolschain.environment().vars(conanfile)
     for compiler, env_var in autotools_mapping.items():
         assert env[env_var] == f"path_to_{compiler}"
+
+
+def test_linker_scripts():
+    conanfile = ConanFileMock()
+    conanfile.conf = Conf()
+    conanfile.conf.define("tools.build:linker_scripts", ["path_to_first_linker_script", "path_to_second_linker_script"])
+    settings = MockSettings({"build_type": "Release",
+                             "os": "Windows",
+                             "arch": "x86_64"})
+    conanfile.settings = settings
+    autotoolschain = AutotoolsToolchain(conanfile)
+    env = autotoolschain.environment().vars(conanfile)
+    assert "-T'path_to_first_linker_script'" in env["LDFLAGS"]
+    assert "-T'path_to_second_linker_script'" in env["LDFLAGS"]
diff --git a/conans/test/unittests/tools/microsoft/test_check_min_vs.py b/conans/test/unittests/tools/microsoft/test_check_min_vs.py
index bca089093..4810a6a81 100644
--- a/conans/test/unittests/tools/microsoft/test_check_min_vs.py
+++ b/conans/test/unittests/tools/microsoft/test_check_min_vs.py
@@ -7,6 +7,23 @@ from conans.test.utils.mocks import MockConanfile, MockSettings
 
 class TestCheckMinVS:
 
+    parametrize_vars = "compiler,version,update,minimum"
+    valid_parametrize_values = [
+        ("Visual Studio", "15", None, "191"),
+        ("Visual Studio", "16", None, "192"),
+        ("msvc", "193", None, "193"),
+        ("msvc", "193", None, "192"),
+        ("msvc", "193", "2", "193.1"),
+    ]
+
+    invalid_parametrize_values = [
+        ("Visual Studio", "15", None, "192"),
+        ("Visual Studio", "16", None, "193.1"),
+        ("msvc", "192", None, "193"),
+        ("msvc", "193", None, "193.1"),
+        ("msvc", "193", "1", "193.2"),
+    ]
+
     @staticmethod
     def _create_conanfile(compiler, version, update=None):
         settings = MockSettings({"compiler": compiler,
@@ -15,25 +32,23 @@ class TestCheckMinVS:
         conanfile = MockConanfile(settings)
         return conanfile
 
-    @pytest.mark.parametrize("compiler,version,update,minimum", [
-        ("Visual Studio", "15", None, "191"),
-        ("Visual Studio", "16", None, "192"),
-        ("msvc", "193", None, "193"),
-        ("msvc", "193", None, "192"),
-        ("msvc", "193", "2", "193.1"),
-    ])
+    @pytest.mark.parametrize(parametrize_vars, valid_parametrize_values)
     def test_valid(self, compiler, version, update, minimum):
         conanfile = self._create_conanfile(compiler, version, update)
         check_min_vs(conanfile, minimum)
 
-    @pytest.mark.parametrize("compiler,version,update,minimum", [
-        ("Visual Studio", "15", None, "192"),
-        ("Visual Studio", "16", None, "193.1"),
-        ("msvc", "192", None, "193"),
-        ("msvc", "193", None, "193.1"),
-        ("msvc", "193", "1", "193.2"),
-    ])
+    @pytest.mark.parametrize(parametrize_vars, valid_parametrize_values)
+    def test_valid_nothrows(self, compiler, version, update, minimum):
+        conanfile = self._create_conanfile(compiler, version, update)
+        assert check_min_vs(conanfile, minimum, raise_invalid=False)
+
+    @pytest.mark.parametrize(parametrize_vars, invalid_parametrize_values)
     def test_invalid(self, compiler, version, update, minimum):
         conanfile = self._create_conanfile(compiler, version, update)
         with pytest.raises(ConanInvalidConfiguration):
             check_min_vs(conanfile, minimum)
+
+    @pytest.mark.parametrize(parametrize_vars, invalid_parametrize_values)
+    def test_invalid_nothrows(self, compiler, version, update, minimum):
+        conanfile = self._create_conanfile(compiler, version, update)
+        assert not check_min_vs(conanfile, minimum, raise_invalid=False)