How to set COMPILE_DEFINITIONS through cmake command line - cmake

I am building a couple of 3rd party libraries for integration.
There are two different source codes for two libs, libA and libB.
The libA has following, in its CMakeLists.txt
project(libA)
...
add_library(${PROJECT} ${SRCS})
target_link_library(${PROJECT} DOESNOTMATTER)
target_compile_definitions(${PROJECT_NAME} PUBLIC LETS_USE_GORILLA)
libA compilation is successful separately as shared lib.
libB is dependent on libA.
While compiling libB, it is expected that public compile definition of libA are automatically taken care by cmake to be available in libB. But they are not.
Now, if I add below target_compile_definitions in CMakeLists.txt of libB, it compiles successfully.
project(libB)
...
add_library(${PROJECT} ${SRCS})
target_link_library(${PROJECT} libA)
target_compile_definitions(${PROJECT_NAME} PUBLIC LETS_USE_GORILLA)
But, as this is a 3rd party code, I am not allowed to change the CMakeLists.txt.
Question - How can I pass some arguments in cmake command line to change the COMPILE_DEFINITIONS

Related

How to include static libraries in a static library?

I have 2 static libraries in two different folders: libA and libB
libB must include libA
My main CMakeLists.txt is:
add_subdirectory(libA)
add_subdirectory(libB)
My first mistake was to think linking libA in libB would include it but it isn't:
target_link_libraries(${PROJECT_NAME} PUBLIC libA::libA)
I get undefined reference to some libA's functions when I try to use libB in an app.
How can I tell CMake to include libA as part of libB?
What's the best practice for that?
I'd like to avoid any extra step (How to merge two "ar" static libraries into one?)
If you control the builds for both libA and libB, you can solve this by creating OBJECT libraries libA-obj and libB-obj. You would then link libA-obj to libA and then link both object libraries to libB.
Here's a more detailed sketch
cmake_minimum_required(VERSION 3.22)
project(example)
# ...
# Can be in subdirectory
add_library(libA-obj ${libA-srcs})
add_library(libA)
target_link_libraries(libA PUBLIC libA-obj)
# Can be in subdirectory
add_library(libB-obj ${libB-srcs})
add_library(libB)
target_link_libraries(libB PUBLIC libB-obj libA-obj)
# Can be in parent directory
add_executable(app ${app-srcs})
target_link_libraries(app PRIVATE libB)
You can add the object files that comprise libA to the sources of libB to include them:
target_sources(LibB PRIVATE $<TARGET_OBJECTS:LibA>)

Linking against GTest fails

I have the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
# adding library
set(ST_SRC simple_tree.cpp)
add_library(st ${ST_SRC})
target_include_directories(st PUBLIC ${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree/)
# adding googletest
set(GOOGLETEST_PATH ~/local/googletest)
set(GTEST_INCLUDE_DIR ~/local/include/)
set(GTEST_LIBRARY ~/local/lib/)
set(GTEST_MAIN_LIBRARY ~/local/lib/)
find_package(GTest REQUIRED)
# adding tests
set(TEST_TARGET test_simple_tree)
add_executable(${TEST_TARGET} test_simple_tree.cpp)
target_include_directories(${TEST_TARGET}
PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree
${GOOGLETEST_PATH}
${GTEST_INCLUDE_DIR})
target_link_libraries(${TEST_TARGET} PUBLIC st)
target_link_libraries(${TEST_TARGET} PUBLIC gtest gtest_main)
Basically, I've installed googletest into my home directory rather than system-wide.
The find_package() command apparently succeeds. However, trying to build test_simple_tree fails with:
/usr/bin/ld: cannot find -lgtest
/usr/bin/ld: cannot find -lgtest_main
Inside this CMakeLists.txt, how else can I tell the linker to look elsewhere
for the gtest?
EDIT: After reading the docs, I've fixed the Gtest issue as described below. However, the following issue cropped up: CMake imported target includes non-existent path
If find_package() was successful in finding the GTest includes/libraries, it should populate targets for you, per the CMake FindGTest documentation:
This module defines the following IMPORTED targets:
GTest::GTest:
The Google Test gtest library, if found; adds Thread::Thread automatically
GTest::Main:
The Google Test gtest_main library, if found
You should use these in your target_link_libraries() command instead. Also, CMake will populate GTEST_INCLUDE_DIRS, but the GTest include directories should be pulled in from the imported targets mentioned above.
Another important note: I'm not sure if you posted all of your CMake code, but I don't see a project() call in your code. As a result, the ${PROJECT_SOURCE_DIR} variable may not be what you expect. In general, it is good practice to declare your project with project() at the top of your CMake.
Your CMake file with these modifications could look something like this:
cmake_minimum_required(VERSION 3.15)
project(simple_tree_example)
set(CMAKE_CXX_STANDARD 17)
# adding library
set(ST_SRC simple_tree.cpp)
add_library(st ${ST_SRC})
target_include_directories(st PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree/)
# adding googletest
set(GOOGLETEST_PATH ~/local/googletest)
set(GTEST_INCLUDE_DIR ~/local/include/)
set(GTEST_LIBRARY ~/local/lib/)
set(GTEST_MAIN_LIBRARY ~/local/lib/)
find_package(GTest REQUIRED)
# adding tests
set(TEST_TARGET test_simple_tree)
add_executable(${TEST_TARGET} test_simple_tree.cpp)
target_include_directories(${TEST_TARGET}
PUBLIC
${PROJECT_SOURCE_DIR}/include/data_structures/simple_tree
${GOOGLETEST_PATH})
target_link_libraries(${TEST_TARGET} PUBLIC st GTest::GTest GTest::Main)
Targets gtest and gtest_main are created only when GTest is used via add_subdirectory() approach.
When use GTest via find_package(), one need to use either IMPORTED targets GTest::GTest and GTest::Main, or variables GTEST_LIBRARIES and GTEST_MAIN_LIBRARIES correspondingly. This is described in the documentation:
find_package(GTest REQUIRED)
...
target_link_libraries(test_simple_tree PUBLIC GTest::GTest gtest_main)
or
find_package(GTest REQUIRED)
...
# That case we need to add include directories
target_include_directories(test_simple_tree ${GTEST_INCLUDE_DIRS})
target_link_libraries(test_simple_tree PUBLIC ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES})

cmake - two targets that depends on single static library that should be compiled based on the target that is being built

I have a static library and two target executables, let's call them libA, EXE1, EXE2.
libA has pre-processor macros which needs to be enabled or disabled and another static library which needs to be linked or ignored based on the target executable that I am building.
Let's say, if I am building EXE1. Then I need to enable the macros in libA and link another static library to it.
If I am building EXE2, I need to disabled the macros in libA and don't link to another library.
I am confused on how to solve this issue. Please kindly help in solving this issue.
You can make use of an interface library as follows:
cmake_minimum_required(VERSION 3.10)
project(test)
add_library(libA INTERFACE)
target_sources(libA INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/liba.c)
add_executable(exe1 exe1.c)
target_link_libraries(exe1 libA)
target_compile_definitions(exe1 PUBLIC -DENABLE_THE_MACROS)
add_executable(exe2 exe2.c)
target_link_libraries(exe2 libA libOtherStatic)
target_compile_definitions(exe1 PUBLIC -DDISABLE_THE_MACROS)
libA is a "virtual" target that does not produce any output, but it can be linked to other targets (here exe1 and exe2)
Any target that links to libA will automatically receive the sources of libA as well. Note that I had to make the path absolute to prevent a warning.

Link one project to another CMAKE

I am building a project which contains lots of sub projects. for example as ...
LibA (build as shared library)
LibB (depends on LibA & build as shared library)
AppB (depends on LinB)
Directory structure (Which I want) is as ...
bin/
output/
src/libA/
src/libB/
src/appB/
Each of sub projects (LibA, LibB & AppB) has their own CMakeLists.txt file.
I want ..
1. Build LibA as shared library (I know how to do it)
2. Build LibB as shared library with linking of LibA (Don't Know how to do)
Explanation: When I start building LibB,
LibA build first and
ready to link for LibB
when LibB ready to finish
3. Build AppB : If I start building AppB,
LibA build first and
LibB build after and
both linked to AppB
Now I know classic way, build separately LibA & LibB and supplied path of lib and include to AppB. But I want to build them at once like
Build LibA
Build LibB (if LibA is already build then ignore, else build LibA)
Build AppB (if LibA, LibB are already build then ignore, else build them)
What I want
How can I achieve such behavior using CMAKE ?
It should be cross platform
Simple enough to include many more sub projects
Here is one solution. You can use a top-level CMakeLists.txt file to tie all the projects together. So in your directory structure, this would be placed here:
bin/
output/
src/libA/
src/libB/
src/appB/
CMakeLists.txt <--- Top-level CMakeLists.txt
Your top-level CMakeLists.txt file (as a sibling to the src directory) could look like this:
cmake_minimum_required(VERSION 3.11)
# Add your dependencies in the order you want them to build.
add_subdirectory(src/libA)
add_subdirectory(src/libB)
add_subdirectory(src/appB)
With each of the src directories having their own CMakeLists.txt file, here's an example of each of these individually.
You can set up LibA as a shared library with CMake with the CMakeLists.txt file in src/libA:
project(LibA_Project)
add_library(LibA SHARED sourceA.cpp ... more sources here ...)
Next, CMake will traverse to the src/libB directory to configure LibB. Here is what the src/libB/CMakeLists.txt file could look like:
project(LibB_Project)
# Create a shared library for LibB as well.
add_library(LibB SHARED sourceB.cpp ... more sources here ...)
# Link LibA to LibB as a dependency.
target_link_libraries(LibB LibA)
Finally, CMake will go to the src/appB directory. Here is that CMakeLists.txt file:
project(AppB_Project)
# Create the executable AppB.
add_executable(AppB main.cpp ... more sources here ...)
# Link LibA and LibB to AppB as dependencies.
target_link_libraries(AppB LibA LibB)
This approach can easily be expanded to include more sub-projects (e.g. LibC, LibD, AppE, etc) if necessary.

CMake: link order of imported targets incorrect

I have the following scenario:
I import two prebuilt libraries into my project (libA, libB)
libB has a dependency on libA
The executable depends on both libA and libB
However, the relative linking order in my link.txt is incorrect
/usr/bin/c++ CMakeFiles/bin.dir/main.cpp.o -o bin ../libA.a ../libB.a
I would expect libA.a to be listed after libB.a.
The CMakeLists.txt looks something along the following lines
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
add_library(MY::libA IMPORTED INTERFACE)
set_target_properties(MY::libA PROPERTIES INTERFACE_LINK_LIBRARIES "${lib_dir}/libA.a")
add_library(MY::libB IMPORTED INTERFACE)
set_target_properties(MY::libB PROPERTIES INTERFACE_LINK_LIBRARIES "MY::libA;${lib_dir}/libB.a")
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC MY::libB MY::libA)
Below a description of my attempts to solve the problem. Some without success and some with sucecss but using modifications that render the code usless for the production environment.
Successful attempts:
Remove the depedency of bin on libA (i.e. replace the last line by target_link_libraries(bin PUBLIC MY::libB). This works but I cannot remove the dependency in real code.
Replace the target type IMPORTED INTERFACE by IMPORTED STATIC. Use IMPORTED_LOCATION instead of INTERFACE_LINK_LIBRARIES and use target_link_libraries to express the dependency of libB on libA. In this case the link.txt yields: [...] -o bin ../libA.a ../libB.a ../libA.a. As soon as I revert the target type for libB the link order breaks down again. In the production environment, however, one of the targets is created by conan as IMPORTED INTERFACE.
Attempts without success (same behaviour as described):
Create a separate IMPORTED target (use IMPORTED_LOCATION) for every lib and group them inside an INTERFACE target
Sprinkle the code with ADD_DEPENDENCIES
Remove libA from the INTERFACE_LINK_LIBRARIES in line 9 and use target_link_libraries(MY::libB INTERFACE MY::libA) instead. Same result.
Example code that shows the same failure using INTERFACES as a building block
cmake_minimum_required(VERSION 3.13)
project(cmake_test)
set(lib_dir ${CMAKE_CURRENT_SOURCE_DIR})
# libA
add_library(MY::libA_file1 IMPORTED STATIC)
set_target_properties(MY::libA_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libA.a")
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE MY::libA_file1)
# libB
add_library(MY::libB_file1 IMPORTED STATIC)
set_target_properties(MY::libB_file1 PROPERTIES IMPORTED_LOCATION "${lib_dir}/libB.a")
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE MY::libB_file1 libA)
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)
You incorrectly think about INTERFACE_LINK_LIBRARIES property as a "content" of the library's target, which is ordered by target_link_libraries call.
Using
target_link_libraries(MY::libB INTERFACE MY::libA)
you setup link dependency between library targets MY::libB and MY::libA. That is, "content" of MY::libB target should come before "content" of MY::libA target in the linking command line.
But INTERFACE_LINK_LIBRARIES property is NOT a "content" of the library target! It is just an additional link dependency.
As opposite, IMPORTED_LOCATION (for non-INTERFACE IMPORTED target) is a "content" of the library, and target_link_libraries affects on its ordering.
It seems that you cannot add link dependency for a library, using INTERFACE library target. You should use IMPORTED library target for that purpose:
# Collect libraries related to 'libA'
file(GLOB libs_A "${lib_dir}/libA*.a")
# For each library create IMPORTED target with IMPORTED_LOCATION property.
set(libs_A_targets)
foreach(lib_A ${libs_A})
# Form a unique name for the IMPORTED target: subtarget_A_*
string(REGEX REPLACE "^${lib_dir}/libA([^.]*).a$" "subtarget_A_\\1" lib_A_target ${lib_A})
# Create a target with this name
add_library(${lib_A_target} STATIC IMPORTED)
set_target_properties(${lib_A_target} PROPERTIES IMPORTED_LOCATION ${lib_A})
# And add the target into the list
list(APPEND libs_A_targets ${lib_A_target})
endforeach()
# In a similar way collect libraries for libB.
set(lib_B_targets ...)
# Now link each libB* library with each libA*.
foreach(lib_B_target ${libs_B_targets})
target_link_libraries(${lib_B_target} INTERFACE ${libs_A_targets})
endforeach()
# Now interface libraries, which combine libA* and libB*, can be created
add_library(libA INTERFACE)
target_link_libraries(libA INTERFACE ${libs_A_targets})
add_library(libB INTERFACE)
target_link_libraries(libB INTERFACE ${libs_B_targets})
# Now these INTERFACE libraries can be linked into an executable in any order
add_executable(bin ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp)
target_link_libraries(bin PUBLIC libA libB)