CMake, Swift-Syntax and Windows

I've been adding cmake support to qtbridge-swift with this commit. It works perfectly fine on macOS and linux. I suspect it'd be relatively trivial to get it to work on other platforms that support c++ interop. Apart from Windows that is ):

This is the relevant patch with the main hiccups being todo with swift-syntax. The first issue is long paths:

C:/qtbridge-swift/Examples/ChatApp/ChatApp/build/_deps/qtbridgelocal-build/QtBridgeMacros_External-prefix/src/QtBridgeMacros_External-build/_deps/swiftsyntax-build/Sources/_SwiftLibraryPluginProviderCShims/CMakeFiles/_SwiftLibraryPluginProviderCShims.dir/./

  has 257 characters.  The maximum full path to an object file is 250
  characters (see CMAKE_OBJECT_PATH_MAX).

Which can at least be side-stepped a bit by shortening the prefix.

The other issue is that swiftc seems to really want lib-prefixed libraries:

LINK : fatal error LNK1104: cannot open file 'libSwiftSyntaxMacros.lib'
clang: error: linker command failed with exit code 1104 (use -v to see invocation)
ninja: build stopped: subcommand failed

So I've had to do a bit of a hack to copy the .libs and prefix them:

function(qtbridge_fix_lib_prefix)
    if(WIN32)
        set(multiValueArgs TARGETS)
        cmake_parse_arguments(FIX_LIB "" "" "${multiValueArgs}" ${ARGN})

        foreach(_t IN LISTS FIX_LIB_TARGETS)
            if(TARGET ${_t})
                add_custom_target(_qtbridge_copy_${_t} ALL
                    COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        "$<TARGET_FILE:${_t}>"
                        "$<TARGET_FILE_DIR:${_t}>/lib$<TARGET_FILE_BASE_NAME:${_t}>$<TARGET_FILE_SUFFIX:${_t}>"
                    DEPENDS ${_t}
                    COMMENT "Copy ${_t} to lib-prefixed name for swiftc on Windows"
                )
            endif()
        endforeach()
    endif()
endfunction()

From the top of my head, this solely arises for swift-syntax and not other libraries?

keeshux's Swon library has run into the exact same two problems.

The fact that the official example didn't need to cater to those issues makes me assume that they weren't around at one point in time?

I think that this is misinterpreting the error message. Linking on Windows is a different model (more similar to modern day macOS, and to AIX).

For dynamically linked library (DLL), you have two pieces.

  1. The DLL, which the developer may not see - it is purely a runtime component. The code should magically appear when needed (aka, it is part of the application distribution or system). These do not have lib prefixes unlike the Unix environments.
  2. The import library - this is all the developer sees - a promise of the ABI. You link against this, and is what the developer receives from the vendor. Traditionally, this is named the same as the DLL with a .lib suffix. So, if your DLL is SwiftSyntaxMacros.dll, you would have SwiftSyntaxMacros.lib as the import library.

For static libraries, you have only the library itself. Static libraries on Unix use .a as the suffix, but Windows uses .lib. So, now if you have a static and dynamic library option, and you want to share that with the developer, you get SwiftSyntaxMacros.lib and SwiftSyntaxMacros.lib. Since you cannot have both in the same location, I opted to adopt a Microsoft runtime convention: static libraries are lib<name>.lib and import libraries are <name>.lib.

Now, you did not share the CMake invocation, but I suspect that you did not explicitly add in -D BUILD_SHARED_LIBS=YES and so you are generating static libraries. The fact that you are building static libraries is critical.

Unlike Unix, static and dynamic libraries are not interchangeable on Windows. That directly impacts the code generation. To accommodate that, we embed that information into the Swift module, which is then consumed for both code generation as well as the auto-linking.

This is not a bug or an issue, its simply a mismatch in what you are building and trying to use.

Yup, that was it! Thanks a lot :smiley: :

include(ExternalProject)
set(QTBRIDGE_MACROS_EP_ROOT "${CMAKE_BINARY_DIR}/qtbridge_macros_ep")
ExternalProject_Add(QtBridgeMacros_External
    PREFIX "${QTBRIDGE_MACROS_EP_ROOT}"
    BINARY_DIR "${QTBRIDGE_MACROS_EP_ROOT}/build"
    SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Sources/QtBridgeMacros"
    CMAKE_ARGS
        -G Ninja
        -DCMAKE_BUILD_TYPE=Release
        -DBUILD_SHARED_LIBS=YES
    USES_TERMINAL_BUILD TRUE
    INSTALL_COMMAND ""
)
ExternalProject_Get_Property(QtBridgeMacros_External BINARY_DIR)
set(QTBRIDGE_MACROS_BINARY_DIR "${BINARY_DIR}" CACHE PATH "Path to QtBridgeMacros build")

Is there a more reliable workaround or a solution to the long paths?

CMAKE_NINJA_FORCE_RESPONSE_FILE should allow you to get away with the longer paths. To be honest, I'm a bit surprised as I would expect that to generally kick in when appropriate.

CC: @etcwilde

It's complicated. There is logic in the generator to do that. It's based on the command line length:

AND.... it's funnier than that.

The calls to write the build pass in a commandLineLenghtLimit:

that is either 0 or -1, based on whether or not you are forcing the response file...

(Here's the invocation for building Swift modules)

The cutoff should probably be a number somewhere between zero and infinity, but this is where it's standing today. :sweat_smile: