CMake append objects from different CMakeLists.txt into one library - build

I would like to create a single library from objects from multiple sub-directories, each one containing their own CMakeLists.txt with OBJECT library trick to have multiple targets with different compile options.
Here are the files:
project_dir
|--- subdir1
| |--- src1.c
| |--- CMakeLists.txt
|--- subdir2
| |--- src2.c
| |--- CMakeLists.txt
|--- CMakeLists.txt
Contents of all CMakeLists.txt
// project_dir/CMakeLists.txt
// what to put here? to make one single library (mainLib)
// project_dir/subdir1/CMakeLists.txt
add_library(lib11 OBJECT src1.c)
set_target_properties(lib11 PROPERTIES COMPILE_FLAGS "some-flags11")
add_library(lib12 OBJECT src1.c)
set_target_properties(lib12 PROPERTIES COMPILE_FLAGS "some-flags12")
// here I would like to add lib11:objects and lib12:objects to mainLib
// how should it be done?
// project_dir/subdir2/CMakeLists.txt
// *** similar to subdir1 but with src2.c that creates lib21 and lib22
// here I would like to add lib21:objects and lib22:objects to mainLib
// how should it be done?
Can it be done platform independently? Thanks.
EDIT:
Based on CMake: how create a single shared library from all static libraries of subprojects?, I can change the cmake files to the following, but still doesn't solve my problem.
// project_dir/subdir1/CMakeLists.txt
add_library(lib11 OBJECT src1.c)
set_target_properties(lib11 PROPERTIES COMPILE_FLAGS "some-flags11")
add_library(lib12 OBJECT src1.c)
set_target_properties(lib12 PROPERTIES COMPILE_FLAGS "some-flags12")
add_library(lib1 STATIC $<TARGET_OBJECTS:lib11> $<TARGET_OBJECTS:lib12>)
// Above add_library cannot be OBJECT which would fix my problem.
// how to append the lib1 (lib11 and lib12) to mainLib?
EDIT:
Updating my post with attempt to chain interface libraries as suggested in the answer.
add_library(lib11 OBJECT test1/sub1/src1.c)
set_target_properties(lib11 PROPERTIES COMPILE_FLAGS "-DF1")
add_library(lib12 OBJECT test1/sub1/src1.c)
set_target_properties(lib12 PROPERTIES COMPILE_FLAGS "-DF2")
add_library(lib1 INTERFACE)
target_sources(lib1 INTERFACE $<TARGET_OBJECTS:lib11>)
target_sources(lib1 INTERFACE $<TARGET_OBJECTS:lib12>)
add_library(lib21 OBJECT test1/sub2/src2.c)
set_target_properties(lib21 PROPERTIES COMPILE_FLAGS "-DF1")
add_library(lib22 OBJECT test1/sub2/src2.c)
set_target_properties(lib22 PROPERTIES COMPILE_FLAGS "-DF2")
add_library(lib2 INTERFACE)
target_sources(lib2 INTERFACE $<TARGET_OBJECTS:lib21>)
target_sources(lib2 INTERFACE $<TARGET_OBJECTS:lib22>)
add_library(PARENT INTERFACE)
target_link_libraries(PARENT INTERFACE lib1)
target_link_libraries(PARENT INTERFACE lib2)
add_library(CORE OBJECT src.c)
add_library(GPARENT STATIC $<TARGET_OBJECTS:CORE>)
target_link_libraries(GPARENT INTERFACE PARENT)

Several OBJECT libraries can be incorporated into one INTERFACE library:
# Create lib1 OBJECT library with some sources, compile flags and so.
add_library(lib1 OBJECT ...)
# Create lib2 OBJECT library with some sources, compile flags and so.
add_library(lib2 OBJECT ...)
# Create INTERFACE library..
add_library(libs INTERFACE)
# .. which combines OBJECT libraries
target_sources(libs INTERFACE $<TARGET_OBJECTS:lib1> $<TARGET_OBJECTS:lib2>)
Resulted library can be used with standard target_link_libraries:
add_library(mainLib <some-source>)
target_link_libraries(mainLib libs)
It is possible to combine INTERFACE libraries futher:
# Create libs_another INTERFACE library like 'libs' one
add_library(libs_another INTERFACE)
..
# Combine 'libs' and 'libs_another' together
add_library(upper_level_libs INTERFACE)
target_link_libraries(upper_level_libs INTERFACE libs libs_another)
Resulted libraries' "chain" could be of any length.
While this way for combination of OBJECT libraries looks hacky, it is actually allowed by CMake documentation:
Although object libraries may not be named directly in calls to the target_link_libraries() command, they can be "linked" indirectly by using an Interface Library whose INTERFACE_SOURCES target property is set to name $<TARGET_OBJECTS:objlib>.

I just collect objects from all places using set with PARENT_SCOPE.
root CMakeLists.txt:
set(OBJECTS)
add_subdirectory(lib1)
add_subdirectory(lib2)
add_library(lib STATIC ${OBJECTS})
CMakeLists.txt in subdirectories:
add_subdirectory(lib11)
add_library(${PROJECT_NAME} OBJECT src1.c)
list(APPEND OBJECTS $<TARGET_OBJECTS:${PROJECT_NAME}>)
set(OBJECTS ${OBJECTS} PARENT_SCOPE)

Related

How to create a shared library using object library in CMake

I have two shared libraries each one having its own CMakeLists.txt. The directory structure is like this.
main_dir
|--- subdir
| |--- src1.cpp
| |--- src2.cpp
| |--- src3.cpp
| |--- CMakeLists.txt
|--- src11.cpp
|--- CMakeLists.txt
Currently, I am able to build both main library and sub library (say main.so and sub.so).
The CMakeLists.txt for both looks as below.
main_dir/CMakeLists.txt
option(BUILD_SHARED_LIBS "Build the shared library" ON)
if(BUILD_SHARED_LIBS)
add_library(mainlib SHARED)
endif()
target_sources(mainlib PRIVATE
src11.cpp
)
add_subdirectory(subdir)
subdir/CMakeLists.txt
option(BUILD_SHARED_LIBS "Build the shared library" ON)
if(BUILD_SHARED_LIBS)
add_library(sublib SHARED)
endif()
target_sources(sublib PRIVATE
src1.cpp
src2.cpp
src3.cpp)
Now I want the object file or symbols of sub library to be included in the main library as well, so that users can still use the main library alone even if they don't link their application to the sub library.
I'm new to CMake and I was trying to create an object library out of all source files in the sub_dir and link this to my mainlib.
add_library(subarchive OBJECT src1.cpp src2.cpp src3.cpp)
target_sources(mainlib INTERFACE $<TARGET_OBJECTS:subarchive>)
But It gives me error.
(add_library) No SOURCES given to target: mainlib
How can I create an object library in sub_dir and add it to both sublib and mainlib. Is there any better way to do this. Thanks.
To use an OBJECT library, link with it. That doesn't do any actual linking -- there is no "thing" that is the object library, it's just a collection of object files that CMake knows about -- but puts the object files in the target:
target_link_libraries(mainlib PRIVATE sublib)
Here is a complete example (it creates source files one.cpp and two.cpp so that it is entirely self-contained):
cmake_minimum_required(VERSION 3.18)
project(example)
if(NOT EXISTS one.cpp)
file(WRITE one.cpp "int x;")
endif()
if(NOT EXISTS two.cpp)
file(WRITE two.cpp "int y;")
endif()
add_library(sublib OBJECT one.cpp)
add_library(mainlib SHARED two.cpp)
target_link_libraries(mainlib PRIVATE sublib)
I was able to get it working by setting PARENT_SCOPE for the object library in the suddirectory. The modifications to the original code look like this.
main_dir/CMakeLists.txt
set(SUBARCHIVE_OBJECTS)
add_subdirectory(subdir)
target_link_libraries(mainlib private ${SUBARCHIVE_OBJECTS}
subdir/CMakeLists.txt
add_library(subarchive OBJECT src1.cpp src2.cpp src3.cpp)
list(APPEND SUBARCHIVE_OBJECTS ${TARGET_OBJECTS:subarchive>)
set(SUBARCHIVE_OBJECTS ${SUBARCHIVE_OBJECTS} PARENT_SCOPE)
Thanks for the responses.

CMake linking static libraries in different subdirectories into one single static library

I use CMake to build a project that consists of multiple nested static libraries .A similar but simple structure is shown in the figure below:
TestProject:
|-CMakeLists.txt
|-Main.cpp
|-level2
| | - level2.cpp
| | - level2.h
| | - CMakeLists.txt
| | - level1
| | |-level1.cpp
| | |-level1.h
| | |-CMakeLists.txt
Now, I use CMake to build static libraries for each level separately. According to my test, the static library of each layer only contains the .cpp and .h files of that layer. However, I want to combine it with the library referenced by the previous layer when the static library of each layer is generated. For example, I build the static lib of level 1 first.Then, in the CMakeLists.txt of level 2, I create the static library of level 2 depend on the static library of level 1 ( target_link_libraries(${PROJECT_NAME} LEVEL1) ), and then, I wanted to merge the libraries of level 2 and level 1 together to a new static lib file named as level1_2.lib.
Here is my CMakeLists.txt in Level1:
cmake_minimum_required(VERSION 3.5)
#projcet name
project(LEVEL1 LANGUAGES CXX)
add_library( ${PROJECT_NAME} add.cpp)
# Add the include directories of user-written sources.
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
And this is the CMakelists.txt of level2.
cmake_minimum_required(VERSION 3.5)
project(LEVEL2 LANGUAGES CXX)
add_subdirectory(level1)
add_library( ${PROJECT_NAME} addplus.cpp)
target_link_libraries(${PROJECT_NAME} LEVEL1)
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
find_program(MSVC_LIB_TOOL lib.exe)
set(LIBNAME "level1_2.lib")
add_custom_command(
TARGET examplelib POST_BUILD
COMMAND ${MSVC_LIB_TOOL} /OUT:${LIBNAME} $<TARGET_FILE:LEVEL2> $<TARGET_FILE:LEVEL1>
DEPENDS LEVEL1 LEVEL2
COMMENT "Combining libs..."
)
add_custom_target(combinedLib
ALL
DEPENDS ${LIBNAME}
)
I use the add_custom_command and add_custom_target method trying to generate the mixed lib, referenced to several website below:
CMake linking libraries into one single library
CMake Project Structure: How do I properly merge libraries together and include them in multiple executables
But they can't really solve my needs.Only level1.lib and level2.lib is generated.
Any help would be appreciated.
UPDATE in 08-21 =======================================================
Thanks for everyone's reply.Now I used object library(reference from Alex's answer), and get the merged static library.Here's my new code:
#CMakeLists.txt in level1
cmake_minimum_required(VERSION 3.5)
#projcet name
project(LEVEL1 LANGUAGES CXX)
# Generate lib
add_library( LEVEL1obj OBJECT add.cpp)
# Add the include directories of user-written sources.
target_include_directories(LEVEL1obj PUBLIC ${PROJECT_SOURCE_DIR})
add_library(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj)
And this is the CMakelists.txt of level2.
#CMakeLists.txt in level2
cmake_minimum_required(VERSION 3.5)
#projcet name
project(LEVEL2 LANGUAGES CXX)
add_subdirectory(level1)
add_library( LEVEL2obj OBJECT addplus.cpp addplus.h)
# Add the include directories of user-written sources.
target_include_directories(LEVEL2obj PUBLIC ${PROJECT_SOURCE_DIR})
target_link_libraries(LEVEL2obj LEVEL1obj)
add_library( ${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj LEVEL2obj)
This is the top level CMakeLists.txt in test project
cmake_minimum_required(VERSION 3.5)
project(TestCppLib)
file(GLOB SRC "${PROJECT_SOURCE_DIR}/*.cpp")
add_subdirectory(level2)
add_executable(${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE LEVEL2)
The slightly different from Alex's answer is : because my level2 library depends on the level1 library, I added a library dependency to it by target_link_libraries(LEVEL2obj LEVEL1obj). The slightly different answer from Alex's is that because my level2 library depends on the level1 library generation, I added a library dependency to it through the following code. At least for now, it works well.
Here is a minimal example using object libraries to manage sharing object files between static (or shared!) libraries and how to link to them.
In level1/CMakeLists.txt:
add_library(level1_obj OBJECT level1.cpp level1.h)
target_include_directories(level1_obj PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")
add_library(level1)
target_link_libraries(level1 PUBLIC level1_obj)
In level2/CMakeLists.txt
add_subdirectory(level1)
add_library(level2_obj OBJECT level2.cpp level2.h)
target_include_directories(level2_obj PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>")
add_library(level2)
target_link_libraries(level2 PUBLIC level1_obj level2_obj)
Notice that level2 is linked to level1_obj, not level1.
In main/CMakeLists.txt:
cmake_minimum_required(VERSION 3.21)
project(TestProject)
add_subdirectory(level2)
add_executable(app Main.cpp)
target_link_libraries(app PRIVATE level2)
One bug to be aware of is that Xcode does not like libraries without any real source files. If that's a concern, you can add an empty source file to your static library targets.
Please be sure to read the documentation on object libraries:
https://cmake.org/cmake/help/latest/manual/cmake-buildsystem.7.html#object-libraries
https://cmake.org/cmake/help/latest/command/target_link_libraries.html#linking-object-libraries
https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries
Also worth noting is that unless you have some need to distribute your level2 library independently from level1, it would be better to keep their objects separate and require linking to both. Normal target linking in CMake handles this automatically via the transitive linking mechanism.

CMake merges several static libs in one lib when they are also depend on other existed third-party libraries

I use CMake to build a project that consists of multiple nested static libraries .A similar but simple structure is shown in the figure below:
TestProject:
|-CMakeLists.txt
|-Main.cpp
|-level2
| | - level2.cpp
| | - level2.h
| | - CMakeLists.txt
| | - level1
| | |-level1.cpp
| | |-level1.h
| | |-CMakeLists.txt
| | |-third_party.lib-------(When I build level1.lib, I need to add dependence of this library.)
I use CMake to build static libraries for each level separately.I want to combine it with the library referenced by the previous layer when the static library of each layer is generated. For example, I build the static lib of level1 first, which depend on the existed third_party.lib.Then, in the CMakeLists.txt of level 2, I create the static library of level 2 depend on the static library of level 1 ( target_link_libraries(${PROJECT_NAME} LEVEL1) ), and then, I wanted to merge the libraries of level 2 and level 1 and third_party.lib together to a new static lib file named as level1_2.lib.
According to another similar question but different at the third_partyl.lib(CMake linking static libraries in different subdirectories into one single static library), I used object library, and get the merged static library.Here's my code:
#CMakeLists.txt in level1
cmake_minimum_required(VERSION 3.20.3)
#projcet name
project(LEVEL1 LANGUAGES CXX)
# Generate lib
add_library( LEVEL1obj OBJECT add.cpp)
# Add the include directories of user-written sources.
target_include_directories(LEVEL1obj PUBLIC ${PROJECT_SOURCE_DIR})
add_library(${PROJECT_NAME})
add_dependencies(${PROJECT_NAME} LEVEL1obj)
target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj)
And this is the CMakelists.txt of level2.
#CMakeLists.txt in level2
cmake_minimum_required(VERSION 3.20.3)
#projcet name
project(LEVEL2 LANGUAGES CXX)
add_subdirectory(level1)
add_library( LEVEL2obj OBJECT addplus.cpp addplus.h)
# Add the include directories of user-written sources.
target_include_directories(LEVEL2obj PUBLIC ${PROJECT_SOURCE_DIR})
target_link_libraries(LEVEL2obj PUBLIC LEVEL1obj)
add_library( ${PROJECT_NAME})
add_dependencies(${PROJECT_NAME} LEVEL1obj LEVEL2obj)
target_link_libraries(${PROJECT_NAME} PUBLIC LEVEL1obj LEVEL2obj)
This is the top level CMakeLists.txt in test project
cmake_minimum_required(VERSION 3.20.3)
project(TestCppLib)
file(GLOB SRC "${PROJECT_SOURCE_DIR}/*.cpp")
add_subdirectory(level2)
add_executable(${PROJECT_NAME} ${SRC})
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR})
add_dependencies(${PROJECT_NAME} LEVEL2)
target_link_libraries(${PROJECT_NAME} PRIVATE LEVEL2)
Now, I completed the compilation of level1-level2 projects with help. However, one problem is overlooked:I don't know how to deal with third-party libraries in level1.I don't know how to use the Object Library method to merge the third-party library with level1.lib.. I cannot use the method of building the object library first and then merging them. Because as far as I know, CMake may not have a method to convert a static library into an OBJECT library.
So, when I merge libraries, how should I deal with this third-party library problem?
Any help would be appreciated.

Build of multiple subprojects with library that has external dependency

I'm struggling with an project that includes CMake (version 3.11) and subdirectories. It includes ITK as an external library.
I want to create library that I can use in the different subdirectories like MyApp to build multiple applications that are based on MyLib and every change in MyLib is updated in the other applications. The MyLib is a header-only templated library. In addition this kind of libraries do not get a own project in VisualStudio.
My questions are:
What is the correct way of using CMake in this scenario? I have searched and tried some example but was not successful. Ideally I can build applications depending on the MyLib independently. The top CMakeLists.txt may not pull in the includes from the library. This should be done by the applications.
What would change if the MyLib becomes a non-header-only library? In the future I may add non templated classes.
Bonus question:
How can I add the header-only library to VisualStudio? This is more a question for convencience.
Any help is appreciated and I can provide more details if necessary.
The source environment looks like this:
ProjectDirectory
- CMakeLists.txt
-- MyLib
- CMakeLists.txt
-- include
- File1.h (template header)
- File1.hxx (template body)
-- src
- (empty for now)
-- MyApp
- CMakeLists.txt
- AppFile1.cxx (File containing main function)
This is an out-of-source-build project. Ideally the compiled libraries and application are put in a directory like this (on Windows):
└── bin
├── Debug
│ ├── MyApp.exe
│ └── MyLib.lib
└── Release
├── MyApp.exe
└── MyLib.lib
ProjectDirectory CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(project_name)
# Setup build locations.
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
add_subdirectory(MyLib)
add_subdirectory(MyApp)
MyLib CMakeLists.txt:
find_package(ITK REQUIRED
COMPONENTS RTK ITKImageIO)
include(${ITK_USE_FILE})
set(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/include/File1.h
${CMAKE_CURRENT_SOURCE_DIR}/include/File1.hxx
)
add_library(MyLib ${HDRS})
target_include_directories(MyLib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src)
target_link_libraries(MyLib
PUBLIC ${ITK_LIBRARIES})
export(TARGETS MyLib FILE MyLibConfig.cmake)
MyApp CMakeLists.txt
find_package(ITK REQUIRED
COMPONENTS RTK ITKRegistrationMethodsv4 ITKTransformIO ITKImageIO)
include(${ITK_USE_FILE})
set(SRCS
AppFile1.cxx)
add_executable(MyApp ${SRCS})
include_directories(${CMAKE_SOURCE_DIR}/MyLib/include)
target_link_libraries(MyApp
${ITK_LIBRARIES}
MyLib)
Edit1: Remove the project(X) from MyLib and MyApp CMakeLists.txt and change target to MyLib.
You can have an empty project, just create a custom target:
add_custom_target(MyLib SOURCES ${CMAKE_MYLIB_SRC} ${CMAKE_MYLIB_HDR})
As your code is header only, any depend target will see the changes in the files and would be recompiled. There is nothing to do on top of this. You don't have to create the target, although for your third question, that's the answer.
But if ITK is only your dependency, now that you have a target, you can add PUBLIC properties on it, like dependent libraries that need to be linked against it because of your library.
In that case, the code for them needs to add:
target_link_library(${NEWTARGET} PRIVATE MyLib) # PUBLIC/PRIVATE has to be tailored
That's the answer for question 1.
If your library becomes non-header only, just change the add_custom_target call.

cmake add external library

I would like to add a dependency to a target. This dependency is a precompiled lib. I would like to be able to get a external dependency just by specifing a name like add_dep(libName) in my CMakeLists.txt. The add_dep function should be reusable for different libraries.
I couldn't find any way to do this, only for libs which are compiled during build
how would I pass the target name in a generic form here?
would be fine, but the "consumer" shouldn't have to add inc/lib dirs etc.
Based on Some programmer dude:
In the dir of the external lib CMakeList.txt:
find_library(lib libName dir/to)
add_library(libName SHARED IMPORTED GLOBAL) # GLOBAL -> if outside src tree
set_property(TARGET libName PROPERTY IMPORTED_LOCATION ${lib})
set_property(TARGET libName APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include)
in the "root" CMakeList.txt:
add_subdirectory("$ENV{COMMON_ROOT}/libs/libName" "$ENV{COMMON_ROOT}/libs/libName") # usage for out of src ref
add target_link_libraries(target libName) to the CMakeLists.txt who needs it.