Repository: conan-io/conan

Issue:
[feature] CMakeToolchain support for linker scripts
When building an ELF executable for bare-metal targets it is normal to include a linker script to define the memory layout of the specific target microcontroller.
It is possible to use multiple linker scripts, too.
Each one is passed with the `-T` flag when using GCC.
I don't think this actually needs to be applied to dependencies, only the final executable that will be loaded on the microcontroller.
It would be great if there was an easy way to configure this for the CMakeToolchain generator.
If it only needs to be applied to the consumer, perhaps the CMakeToolchain generator could define a dedicated block for this.
I'm not sure if there should be additional ways to configure linker scripts, but there might be some good ideas out there.

- [X] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).


Discussion:
jwillikers: @memsharded It would be that simple if the only `exelinkflags` I had to set were for a single linker script flag. Unfortunately, it's necessary to set several additional linker flags for the particular architecture, standard library, and flags that help reduce the code size. I actually did end up making an extremely difficult to debug copy-paste error due to having to duplicate the rest of the linker flags. Here's a snippet of the current situation I'm describing.

```
# Common profile used by all applications built for this particular MCU
tools.build:exelinkflags+=["-mthumb", "-mfloat-abi=hard", "-march=armv7e-m+fp", "-ffunction-sections", "-fdata-sections", "-specs=nano.specs", "-specs=nosys.specs", "-Xlinker --gc-sections"]
```

```
# Separate profile that inherits the previous one and sets the linker scripts for the different apps

# In the default case, the linker script can just be appended to the inherited linker flags
tools.build:exelinkflags+=["-T /path/to/mcu_flash.ld"]

# Overwrite the linker script for two special apps that use other portions of the flash memory and therefore different linker scripts.
# All other linker flags are the exact same, but we must completely overwrite the existing flags with `=` to avoid the previous linker script being included and duplicating the `-specs` flags.
bootloader/*:tools.build:exelinkerflags=["-mthumb", "-mfloat-abi=hard", "-march=armv7e-m+fp", "-ffunction-sections", "-fdata-sections", "-specs=nano.specs", "-specs=nosys.specs", "-Xlinker --gc-sections", "-T/path/to/bootloader_flash.ld"]
upgrade/*:tools.build:exelinkerflags=["-mthumb", "-mfloat-abi=hard", "-march=armv7e-m+fp", "-ffunction-sections", "-fdata-sections", "-specs=nano.specs", "-specs=nosys.specs", "-Xlinker --gc-sections", "-T/path/to/upgrade_flash.ld"]
```

By having a separate conf variable for the linker scripts, this becomes:

```
# Default linker script
tools.build:linker_scripts=["-T /path/to/mcu_flash.ld"]

bootloader/*:tools.build:linker_scripts=["-T/path/to/bootloader_flash.ld"]
upgrade/*:tools.build:linker_scripts=["-T/path/to/upgrade_flash.ld"]
```

My copy-paste error was related to me duplicating these profiles for a very similar MCU.
For one of the app overrides, I missed updating the architecture flag from `"-march=armv7e-m+fp"` to `"-march=armv7e-m+dp"`, literally a one letter difference that has very important but non-obvious consequences.
I think the ability to similarly configure / overwrite a `linker_scripts` `conf` variable in a project's `generate` method is also helpful in the common case where a project wants to keep their linker script with the project's source code.
jwillikers: I'll ping @kammce since I know he has shown interest in this feature in the past. He might have something to add to the conversation.
memsharded: Hi @jwillikers 

I think the problem that you are describing is not really happening. I have done a test in https://github.com/conan-io/conan/pull/12862 to validate it. These are the profiles:

profile1:
```
[conf]
user.myteam:myconf=["a", "b", "c"]
```
profile2:
```
include(profile1)
[conf]
mypkg*:user.myteam:myconf+=["d"]
mydep*:user.myteam:myconf=+["e"]
```
Applying it to different packages works fine:
```
conan install . mypkg/0.1@ -pr=profile2
conanfile.py (mypkg/0.1): MYCONF: ['a', 'b', 'c', 'd']
conan install . mydep/0.1@ -pr=profile2
conanfile.py (mydep/0.1): MYCONF: ['e', 'a', 'b', 'c']
```
jwillikers: @memsharded Yes, your stated case works in my experience as well. My use case is slightly different than that because I have a linker script which is used in the default case for _all_ packages:

```
# Default linker script
tools.build:linker_scripts=["/path/to/mcu_flash.ld"]
```

Later in the profile, I override the default linker script for two specific packages.

```
bootloader/*:tools.build:linker_scripts=["/path/to/bootloader_flash.ld"]
upgrade/*:tools.build:linker_scripts=["/path/to/upgrade_flash.ld"]
```

I use a linker script for all cases for convenience and I remember correctly, it's necessary for test packages to link correctly. For instance, if my package uses `fmt`, then I think in order for the `fmt` `test_package` to link correctly, there has to be a linker script defining the memory layout.
memsharded: Ok, perfect, thanks very much @jwillikers, that fully clarifies now the use case.
Lets ask for some extra review, but I'd say this seems doable.
kammce: So I'm still learning the different ways to use Conan so this may come off as a dumb question. Is there a way with a conan recipe to produce multiple package targets. Like, installing a package `example-mcu-6xxx`, which will have multiple microcontrollers in its family. For each MCU in that family, it would be helpful to have a separate library link target and separate `tools.build.linker_script`'s for each. This would be a requirement as a single linker script cannot do when the size of RAM and Flash vary. You'd either use the smallest to be safe or the largest and maybe do some post process checks. But if we have this capability, then that leads to my second question. 

Is it a choice that the user can use the conf variable or not? Because there are many times when a user will want to use their own linker script rather than the one handed to them. Most times to segment out sections of RAM or Flash for particular types of data. 

So long as we have 1 package multiple subpackages and a way to opt in/out of using the package provided linker script, then this feature would be great. This would definitely help get rid of the copy & paste problem I've been dealing with. If I have a ton of apps using a library, and I want to use the same default linker script for everyone, I have to copy it N number of times 
memsharded:  Hi @kammce 

> So I'm still learning the different ways to use Conan so this may come off as a dumb question. Is there a way with a conan recipe to produce multiple package targets. Like, installing a package example-mcu-6xxx, which will have multiple microcontrollers in its family. For each MCU in that family, it would be helpful to have a separate library link target and separate tools.build.linker_script's for each. This would be a requirement as a single linker script cannot do when the size of RAM and Flash vary. You'd either use the smallest to be safe or the largest and maybe do some post process checks. But if we have this capability, then that leads to my second question.

I am not sure what you mean exactly, but it sounds that you want to extend the Conan settings.yml (https://docs.conan.io/en/latest/extending/custom_settings.html), to define different micros, as each one might result in different binaries as you describe. Then in the profile you would define the custom setting value, and that will give you a different package_id. This is the "binary model".

> Is it a choice that the user can use the conf variable or not? Because there are many times when a user will want to use their own linker script rather than the one handed to them. Most times to segment out sections of RAM or Flash for particular types of data.

I know nothing about the linker scripts, but yes, the ``conf`` is defined by the users in the profile. So every user can provide their own linker script.

> So long as we have 1 package multiple subpackages and a way to opt in/out of using the package provided linker script, then this feature would be great. This would definitely help get rid of the copy & paste problem I've been dealing with. If I have a ton of apps using a library, and I want to use the same default linker script for everyone, I have to copy it N number of times

I don't fully get it either. Maybe @jwillikers can give some more insights given he has the knowledge both about Conan and these linker scripts for micros.
jwillikers: @kammce Conan doesn't really have a concept for _subpackages_, so you would want to actually create individual builds of such a package for each microcontroller you want to target. I'm imagining your use case would be to create a HAL library like `stm32f0xx` for various MCU's in the STM32F0 family. You could add settings or options for your package to build for a particular microcontroller in that family, which would allow you to control the linker script for MCU.

I'm primarily controlling linker scripts through profiles at this point, outside of any package. If you'd like such a HAL library to configure the linker scripts for consumers' projects, they would need to consume it as a `tool_requires` in order to propagate the linker scripts, and also as a regular requires in order to use the provided library target.

I believe it's possible to opt out or override linker scripts provided through profiles, `tool_requires`, and the CLI in individual packages in the `generate` method:

```
self.conf.define("tools.build:linker_scripts", ["my_linker_script"])
```
kammce: Okay understood. For profiles this seems like a good idea then. I'll look into settings/options for my driver libraries for setting up linker scripts. 
memsharded: Sorry, closed by accident by another PR, re-opening

Patch:
diff --git a/conan/tools/cmake/presets.py b/conan/tools/cmake/presets.py
index 306b811d3..11a443a62 100644
--- a/conan/tools/cmake/presets.py
+++ b/conan/tools/cmake/presets.py
@@ -117,7 +117,7 @@ def _configure_preset(conanfile, generator, cache_variables, toolchain_file, mul
 
 
 def _insert_preset(data, preset_name, preset):
-    position = _get_already_existing_preset_index(preset["name"], data[preset_name])
+    position = _get_already_existing_preset_index(preset["name"], data.setdefault(preset_name, []))
     if position is not None:
         data[preset_name][position] = preset
     else:
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/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/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..4ed113b61 100644
--- a/conans/model/conf.py
+++ b/conans/model/conf.py
@@ -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/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/toolchains/cmake/test_cmaketoolchain.py b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
index dcbc046aa..7f2bac7fb 100644
--- a/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/conans/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -902,6 +902,27 @@ def test_cmake_layout_toolchain_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
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/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"]