Search code examples
cmakemingw-w64

CMake with MinGW-w64 does not install built DLL, but it's being built fine


NB: The files referenced here are all given below the horizontal ruler a bit down.

This is the MWE derived from a project where I ran into this. CMake version used is 3.12.2 on Ubuntu 16.04 (16.04.6 to be precise).

The goal is to create a CMakeLists.txt which can be re-targeted to build a Windows DLL using the MinGW-w64 toolchain (apt-get install mingw-w64 on Ubuntu/Debian).

When using no explicit toolchain file (i.e. without -DCMAKE_TOOLCHAIN_FILE=...), all works as expected and the lib${PRJNAME}.so gets installed as desired. However, once I use the toolchain file given below, I only get the resulting import lib ${PRJNAME}Lib.dll.a but not the corresponding .dll file installed.

If I invoke the Bash script as follows:

./build.sh 2>&1 |grep '^-- Install'

the output is:

-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-native/install-target/lib/libtest.so
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll.a

As you can see the native build installs the actual shared library, but the one targeting Windows only installs the import lib, not the DLL. What I'd expect to see is something like this:

-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-native/install-target/lib/libtest.so
-- Install configuration: ""
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll
-- Installing: /home/user/cmake-test/build-windows/install-target/lib/test.dll.a

What is it I am doing wrong here? It would seem that the install() function is invoked properly:

install(
    TARGETS ${PRJNAME}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    COMPONENT library
)

Clearly ARCHIVE DESTINATION takes effect as that's where the import lib ends up. But why is the built .dll completely ignored here?

Side-note: I am aware of GNUInstallDirs, but that fell totally apart once I started cross-compiling for Windows. So I am setting the desired paths "manually" before invoking install().


build.sh (should be executable)

The script will first wipe the folders build-native and build-windows, if present, and then create those again. Then it will invoke cmake from these folders respectively, targeting using the system (native) toolchain and the MinGW-w64 toolchain respectively. Last but not least it will invoke the installation from these folders respectively.

So if you place this into an empty folder along with the other files this should not meddle with your data elsewhere in any way.

#/usr/bin/env bash
for i in native windows; do
        D=build-$i
        test -d $D && rm -rf $D
        mkdir $D
        [[ "$i" == "windows" ]] && TCFILE=mingw64-64bit.cmake
        ( set -x; cd $D && cmake .. -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=. ${TCFILE+-DCMAKE_TOOLCHAIN_FILE=$TCFILE} -DCMAKE_VERBOSE_MAKEFILE=ON )
        ( set -x; cd $D && cmake --build . --target install )
done

test.cpp

#ifdef _WIN32
#   if defined(test_EXPORTS)
#       define TEST_API  __declspec(dllexport)
#   else
#       define TEST_API __declspec(dllimport)
#   endif
#else
#   define TEST_API
#endif

TEST_API void SomeFunction()
{
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.12)
set(PRJNAME test)
set(TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/install-target")
project(${PRJNAME})

add_library(${PRJNAME} SHARED test.cpp)

if(CMAKE_SYSTEM_NAME MATCHES "Windows")
    set(DLL_PREFIX)
    set(DLL_POSTFIX Lib)
else()
    set(DLL_PREFIX lib)
    set(DLL_POSTFIX)
endif()

set_target_properties(
    ${PRJNAME}
    PROPERTIES
    PREFIX "${DLL_PREFIX}"
    IMPORT_PREFIX "${DLL_PREFIX}"
    DEBUG_POSTFIX "${DLL_POSTFIX}"
    RELEASE_POSTFIX "${DLL_POSTFIX}"
    CXX_STANDARD 11
    CXX_EXTENSIONS OFF
    CXX_STANDARD_REQUIRED ON
    POSITION_INDEPENDENT_CODE 1
)

set(CMAKE_INSTALL_PREFIX ${TARGET_DIR})
set(CMAKE_INSTALL_LIBDIR lib)
set(CMAKE_INSTALL_INCLUDEDIR include)

install(
    TARGETS ${PRJNAME}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    COMPONENT library
)

mingw64-64bit.cmake

set(CMAKE_SYSTEM_NAME Windows)
set(TOOLCHAIN_PREFIX x86_64-w64-mingw32)

set(CMAKE_C_COMPILER   ${TOOLCHAIN_PREFIX}-gcc-posix)
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}-g++-posix)
set(CMAKE_RC_COMPILER  ${TOOLCHAIN_PREFIX}-windres)

set(CMAKE_FIND_ROOT_PATH  /usr/${TOOLCHAIN_PREFIX})

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

Solution

  • install(
        TARGETS ${PRJNAME}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
        COMPONENT library
    )
    

    According to the install command documentation the DLL file is considered a runtime object. I tested the example on Ubuntu 14.04.