Repository: conan-io/conan
Pull request #16883 (2024-08-27 13:17:32)

Fix for #16882: Generated CMake build when using tools.build.linker_scripts
The generated build otherwise appends the "-Tfoo.ld" argument directly without a preceding space before which can generate an invalid command line.

Changelog: Bugfix: This is a possible fix for #16882.

- [X] Refer to the issue that supports this Pull Request.
- [ ] If the issue has missing info, explain the purpose/use case/pain/need that covers this Pull Request.
- [x] I've read the [Contributing guide](https://github.com/conan-io/conan/blob/develop2/.github/CONTRIBUTING.md).
- [x] I've followed the PEP8 style guides for Python code.
- [ ] I've opened another PR in the Conan docs repo to the ``develop`` branch, documenting this one.


memsharded: I have pushed some changes, fixing the test (that test will cover the change already), and trying to keep the original quotes. Can you please test this branch with your build to see if it works? Thanks!
photex: @memsharded you're update fixed the cmake warnings I was seeing as well. :D 

Patch:
diff --git a/conan/tools/cmake/toolchain/blocks.py b/conan/tools/cmake/toolchain/blocks.py
index 930d62f48..5549fdf1d 100644
--- a/conan/tools/cmake/toolchain/blocks.py
+++ b/conan/tools/cmake/toolchain/blocks.py
@@ -254,7 +254,7 @@ class LinkerScriptsBlock(Block):
         # Add linker flags from tools.build:linker_scripts conf
 
         message(STATUS "Conan toolchain: Defining linker script flag: {{ linker_script_flags }}")
-        string(APPEND CONAN_EXE_LINKER_FLAGS {{ linker_script_flags }})
+        string(APPEND CONAN_EXE_LINKER_FLAGS " {{ linker_script_flags }}")
         """)
 
     def context(self):
@@ -265,7 +265,7 @@ class LinkerScriptsBlock(Block):
         linker_scripts = [linker_script.replace('\\', '/') for linker_script in linker_scripts]
         linker_scripts = [relativize_path(p, self._conanfile, "${CMAKE_CURRENT_LIST_DIR}")
                           for p in linker_scripts]
-        linker_script_flags = ['-T"' + linker_script + '"' for linker_script in linker_scripts]
+        linker_script_flags = [r'-T\"' + linker_script + r'\"' for linker_script in linker_scripts]
         return {"linker_script_flags": " ".join(linker_script_flags)}
 
 
diff --git a/conan/tools/scm/git.py b/conan/tools/scm/git.py
index 117a064b8..c1aeb577a 100644
--- a/conan/tools/scm/git.py
+++ b/conan/tools/scm/git.py
@@ -120,15 +120,18 @@ class Git:
             # Don't raise an error because the fetch could fail for many more reasons than the branch.
             return False
 
-    def is_dirty(self):
+    def is_dirty(self, repository=False):
         """
         Returns if the current folder is dirty, running ``git status -s``
         The ``Git(..., excluded=[])`` argument and the ``core.scm:excluded`` configuration will
         define file patterns to be skipped from this check.
 
+        :param repository: By default checks if the current folder is dirty. If repository=True
+                     it will check the root repository folder instead, not the current one.
         :return: True, if the current folder is dirty. Otherwise, False.
         """
-        status = self.run("status . --short --no-branch --untracked-files").strip()
+        path = '' if repository else '.'
+        status = self.run(f"status {path} --short --no-branch --untracked-files").strip()
         self._conanfile.output.debug(f"Git status:\n{status}")
         if not self._excluded:
             return bool(status)
@@ -168,7 +171,7 @@ class Git:
                      the commit of the repository instead.
         :return: (url, commit) tuple
         """
-        dirty = self.is_dirty()
+        dirty = self.is_dirty(repository=repository)
         if dirty:
             raise ConanException("Repo is dirty, cannot capture url and commit: "
                                  "{}".format(self.folder))


Test patch:
diff --git a/test/functional/tools/scm/test_git.py b/test/functional/tools/scm/test_git.py
index c566270d8..47214e461 100644
--- a/test/functional/tools/scm/test_git.py
+++ b/test/functional/tools/scm/test_git.py
@@ -177,6 +177,24 @@ class TestGitCaptureSCM:
         c.run("export .", assert_error=True)
         assert "Repo is dirty, cannot capture url and commit" in c.out
 
+    def test_capture_commit_local_repository(self):
+        """
+        same as above, but with ``get_url_and_commit(repository=True)``
+        """
+        c = TestClient()
+        c.save({"pkg/conanfile.py": self.conanfile.replace("get_url_and_commit()",
+                                                           "get_url_and_commit(repository=True)"),
+                "somefile.txt": ""})
+        commit = c.init_git_repo()
+        c.run("export pkg")
+        assert "This revision will not be buildable in other computer" in c.out
+        assert "pkg/0.1: SCM COMMIT: {}".format(commit) in c.out
+        assert "pkg/0.1: SCM URL: {}".format(c.current_folder.replace("\\", "/")) in c.out
+
+        c.save({"somefile.txt": "something"})
+        c.run("export pkg", assert_error=True)
+        assert "Repo is dirty, cannot capture url and commit" in c.out
+
     def test_capture_remote_url(self):
         """
         a cloned repo that is expored, will report the URL of the remote
@@ -233,12 +251,14 @@ class TestGitCaptureSCM:
         Expected to throw an exception
         """
         folder = temp_folder()
-        url, commit = create_local_git_repo(files={"conanfile.py": self.conanfile}, folder=folder)
+        create_local_git_repo(files={"conanfile.py": self.conanfile}, folder=folder)
         c = TestClient()
         with c.chdir(folder):
             c.save(files={"some_header.h": "now the repo is dirty"})
             c.run_command("git config --local status.showUntrackedFiles no")
             c.run("export .", assert_error=True)
+            assert "Repo is dirty, cannot capture url and commit" in c.out
+
 
 @pytest.mark.tool("git")
 class TestGitBasicClone:
@@ -512,7 +532,7 @@ class TestGitCloneWithArgs:
         folder = os.path.join(temp_folder(), "myrepo")
         url, commit = create_local_git_repo(files={"src/myfile.h": "myheader!",
                                                    "CMakeLists.txt": "mycmake"}, folder=folder,
-                                                   commits=3, branch="main", tags=["v1.2.3"])
+                                            commits=3, branch="main", tags=["v1.2.3"])
 
         c = TestClient()
         git_args = ['--branch', 'main']
@@ -531,7 +551,7 @@ class TestGitCloneWithArgs:
         folder = os.path.join(temp_folder(), "myrepo")
         url, commit = create_local_git_repo(files={"src/myfile.h": "myheader!",
                                                    "CMakeLists.txt": "mycmake"}, folder=folder,
-                                                   commits=3, branch="main", tags=["v1.2.3"])
+                                            commits=3, branch="main", tags=["v1.2.3"])
         c = TestClient()
         git_args = ['--branch', 'foobar']
         c.save({"conanfile.py": self.conanfile.format(url=url, commit=commit, args=str(git_args))})
diff --git a/test/integration/toolchains/cmake/test_cmaketoolchain.py b/test/integration/toolchains/cmake/test_cmaketoolchain.py
index d62de1aeb..2b6c8ac40 100644
--- a/test/integration/toolchains/cmake/test_cmaketoolchain.py
+++ b/test/integration/toolchains/cmake/test_cmaketoolchain.py
@@ -1152,7 +1152,8 @@ def test_set_linker_scripts():
                 "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
+    assert 'string(APPEND CONAN_EXE_LINKER_FLAGS ' \
+           r'" -T\"/usr/local/src/flash.ld\" -T\"C:/local/extra_data.ld\"")' in toolchain
 
 
 def test_test_package_layout():
diff --git a/test/unittests/tools/cmake/test_cmaketoolchain.py b/test/unittests/tools/cmake/test_cmaketoolchain.py
index 3eabef9b4..0420540f7 100644
--- a/test/unittests/tools/cmake/test_cmaketoolchain.py
+++ b/test/unittests/tools/cmake/test_cmaketoolchain.py
@@ -650,10 +650,12 @@ def test_compilers_block(conanfile):
 
 
 def test_linker_scripts_block(conanfile):
-    conanfile.conf.define("tools.build:linker_scripts", ["path_to_first_linker_script", "path_to_second_linker_script"])
+    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
+    assert r'string(APPEND CONAN_EXE_LINKER_FLAGS " -T\"path_to_first_linker_script\" ' \
+           r'-T\"path_to_second_linker_script\"")' in content
 
 
 class TestCrossBuild: