I am trying to follow the answer given here to combine multiple static libraries into a single archive under MinGW64. Specifically, I use CMake and specify the following command:
add_custom_command(
OUTPUT ${COMBINED_CORE_LIB_NAME}
COMMAND ${AR} -crs ${COMBINED_CORE_LIB_NAME} ${CORE_LIB_TARGET_FILES}
DEPENDS ${DILIGENT_CORE_INSTALL_LIBS_LIST}
COMMENT "Combining core libraries..."
)
Also, following the recommendation from here, I do not use stock ar, but rather cross ar:
find_program(AR NAMES x86_64-w64-mingw32-gcc-ar)
However, no matter what I do, ar refuses to generate index and every time I am trying to link against the produced library, I get this error:
error adding symbols: Archive has no index; run ranlib to add one
Running ranlib as suggested either stock one or x86_64-w64-mingw32-gcc-ranlib makes no difference.
I spent 15 minutes to make this work with MSVC and lib.exe and have been struggling for 8 hours with MinGW. Any suggestion would be highly appreciated.
[Edit]: It turned out that this problem is not really specific to MinGW and also happens on Linux in a very similar fashion.
As it turned out, it is not possible to merge multiple .a files into a single .a library using ar utility: it refuses to generate index. A working solution is to unpack all object files from all static libraries and then repack them into a combined library.
Below is the CMake instructions I ended up with:
set(COMBINED_CORE_LIB_NAME "${CMAKE_STATIC_LIBRARY_PREFIX}DiligentCore${CMAKE_STATIC_LIBRARY_SUFFIX}")
foreach(CORE_LIB ${DILIGENT_CORE_INSTALL_LIBS_LIST})
list(APPEND CORE_LIB_TARGET_FILES $<TARGET_FILE:${CORE_LIB}>)
endforeach(CORE_LIB)
if(MSVC)
add_custom_command(
OUTPUT ${COMBINED_CORE_LIB_NAME}
COMMAND ${LIB_EXE} /OUT:${COMBINED_CORE_LIB_NAME} ${CORE_LIB_TARGET_FILES}
DEPENDS ${DILIGENT_CORE_INSTALL_LIBS_LIST}
COMMENT "Combining core libraries..."
)
add_custom_target(DiligentCore-static ALL DEPENDS ${COMBINED_CORE_LIB_NAME})
else()
if(PLATFORM_WIN32)
# do NOT use stock ar on MinGW
find_program(AR NAMES x86_64-w64-mingw32-gcc-ar)
else()
set(AR ${CMAKE_AR})
endif()
if(AR)
add_custom_command(
OUTPUT ${COMBINED_CORE_LIB_NAME}
# Delete all object files from current directory
COMMAND ${CMAKE_COMMAND} -E remove "*${CMAKE_C_OUTPUT_EXTENSION}"
DEPENDS ${DILIGENT_CORE_INSTALL_LIBS_LIST}
COMMENT "Combining core libraries..."
)
# Unpack all object files from all targets to current directory
foreach(CORE_LIB_TARGET ${CORE_LIB_TARGET_FILES})
add_custom_command(
OUTPUT ${COMBINED_CORE_LIB_NAME}
COMMAND ${AR} -x ${CORE_LIB_TARGET}
APPEND
)
endforeach()
# Pack object files to a combined library and delete them
add_custom_command(
OUTPUT ${COMBINED_CORE_LIB_NAME}
COMMAND ${AR} -crs ${COMBINED_CORE_LIB_NAME} "*${CMAKE_C_OUTPUT_EXTENSION}"
COMMAND ${CMAKE_COMMAND} -E remove "*${CMAKE_C_OUTPUT_EXTENSION}"
APPEND
)
add_custom_target(DiligentCore-static ALL DEPENDS ${COMBINED_CORE_LIB_NAME})
else()
message("ar command is not found")
endif()
endif()
if(TARGET DiligentCore-static)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${COMBINED_CORE_LIB_NAME}"
DESTINATION "${DILIGENT_CORE_INSTALL_DIR}/lib/"
)
set_target_properties(DiligentCore-static PROPERTIES
FOLDER Core
)
else()
message("Unable to find librarian tool. Combined DiligentCore static library will not be produced.")
endif()
Related
I have a very similar problem to one described on the cmake mailing list where we have a project dependent on many static libraries (all built from source in individual submodules, each with their own CMakeLists.txt describing the build process for each library) that I'd like to combine into a single static library for release to the consumers. The dependencies of my library are subject to change, and I do not want to burden developers further down the chain with those changes. The neat solution would be to bundle all of the libs into one single lib.
Interestingly, the target_link_libraries command does not combine all of the statics when setting the target to mylib and using it like so . .
target_link_libraries(mylib a b c d)
However, bizarrely, if I make the mylib project a submodule of an executable project, and only link against mylib in the top level executable CMAkeLists.txt, the library does seem to be combined. I.e. mylib is 27 MB, instead of the 3MB when I set the target to only build mylib.
There are solutions describing unpacking of the libs into object files and recombining (here, and here), but this seems remarkably clumsy when CMake seems perfectly capable of automatically merging the libs as described in the above example. It there a magic command I'm missing, or a recommended elegant way of making a release library?
Given the most simple working example I can think of: 2 classes, a and b, where a depends on b . .
a.h
#ifndef A_H
#define A_H
class aclass
{
public:
int method(int x, int y);
};
#endif
a.cpp
#include "a.h"
#include "b.h"
int aclass::method(int x, int y) {
bclass b;
return x * b.method(x,y);
}
b.h
#ifndef B_H
#define B_H
class bclass
{
public:
int method(int x, int y);
};
#endif
b.cpp
#include "b.h"
int bclass::method(int x, int y) {
return x+y;
}
main.cpp
#include "a.h"
#include <iostream>
int main()
{
aclass a;
std::cout << a.method(3,4) << std::endl;
return 0;
}
It is possible to compile these into separate static libs, and then combine the static libs using a custom target.
cmake_minimum_required(VERSION 2.8.7)
add_library(b b.cpp b.h)
add_library(a a.cpp a.h)
add_executable(main main.cpp)
set(C_LIB ${CMAKE_BINARY_DIR}/libcombi.a)
add_custom_target(combined
COMMAND ar -x $<TARGET_FILE:a>
COMMAND ar -x $<TARGET_FILE:b>
COMMAND ar -qcs ${C_LIB} *.o
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS a b
)
add_library(c STATIC IMPORTED GLOBAL)
add_dependencies(c combined)
set_target_properties(c
PROPERTIES
IMPORTED_LOCATION ${C_LIB}
)
target_link_libraries(main c)
It also works just fine using Apple's libtool version of the custom target . . .
add_custom_target(combined
COMMAND libtool -static -o ${C_LIB} $<TARGET_FILE:a> $<TARGET_FILE:b>
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
DEPENDS a b
)
Still seams as though there should be a neater way . .
You can use this function to join any number of libraries.
function(combine_archives output_archive list_of_input_archives)
set(mri_file ${TEMP_DIR}/${output_archive}.mri)
set(FULL_OUTPUT_PATH ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${output_archive}.a)
file(WRITE ${mri_file} "create ${FULL_OUTPUT_PATH}\n")
FOREACH(in_archive ${list_of_input_archives})
file(APPEND ${mri_file} "addlib ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/lib${in_archive}.a\n")
ENDFOREACH()
file(APPEND ${mri_file} "save\n")
file(APPEND ${mri_file} "end\n")
set(output_archive_dummy_file ${TEMP_DIR}/${output_archive}.dummy.cpp)
add_custom_command(OUTPUT ${output_archive_dummy_file}
COMMAND touch ${output_archive_dummy_file}
DEPENDS ${list_of_input_archives})
add_library(${output_archive} STATIC ${output_archive_dummy_file})
add_custom_command(TARGET ${output_archive}
POST_BUILD
COMMAND ar -M < ${mri_file})
endfunction(combine_archives)
It has the benefits of using add_custom_command and not add_custom_target. This way, the library (and it's dependencies) are only built when needed and not every time.
The drawback is the print of the generation of the dummy file.
This doesn't directly answer the question, but I found it useful:
https://cristianadam.eu/20190501/bundling-together-static-libraries-with-cmake/
Basic, define a CMake function that will collect all the static libs required by a target and combine them into a single static lib:
add_library(awesome_lib STATIC ...);
bundle_static_library(awesome_lib awesome_lib_bundled)
Here's a copy & paste of the actual function:
function(bundle_static_library tgt_name bundled_tgt_name)
list(APPEND static_libs ${tgt_name})
function(_recursively_collect_dependencies input_target)
set(_input_link_libraries LINK_LIBRARIES)
get_target_property(_input_type ${input_target} TYPE)
if (${_input_type} STREQUAL "INTERFACE_LIBRARY")
set(_input_link_libraries INTERFACE_LINK_LIBRARIES)
endif()
get_target_property(public_dependencies ${input_target} ${_input_link_libraries})
foreach(dependency IN LISTS public_dependencies)
if(TARGET ${dependency})
get_target_property(alias ${dependency} ALIASED_TARGET)
if (TARGET ${alias})
set(dependency ${alias})
endif()
get_target_property(_type ${dependency} TYPE)
if (${_type} STREQUAL "STATIC_LIBRARY")
list(APPEND static_libs ${dependency})
endif()
get_property(library_already_added
GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency})
if (NOT library_already_added)
set_property(GLOBAL PROPERTY _${tgt_name}_static_bundle_${dependency} ON)
_recursively_collect_dependencies(${dependency})
endif()
endif()
endforeach()
set(static_libs ${static_libs} PARENT_SCOPE)
endfunction()
_recursively_collect_dependencies(${tgt_name})
list(REMOVE_DUPLICATES static_libs)
set(bundled_tgt_full_name
${CMAKE_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${bundled_tgt_name}${CMAKE_STATIC_LIBRARY_SUFFIX})
if (CMAKE_CXX_COMPILER_ID MATCHES "^(Clang|GNU)$")
file(WRITE ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in
"CREATE ${bundled_tgt_full_name}\n" )
foreach(tgt IN LISTS static_libs)
file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in
"ADDLIB $<TARGET_FILE:${tgt}>\n")
endforeach()
file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "SAVE\n")
file(APPEND ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in "END\n")
file(GENERATE
OUTPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar
INPUT ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar.in)
set(ar_tool ${CMAKE_AR})
if (CMAKE_INTERPROCEDURAL_OPTIMIZATION)
set(ar_tool ${CMAKE_CXX_COMPILER_AR})
endif()
add_custom_command(
COMMAND ${ar_tool} -M < ${CMAKE_BINARY_DIR}/${bundled_tgt_name}.ar
OUTPUT ${bundled_tgt_full_name}
COMMENT "Bundling ${bundled_tgt_name}"
VERBATIM)
elseif(MSVC)
find_program(lib_tool lib)
foreach(tgt IN LISTS static_libs)
list(APPEND static_libs_full_names $<TARGET_FILE:${tgt}>)
endforeach()
add_custom_command(
COMMAND ${lib_tool} /NOLOGO /OUT:${bundled_tgt_full_name} ${static_libs_full_names}
OUTPUT ${bundled_tgt_full_name}
COMMENT "Bundling ${bundled_tgt_name}"
VERBATIM)
else()
message(FATAL_ERROR "Unknown bundle scenario!")
endif()
add_custom_target(bundling_target ALL DEPENDS ${bundled_tgt_full_name})
add_dependencies(bundling_target ${tgt_name})
add_library(${bundled_tgt_name} STATIC IMPORTED)
set_target_properties(${bundled_tgt_name}
PROPERTIES
IMPORTED_LOCATION ${bundled_tgt_full_name}
INTERFACE_INCLUDE_DIRECTORIES $<TARGET_PROPERTY:${tgt_name},INTERFACE_INCLUDE_DIRECTORIES>)
add_dependencies(${bundled_tgt_name} bundling_target)
endfunction()
If the libraries you are trying to merge are from third parties, then (following learnvst example) this code take care of possible .o file replacements (if for instance both liba and libb have a file name zzz.o)
## Create static library (by joining the new objects and the dependencies)
ADD_LIBRARY("${PROJECT_NAME}-static" STATIC ${SOURCES})
add_custom_command(OUTPUT lib${PROJECT_NAME}.a
COMMAND rm ARGS -f *.o
COMMAND ar ARGS -x ${CMAKE_BINARY_DIR}/lib${PROJECT_NAME}-static.a
COMMAND rename ARGS 's/^/lib${PROJECT_NAME}-static./g' *.o
COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
COMMAND ar ARGS -x ${CMAKE_SOURCE_DIR}/lib/a/liba.a
COMMAND rename ARGS 's/^/liba./g' *.o
COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
COMMAND ar ARGS -x ${CMAKE_SOURCE_DIR}/lib/b/libb.a
COMMAND rename ARGS 's/^/libb./g' *.o
COMMAND rename ARGS 's/\\.o/.otmp/g' *.o
COMMAND rename ARGS 's/\\.otmp/.o/g' *.otmp
COMMAND ar ARGS -r lib${PROJECT_NAME}.a *.o
COMMAND rm ARGS -f *.o
DEPENDS "${PROJECT_NAME}-static")
add_custom_target(${PROJECT_NAME} ALL DEPENDS lib${PROJECT_NAME}.a)
Otherwise, if the libraries are yours, you should use CMake OBJECT libraries, that are a pretty good mechanism to get them merged.
https://cmake.org/pipermail/cmake/2018-September/068263.html
It seems CMake doesn't support that.
The proper way to do this is not to fudge around with combining static libraries, but to provide CMake Config files to the user that contain all the necessary bits that link everything the way it's supposed to be linked.
CMake can be used to generate these files, or generate pkg-config files, and probably other formats of "tell me how to link with this and that library" tools.
Chances are some users will be interested in what libraries yours link to, and they might even be using their own copies/versions of the same exact libraries when linking yours. It is in exactly this case that your solution is terrible and prevents users from integrating multiple pieces of code, because you decided they must absolutely use your copy of that dependency (which is what you do when you combine static library dependencies into one static library).
I created a solution based on zbut's answer, but it supports retrieving the input library paths from given targets and also supports multi-configuration generators like Ninja Multi-Config:
# Combine a list of library targets into a single output archive
# Usage:
# combine_archives(output_archive_name input_target1 input_target2...)
function(combine_archives output_archive)
# Generate the MRI file for ar to consume.
# Note that a separate file must be generated for each build configuration.
set(mri_file ${CMAKE_BINARY_DIR}/$<CONFIG>/${output_archive}.mri)
set(mri_file_content "create ${CMAKE_BINARY_DIR}/$<CONFIG>/lib${output_archive}.a\n")
FOREACH(in_target ${ARGN})
string(APPEND mri_file_content "addlib $<TARGET_FILE:${in_target}>\n")
ENDFOREACH()
string(APPEND mri_file_content "save\n")
string(APPEND mri_file_content "end\n")
file(GENERATE
OUTPUT ${mri_file}
CONTENT ${mri_file_content}
)
# Create a dummy file for the combined library
# This dummy file depends on all the input targets so that the combined library is regenerated if any of them changes.
set(output_archive_dummy_file ${CMAKE_BINARY_DIR}/${output_archive}.dummy.cpp)
add_custom_command(OUTPUT ${output_archive_dummy_file}
COMMAND touch ${output_archive_dummy_file}
DEPENDS ${ARGN})
add_library(${output_archive} STATIC ${output_archive_dummy_file})
# Add a custom command to combine the archives after the static library is "built".
add_custom_command(TARGET ${output_archive}
POST_BUILD
COMMAND ar -M < ${mri_file}
COMMENT "Combining static libraries for ${output_archive}"
)
endfunction(combine_archives)
Usage to generate a libTargetC.a from libTargetA.a and libTargetB.a would be something like:
add_library(TargetA STATIC ...)
add_library(TargetB STATIC ...)
combine_archives(TargetC TargetA TargetB)
I want to manipulate the linker call in my linker trampoline python script, but for the life of me I cannot get CMake to respect the CMAKE_CXX_CREATE_SHARED_LIBRARY value. It always uses the CMAKE_CXX_COMPILER for linking. If I switch to an add_executable and instead use CMAKE_CXX_LINK_EXECUTABLE this works perfectly. Is there anyway to convince CMake to respect the setting of CMAKE_CXX_CREATE_SHARED_LIBRARY?
A trivial CMakeLists.txt
cmake_minimum_required( VERSION 3.12 )
project( mylib )
include( CMakePrintSystemInformation )
add_library( mylib SHARED mylib.cpp )
A toolchain excerpt
I've tried this in both a toolchain file and an -C pre-cache option.
set( CMAKE_LINKER "/path/to/linker_trampoline.py" )
set( CMAKE_CXX_CREATE_SHARED_LIBRARY "<CMAKE_LINKER> --CMAKE_CXX_COMPILER <CMAKE_CXX_COMPILER> --CMAKE_SHARED_LIBRARY_CXX_FLAGS <CMAKE_SHARED_LIBRARY_CXX_FLAGS> --LANGUAGE_COMPILE_FLAGS <LANGUAGE_COMPILE_FLAGS> --LINK_FLAGS <LINK_FLAGS> --CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS <CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS> --SONAME_FLAG <SONAME_FLAG> --TARGET_SONAME <TARGET_SONAME> --TARGET <TARGET> --OBJECTS <OBJECTS> --LINK_LIBRARIES <LINK_LIBRARIES>" CACHE STRING "" FORCE )
Works as expected with CMake version 3.10
I have tried your example. No issues with your intention. The only differences are, I tried this with an older version of CMake and placed the variable declarations directly in the file CMakeLists.txt (Should be placed in a toolchain file).
CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(mylib)
include(CMakePrintSystemInformation)
set (CMAKE_LINKER "/path/to/link.sh")
set (CMAKE_CXX_CREATE_SHARED_LIBRARY "<CMAKE_LINKER> <OBJECTS>")
add_library(mylib SHARED mylib.cpp)
I have shortened the declaration of CMAKE_CXX_CREATE_SHARED_LIBRARY here because I only pass the object files to the following script.
link.sh: (Simple print the object files):
#!/bin/bash
echo "$#"
Output:
$ cmake CMakeLists.txt
$ make
[ 50%] Building CXX object CMakeFiles/mylib.dir/mylib.cpp.o
[100%] Linking CXX shared library libmylib.so
CMakeFiles/mylib.dir/mylib.cpp.o
[100%] Built target mylib
$
link.sh will be definitely executed. You an ignore the formal message about linking libmylib.so. The library will be never created (with my linker command).
CMake version:
$ cmake --version
cmake version 3.10.2
CMake suite maintained and supported by Kitware (kitware.com/cmake).
$
I have seen you have already created an issue or rather a feature request on the CMake project site.
Response from Kitware
Brad King commented:
CMAKE_CXX_LINK_EXECUTABLE and CMAKE_CXX_CREATE_SHARED_LIBRARY are not meant to be set by toolchain files directly (and if either seems to work it is only by accident). They are not meant to be cached either. They are set in CMake's platform/compiler information modules which are loaded after the toolchain file. For toolchain files meant to cross-compile to a custom platform, they can set CMAKE_SYSTEM_NAME to the name of the target platform, set CMAKE_MODULE_PATH to a custom directory, and then add Platform/<os>*.cmake modules with such settings for that platform.
To hook in to the link line for an existing platform another approach will be needed. #18316 is related.
I have a small test program that I want to link to GLFW. I am currently able to download, configure and build the .dll using ExternalProject_Add command. When I build my test program I get an executable that doesn't run because it can't find the .dll. If I manually copy the .dll to the directory where the executable is, it runs just fine.
How do I get my executable to properly link to the library?
Is there a way to automatically copy the .dll to where it needs to be?
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-test)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/app )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
# Include OpenGL
find_package(OpenGL REQUIRED)
if (OPENGL_FOUND)
include_directories(${OPENGL_INCLUDE_DIR})
link_libraries(${OPENGL_LIBRARIES})
endif()
# Add directories for library linkage
set(GLFW_LIB_DIR ${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX/src)
link_directories(${GLFW_LIB_DIR})
# Download and unpack dependencies at configure time
configure_file(deps-CMakeLists.txt downloads/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
execute_process(COMMAND ${CMAKE_COMMAND} --build .
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/downloads)
add_subdirectory(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX
${CMAKE_BINARY_DIR}/downloads/deps/Build/GLFW_EX
EXCLUDE_FROM_ALL )
include_directories(${CMAKE_BINARY_DIR}/downloads/deps/Source/GLFW_EX/include)
add_executable(GLFW-test src/GLFW-test.cpp)
target_link_libraries (GLFW-test glfw3 ${OPENGL_LIBRARIES})
add_custom_command(TARGET GLFW-test POST_BUILD # Adds a post-build event to MyTest
COMMAND ${CMAKE_COMMAND} -E copy_if_different # which executes "cmake - E copy_if_different..."
"${GLFW_LIB_DIR}/glfw3.dll" # <--this is in-file
$<TARGET_FILE_DIR:GLFW-test>) # <--this is out-file path
dep-CMakeLists.txt:
cmake_minimum_required (VERSION 2.8)
project (GLFW-dl)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_BASE "./deps/")
# Include GLFW
ExternalProject_Add (
GLFW_EX
GIT_REPOSITORY "https://github.com/glfw/glfw.git"
GIT_TAG "master"
CMAKE_ARGS -DGLFW_BUILD_EXAMPLES=OFF
-DGLFW_BUILD_TESTS=OFF
-DGLFW_BUILD_DOCS=OFF
-DGLFW_INSTALL=OFF
-DBUILD_SHARED_LIBS=ON
UPDATE_COMMAND ""
TEST_COMMAND "" )
UPDATE:
The way I am using ExternalProject_Add is described on this site: https://crascit.com/2015/07/25/cmake-gtest/
It allows the external projects to be configured and built only once during the configure phase of my project. I have changed the directories around a bit from their test program to make things a little easier for when I eventually add more external projects. The test project on the site does not seem to account for dynamic libraries which is what I am trying to do.
UPDATE 2:
I've added 2 set commands to help clean up the build directory towards the top of the CMakeLists file. I also added a command at the bottom which copies .dll that is built from the ExternalProject_Add command to where I need it (next to the final executable). That seems to work for Windows, but it seems a bit hacky and doesn't resolve the errors in my IDE, which is currently Eclipse. Is there still a better way to do this?
Helpful Related topics:
Setting the RPATH for external projects?
Cmake on Windows doesn't add shared library paths (works on linux)
How to copy DLL files into the same folder as the executable using CMake?
How do I get my executable to properly link to the library?
As your second link states, there is no other way than to have .dll in the same directory as executable.
Is there a way to automatically copy the .dll to where it needs to be?
In you main project you already use variable CMAKE_RUNTIME_OUTPUT_DIRECTORY for setup directory where executables and .dlls should be placed after build. You can pass this variable to ExternalProject_add for force it to use same conventions:
ExternalProject_Add (...
CMAKE_ARGS -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
...
)
What is the best way to ensure that, when it comes time to package my program, the library is available to use and easily accessible?
Packaging just uses install-tree of your project. So it is sufficient to install executables and libraries into same location:
set(INSTALL_RUNTIME_DIR bin)
install(TARGETS GLFW-test
RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR}
)
install(FILES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/glfw3.dll
DESTINATION ${INSTALL_RUNTIME_DIR}
)
Note, that target GLFW_EX obtained from external project has no special type(like executable or library), so you need to install its deliverables using plain filenames.
I am working with gcc(cygwin), gnu make, windows 7 and cmake.
my cmake testprojekt has the following structure
rootdir
|-- App
| |-- app.cpp
| +-- CMakeLists.txt
|-- Lib
| |-- lib.cpp
| |-- CMakeLists.txt
|-- MakeFileProject
+ CMakeLists.txt
rootdir/App/app.cpp:
#include<string>
void printThemMessageToScreen(std::string input);//prototype
int main(int argc,char **argv){
printThemMessageToScreen("this will be displayed by our lib");
return 0;
}
rootdir/Lib/lib.cpp:
#include<iostream>
#include<string>
void printThemMessageToScreen(std::string input){
std::cout<<input;
}
rootdir/CMakeLists.txt:
cmake_minimum_required(VERSION 2.6)
project(TestProject)
add_subdirectory(App)
add_subdirectory(Lib)
rootdir/Lib/CMakeLists.txt:
add_library(Lib SHARED lib.cpp)
rootdir/App/CMakeLists.txt:
# Make sure the compiler can find include files from our Lib library.
include_directories (${LIB_SOURCE_DIR}/Lib)
# Make sure the linker can find the Lib library once it is built.
link_directories (${LIB_BINARY_DIR}/Lib)
# Add executable called "TestProjectExecutable" that is built from the source files
add_executable (TestProjectExecutable app.cpp)
# Link the executable to the lib library.
target_link_libraries (TestProjectExecutable Lib)
Now, when i run cmake and make, everything will get generated & built with no errors, but when i try to execute the binary, it will fail because the library which was generated could not be found.
BUT: when i copy the lib dll into the same directory like the app exe, it will get executed!
also: if i configure the library to be static, it will also execute.
how to tell the runtime linker where to look for my dll?
UPDATE:
Solution according to the Method proposed by User Vorren:
I opened up the registry editor, and navigated to the following Key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
, here i created a new key with the name of my Applikation:
in this case : TestProjectExecutable.exe
after that, the (default) value was set to the full path of TestProjectExecutable.exe including the filename and extension. Then i created another String Value called "Path" and set the value to the folder where the dll was located:
Your problem lies not with linker or compiler, but with the way Windows searches for DLL's.
The OS will use the following algorithm to locate the required DLL's:
Look in:
The directories listed in the Application-specific Path registry key;
The directory where the executable module for the current process is located;
The current directory;
The Windows system directory;
The Windows directory;
The directories listed in the PATH environment variable;
Thus you have two reasonable options if you don't want to clutter the OS directories with your app-specific dll:
Create an app-specific Path registry entry (I would go with this option);
Put your DLL in the same folder as your EXE;
Modify the PATH variable (but why would you do that, if you can go with option 1?);
A solution I prefer that hasn't really been mentioned, is build your shared-libs into the same directory as your executables. This tends to be a much simpler solution.
One way to do this with cmake is
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
Or you can also set output directories based on build flavours.
See how do I make cmake output into a 'bin' dir?
I discovered (what I believe to be) quite a nice way of handling this. It follows the approach of adding the .dll to the same directory as the .exe. You can do it in CMake like so:
if (WIN32)
# copy the .dll file to the same folder as the executable
add_custom_command(
TARGET <app-target> POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:<lib-target>>
$<TARGET_FILE_DIR:<app-target>)
endif()
where app-target is the name of the application or library you're building (created through add_executable or add_library) and lib-target is the imported library brought in with find_package.
# full example
cmake_minimum_required(VERSION 3.14)
project(my-app-project VERSION 0.0.1 LANGUAGES CXX)
find_package(useful-library REQUIRED)
add_executable(my-application main.cpp)
target_link_libraries(my-application PUBLIC useful-library::useful-library)
if (WIN32)
# copy the .dll file to the same folder as the executable
add_custom_command(
TARGET my-application POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
$<TARGET_FILE_DIR:useful-library::useful-library>
$<TARGET_FILE_DIR:my-application>)
endif()
I tried the option 1 from accepted answer (by pdeschain).
I even created a cmake hook to register paths of linked libraries automatically
function (xtarget_link_libraries target libs) # same as target_link_libraries but with additional improvements to allow windows find the library at runtime
LIST(REMOVE_AT ARGV 0)
SET(LIBS ${ARGV}) # this is to pass list into this function
target_link_libraries(${target} ${LIBS}) # call standard routine
if(WIN32)
set(TFILE ".")
get_property(slibs TARGET ${target} PROPERTY all_libs) # recall libs linked before
set(LIBS ${slibs};${LIBS})
set_property(TARGET ${target} PROPERTY all_libs ${LIBS}) # save all libs
FOREACH(lib ${LIBS}) # compose a list of paths
set(TFILE "${TFILE};$<TARGET_LINKER_FILE_DIR:${lib}>")
ENDFOREACH()
#add reg key
add_custom_command(TARGET ${target} POST_BUILD COMMAND reg add "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${target}.exe" /v "Path" /d "${TFILE}" /f )
endif()
endfunction()
Can be used as xtarget_link_libraries(test lib1 lib2). The application will be able to find dynamic libraries at their absolute paths.
BUT, there is a big problem with this, that the App Paths mechanism https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121(v=vs.85).aspx#appPaths
does not allow to have different entries for say 'Debug/test.exe' and 'Release/test.exe'. So to me this is a poor option.
You may add the following line to fill the Default key as path to the program as suggested in the post.
add_custom_command(TARGET ${target} POST_BUILD COMMAND reg add "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\${target}.exe" /ve /d "$<TARGET_FILE:${target}>" /f )
Now you can enjoy running test.exe from anywhere in the system... I guess my next try will be option
Create symbolic links to dlls with cmake.
I have a project that needs access to an ELF file embedded into the executable in a special section.
I was handcrafting Makefiles before and simply had a shell script where I used objcopy to copy the target I wanted to embed into an .o file, then link to this file in the executable.
# Create a new section and copy the binary there ($1=input $2=output name)
objcopy --input-target binary --output-target elf64-x86-64 \
--binary-architecture i386 $1 $2.o
Now I want to get rid of the custom Makefiles and use CMake to generate them. However, I don't see an easy way to link to such a file. I am able to create and add this file, but not to link against it:
# Invoke script to package module as a library
add_custom_command(OUTPUT ${PACKAGED_FILE}
COMMAND ./package.sh ${MODULE_FILE} ${PACKAGED_FILE}
WORKING_DIRECTORY ${MODULE_DIR}
DEPENDS ${MODULE_FILE}
COMMENT packaging file into ELF object
VERBATIM
)
add_custom_target(${PACKAGED_NAME} ALL DEPENDS ${PACKAGED_FILE})
I have tried to add it with:
target_link_libraries(binary ${PROJECT_BINARY_DIR}/${PACKAGED_FILE})
However, this fails because the file isn't there yet. It will be, but CMake doesn't know that. Adding the target name as a link library doesn't help either because it can't be found. Adding it as a also dependency doesn't help. Does anyone have an idea how this could be accomplished?
We are doing a similar thing in our project - the following part of our CMakeLists.txt does the trick:
set(PROJECT_EMBED_OBJ_FILES "")
set(PROJECT_EMBED_FILES "file1.elf" "file2.elf")
foreach(FILENAME ${PROJECT_EMBED_FILES})
get_filename_component(FILENAME_ONLY ${FILENAME} NAME)
get_filename_component(FILEPATH_ONLY ${FILENAME} PATH)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME_ONLY}.o
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/${FILEPATH_ONLY}
COMMAND ${CMAKE_OBJCOPY}
ARGS -I binary -O elf64-x86-64 -B i386 ${FILENAME_ONLY} ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME_ONLY}.o )
list(APPEND PROJECT_EMBED_OBJ_FILES ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME_ONLY}.o)
message(STATUS "Objcopy'ing ${FILENAME}")
endforeach(FILENAME)
And then in the call to add_executable:
add_executable(projectname ${PROJECT_SOURCES} ${PROJECT_EMBED_OBJ_FILES})
You may try
add_custom_command(TARGET $(PROJECT_NAME).elf
POST_BUILD
COMMAND ${CMAKE_OBJCOPY} ARGS -O binary ${PROJECT_NAME}.elf \
${PROJECT_NAME}.bin)
Put this after your add_executable().
The POST_BUILD means execute after build.