Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] CMakeToolchain user_toolchain invalid escape character on Windows #10539

Closed
jwillikers opened this issue Feb 8, 2022 · 6 comments
Closed
Assignees

Comments

@jwillikers
Copy link
Contributor

jwillikers commented Feb 8, 2022

Environment Details (include every applicable attribute)

  • Operating System+version: Windows servercore 2019
  • Compiler+version: MSVC 192
  • Conan version: 1.45.0
  • Python version: 3.10.2

Steps to reproduce (Include if Applicable)

Create a package for providing a CMake toolchain file via tool_requires and enable short_paths on Windows.
When building a test package on Windows the following error occurs, which looks like it could be fixed by using forward slashes in the CMake toolchain instead of backslashes when including the user_toolchain.

Logs (Executed commands with output) (Include/Attach if Applicable)

my-conan-toolchain/c8783856@my-conan-remote/testing (test package): Calling build()
my-conan-toolchain/c8783856@my-conan-remote/testing (test package): CMake command: cmake -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="C:/builds/components/conan-toolchain/test_package/build/79d6417e7f7125abb0ee5e356ea18ea032664329/conan_toolchain.cmake" -DCMAKE_INSTALL_PREFIX="C:/builds/components/conan-toolchain/test_package/build/79d6417e7f7125abb0ee5e356ea18ea032664329/package" "C:\builds\components\conan-toolchain\test_package"
**********************************************************************
** Visual Studio 2019 Developer Command Prompt v16.11.9
** Copyright (c) 2021 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
Using Conan toolchain: C:/builds/components/conan-toolchain/test_package/build/79d6417e7f7125abb0ee5e356ea18ea032664329/conan_toolchain.cmake.
CMake Error at build/79d6417e7f7125abb0ee5e356ea18ea032664329/conan_toolchain.cmake:19 (include):
  Syntax error in cmake code at
    C:/builds/components/conan-toolchain/test_package/build/79d6417e7f7125abb0ee5e356ea18ea032664329/conan_toolchain.cmake:19
  when parsing string
    C:\.conan\193a6c\1\conan_toolchain.cmake
  Invalid character escape '\1'.
Call Stack (most recent call first):
  C:/Program Files/CMake/share/cmake-3.22/Modules/CMakeDetermineSystem.cmake:124 (include)
  CMakeLists.txt:3 (project)
CMake Error: CMake was unable to find a build program corresponding to "Ninja".  CMAKE_MAKE_PROGRAM is not set.  You probably need to select a different build tool.
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
-- Configuring incomplete, errors occurred!
@memsharded
Copy link
Member

Hi @jwillikers

I haven't managed to reproduce this. I have modified tests to cover this case, and they keep passing in Windows:

diff --git a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py 
index 4316a5650..26b1f8a38 100644                                                                                                              
--- a/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py                                                                          
+++ b/conans/test/functional/toolchains/cmake/test_cmake_toolchain.py                                                                          
@@ -46,12 +46,14 @@ def test_cmake_toolchain_user_toolchain():                                                                                 
     client = TestClient(path_with_spaces=False)                                                                                               
     conanfile = GenConanfile().with_settings("os", "compiler", "build_type", "arch").\                                                        
         with_generator("CMakeToolchain")                                                                                                      
-    save(client.cache.new_config_path, "tools.cmake.cmaketoolchain:user_toolchain=mytoolchain.cmake")                                         
+    # use backslash, make sure https://github.com/conan-io/conan/issues/10539 works                                                           
+    save(client.cache.new_config_path,                                                                                                        
+         r"tools.cmake.cmaketoolchain:user_toolchain=folder\mytoolchain.cmake")                                                               
                                                                                                                                               
     client.save({"conanfile.py": conanfile})                                                                                                  
     client.run("install .")                                                                                                                   
     toolchain = client.load("conan_toolchain.cmake")                                                                                          
-    assert 'include("mytoolchain.cmake")' in toolchain                                                                                        
+    assert 'include("folder/mytoolchain.cmake")' in toolchain                                                                                 
                                                                                                                                               
                                                                                                                                               
 def test_cmake_toolchain_custom_toolchain():                                                                                                  
@@ -73,6 +75,7 @@ def test_cmake_toolchain_user_toolchain_from_dep():                                                                          
         import os                                                                                                                             
         from conans import ConanFile                                                                                                          
         class Pkg(ConanFile):                                                                                                                 
+            short_paths = True                                                                                                                
             exports_sources = "*"                                                                                                             
             def package(self):                                                                                                                
                 self.copy("*")                                                                                                                

Furthermore, the code that writes the toolchain is this:

class UserToolchain(Block):
    template = textwrap.dedent("""
        {% for user_toolchain in paths %}
        include("{{user_toolchain}}")
        {% endfor %}
        """)

    def context(self):
        # This is global [conf] injection of extra toolchain files
        user_toolchain = self._conanfile.conf["tools.cmake.cmaketoolchain:user_toolchain"]
        toolchains = [user_toolchain.replace("\\", "/")] if user_toolchain else []
        return {"paths": toolchains if toolchains else []}

I cannot find how a path would be saved there without replacing the backslash for forward slash could be saved into the conan_toolchain.cmake. Can you please double check that you are running 1.45 and that your generated toolchain is generated with 1.45? Thanks

@jwillikers
Copy link
Contributor Author

@memsharded Thanks for doing that!
You're response makes the issue clear.
I'm generating a toolchain and including it based off of the latest Conan blog post which uses tool_requires for the toolchain: https://blog.conan.io/2022/01/17/New-conan-release-1-44.html

My generate method looks like this:

    def generate(self):
        user_toolchains = []
        for dep in self.dependencies.test.values():
            ut = dep.conf_info["tools.cmake.cmaketoolchain:user_toolchain"]
            if ut:
                user_toolchains.append(ut)
        t = CMakeToolchain(self)
        t.blocks["user_toolchain"].values["paths"] = user_toolchains
        t.generate()

As you can see, I overwrite the sanitized paths value with my own, unsanitized value, which is why the backslashes weren't being replaced.

Feel free to close this issue if nothing has been left outstanding on your side. Thanks!

@memsharded
Copy link
Member

Oh, that makes sense indeed, if you are directly providing unsanitized paths, then there is little that Conan can do (the template could try to do it too, but the template can also be overriden, so probably not worth the effort). As the solution is straightforward and is reasonable, I think we can close this, thanks for the feedback!

@ericLemanissier
Copy link
Contributor

@memsharded @jwillikers I just hit the same error on https://github.com/eirikb/proof-of-conan/actions/runs/3636633060/jobs/6136798729#step:10:11464:

  Syntax error in cmake code at

    C:/.conan/20ab30/1/build/generators/conan_toolchain.cmake:94

  when parsing string
-- Configuring incomplete, errors occurred!

    C:\.conan\60a005\1\res\archdatadir\mkspecs

  Invalid character escape '\6'.

This is triggered by tc.variables["INSTALL_MKSPECSDIR"] = os.path.join(self.package_folder, "res", "archdatadir", "mkspecs")
Is it correct to say that it's the recipe's role to properly escape all paths ? does conan offer a tool doing this ?
the previous cmake integration was using cmake.definitions["INSTALL_MKSPECSDIR"] = os.path.join(self.package_folder, "res", "archdatadir", "mkspecs") which worked just fine

@friendlyanon
Copy link
Contributor

friendlyanon commented Dec 7, 2022

Note that this happens because Conan takes the inputs as-is and outputs them inside a double quote string, where CMake will try to interpret the backslashes as escapes. The easiest solution to this would be to use the Lua style brackets instead of double quotes. So instead of include("{{user_toolchain}}") it would say include([==[{{user_toolchain}}]==]). This will also prevent "smuggling" ${} tokens in. This is also what CMake itself does in some places to preserve user input as-is.

@SpaceIm
Copy link
Contributor

SpaceIm commented Dec 7, 2022

@ericLemanissier In all CCI recipes migrated to conan v2, I add .replace("\\", "/") to all paths injected into variables of CMakeToolchain, not sure it's worth to expose a conan helper for this.

ericLemanissier added a commit to ericLemanissier/conan-center-index that referenced this issue Dec 7, 2022
conan-center-bot pushed a commit to conan-io/conan-center-index that referenced this issue Dec 13, 2022
* use is_mvc

* use CMakeDeps

fixes #13144

* use PkgConfigDeps

* use modern CMake integration

* debug cmake find package

* work arround conan-io/conan#11962

* don't test with qmake on static qt

* fix Invalid character escape

cf conan-io/conan#10539
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants