CMake CMAKE_AUTOMOC in cross compilation - c++

I've following issue. I'm tring to use native mechanism build in CMake for cross compilation. I prepared following toolchain.cmake file:
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_SYSROOT /tmp/filesystem)
set(tools /opt/gcc-linaro-arm-linux-gnueabihf-4.8-2014.04_linux/bin/arm-linux-gnueabihf)
set(CMAKE_C_COMPILER ${tools}-gcc)
set(CMAKE_CXX_COMPILER ${tools}-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
and in target CMakeList.txt is use:
set(CMAKE_AUTOMOC ON)
I expect that when I use CMAKE_FIND_ROOT_PATH_MODE_PROGRAM set to NEVER the CMake, according to documentation, will use moc from HOST:
If set to NEVER, then the roots in CMAKE_FIND_ROOT_PATH will be
ignored and only the host system root will be used.
However it still try to use moc from TARGET arm image rootfs.
I try to refind the moc executable like in first answer from this post: How to include a certain Qt installation using CMake? but with no luck.
I also try to set the QT_MOC_EXECUTABLE variable to proper path from HOST rootfs instead of TARGET one but also with no luck there. I event think that this variable isn't use by CMake when CMAKE_AUTOMOC is set to ON since after forcing change this cached variable cmake still use moc from TARGET rootfs.
Any ideas how to resolve this issue?
# EDIT 1
I found that the automoc generates such file in build folder:
CMakeFiles/*target_name*_automoc.dir/AutogenInfo.cmake
And in my case such variable is set to wrong path:
set(AM_QT_MOC_EXECUTABLE "/tmp/filesystem/usr/lib/arm-linux-gnueabihf/qt5/bin/moc")
should be:
set(AM_QT_MOC_EXECUTABLE "/usr/bin/moc")
I set AM_QT_MOC_EXECUTABLE to correct value in main CMakeList.txt but still after mentioned file is generated with wrong path from TARGET rootfs.

I finally found the solution thanks to this post: How can I use CMake's AUTOMOC feature with a custom Qt package?. As I assumed the QT_MOC_EXECUTABLE isn't use directly by AUTOMOC.
Before first qt find_package following lines must be added:
set(QT_MOC_EXECUTABLE /usr/bin/moc)
add_executable(Qt5::moc IMPORTED)
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION ${QT_MOC_EXECUTABLE})
The issue here was that not only the variable QT_MOC_EXECUTABLE has to be set to proper value but finally the automoc uses just Qt5:moc target which must be declared before any qt package will be included in CMakeList.txt file.
This same issue is with other qt tools so more generic option will be:
file(GLOB Qt_bin /usr/bin)
find_program(QT_MOC_EXECUTABLE qt_moc moc PATHS ${Qt_bin})
add_executable(Qt5::moc IMPORTED)
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION ${QT_MOC_EXECUTABLE})

Related

Access CMake's target/library name at compile time globally

We implement a logger which automatically prints out the project name of a log entry among other infos.
We recently change our build system from using native Microsoft visual c++ to cmake generated.
With native Microsoft c++ build files, we were just defining a macro "PROJECT_NAME" in a global ".props" as such:
<PreprocessorDefinitions>%(PreprocessorDefinitions);PROJECT_NAME=R"($(ProjectName))"</PreprocessorDefinitions>
However, now that we use CMake, I struggle to find a good way to access the project name at compile time. I can use configure_file or target_compile_definitions to access a CMake variable at compile time but I do not know any variable holding a string with the target name. Is there such a variable or can it be defined?
Also, can it be defined only in one place? I don't want to copy paste in every CMakeList.txt a line of the such:
target_compile_definitions(MYTARGET PRIVATE PROJECT_NAME="$mytarget_name")
If you've got multiple options to apply to all targets, you could link a INTERFACE library which allows you to inherit multiple properties of this cmake target via a single target_link_libraries use.
For using the name of the linking target, generator expressions that do not require specifying the target can be used:
add_library(OptionsForAll INTERFACE)
# note: using the target name not the cmake project name here
target_compile_definitions(OptionsForAll INTERFACE PROJECT_NAME=\"$<TARGET_PROPERTY:NAME>\")
target_link_libraries(MYTARGET PRIVATE OptionsForAll)
Another option mentioned in #Tsyvarevs comment would be to use add_compile_definitions to apply the definition for all targets defined in the current directory and subdirectory, but this makes it harder to remove it from some targets...
fabian's answer is IMO the good approach.
However, i wanted to offer an alternative based on BUILDSYSTEM_TARGETS inspired from this answer.
Thus, there is no need to modify all target's call to target_link_libraries.
function(get_all_targets var)
set(targets)
get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR})
set(${var} ${targets} PARENT_SCOPE)
endfunction()
macro(get_all_targets_recursive targets dir)
get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
foreach(subdir ${subdirectories})
get_all_targets_recursive(${targets} ${subdir})
endforeach()
get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS)
list(APPEND ${targets} ${current_targets})
endmacro()
function(define_targetname all_targets)
foreach(target ${all_targets})
get_target_property(type ${target} TYPE)
if (NOT ${type} STREQUAL "INTERFACE_LIBRARY" AND NOT ${target} STREQUAL "PCHTarget") # INTERFACE library cannot target_compile_definitions && precompiled headers will redefine the MACRO
target_compile_definitions(${target} PRIVATE PROJECT_NAME="${target}")
endif()
endforeach()
endfunction()
get_all_targets(all_targets)
define_targetname("${all_targets}")

How do I make CMake find a library at a custom location (cross-compile raspberry pi 4)

How do I point find_package() to my "custom" directory $SYSROOT/usr/lib/arm-linux-gnueabihf/ so it can find libz.so ?
I have a library in this location:
/home/user/bla/sysroot/usr/lib/arm-linux-gnueabihf/libz.so
With a header that can be found here:
/home/user/bla/sysroot/usr/include/zlib.h
With CMake I try:
cmake_minimum_required(VERSION 3.18)
find_package(ZLIB REQUIRED)
Which results in:
CMake Error at /home/user/cmake/linux/share/cmake-3.19/Modules/FindPackageHandleStandardArgs.cmake:218 (message):
Could NOT find ZLIB (missing: ZLIB_LIBRARY) (found version "1.2.11")
So CMake cannot find ZLIB_LIBRARY (libz.so) but it can parse the version (1.2.11) from the header file.
I set my SYSROOT in CMake like this:
SET(CMAKE_FIND_ROOT_PATH "${toolchain_dir}/sysroot/")
But it seems CMake doesn't know about usr/lib/arm-linux-gnueabihf/libz.so.
What I tried
I tried to give HINTS or PATH to find_package like this:
find_package(ZLIB REQUIRED HINTS "${sysroot_dir}/usr/lib/arm-linux-gnueabihf/" NO_DEFAULT_PATH)
or like this:
find_package(ZLIB REQUIRED PATH "${sysroot_dir}/usr/lib/arm-linux-gnueabihf/" NO_DEFAULT_PATH)
But it results in this error message:
CMake Error at CMakeLists.txt:12 (find_package):
Could not find a package configuration file provided by "ZLIB" with any of
the following names:
ZLIBConfig.cmake
zlib-config.cmake
Add the installation prefix of "ZLIB" to CMAKE_PREFIX_PATH or set
"ZLIB_DIR" to a directory containing one of the above files. If "ZLIB"
provides a separate development package or SDK, be sure it has been
installed.
extra info
Here is the contents of my toolchain file that I use (targeting raspberry pi 4) with -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake
# Define our host system
SET(CMAKE_SYSTEM_NAME Linux)
SET(CMAKE_SYSTEM_VERSION 1)
# toolchain
set(toolchain_dir "/home/user/bla")
set(sysroot_dir "${toolchain_dir}/sysroot")
SET(CMAKE_C_COMPILER "${toolchain_dir}/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc")
SET(CMAKE_CXX_COMPILER "${toolchain_dir}/tools/gcc-linaro-7.5.0-2019.12-i686_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++")
# Define the sysroot path for the RaspberryPi distribution
SET(CMAKE_FIND_ROOT_PATH "${toolchain_dir}/sysroot/")
message(STATUS "${CMAKE_FIND_ROOT_PATH}")
# Use our definitions for compiler tools
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# Search for libraries and headers in the target directories only
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
FWIW, I have used toolchain files like this for cross-compiling:
# cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/arm32.cmake ..
# Define our host system
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 1)
set(CMAKE_SYSTEM_PROCESSOR armv61)
# Define the cross compiler locations
set(CMAKE_C_COMPILER arm-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabi-g++)
# Define the sysroot path for the RaspberryPi distribution in our tools folder
set(CMAKE_FIND_ROOT_PATH $ENV{AARCH64_LINUX_TOOLS_DIR}/aarch64-linux-gnu/sysroot)
# Use our definitions for compiler tools
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
I am not exactly sure, but it could be that those last 2 lines are overriding something:
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
I would try removing those.
Also, I would try to periodically try to delete the entire build folder and start over because CMake caches a lot of things. It could be that something is getting cached (a path to some library NOT in your specific sysroot) that is messing up the configuration process.
A handy way to check if your variable is as expected.
message(STATUS "sysroot_dir: ${sysroot_dir}")
You should change sysroot's value by set(CMAKE_SYSROOT /your/path) instead of set(sysroot /your/path) which directly set the variable inside cmake.
Reference here
The issue was that I forgot to add CMAKE_SYSROOT to my toolchain file. Without this, CMake cannot find the libraries in $SYSROOT/usr/lib/arm-linux-gnueabihf.

CMake BUILD undefined reference (findpng)

I'm still very new to CMake so feedback is definitely welcome. So, I'm trying to build a simple application that should eventually create a pdf using the library libharu.
I think i figured it out how to link the library. But I still receive build errors for the findpng module (I suppose libharu depends on it)
CMakeLists.txt:
cmake_minimum_required(VERSION 3.2.0 FATAL_ERROR) # current latest stable version (if lower give FATAL_ERROR)
project(pdf_generator VERSION 0.1.0) # name of the project, version.
file(GLOB TARGET_SRC "./src/*.cpp") # Creates variable, using globbing.
include_directories(${PROJECT_SOURCE_DIR}/include) # list of directories to be used as header search paths.
add_executable(main ${TARGET_SRC}) # Create an executable of set of source files [exe name files to bundle].
find_library(libhpdf_location NAMES libhpdf.a) # find the location of libhpdf.a and save the value in the variable libhpdf_location.
message(STATUS ${libhpdf_location}) # print status of variable.
add_library(libhpdf STATIC IMPORTED) # Add library via a static import.
set_target_properties(
libhpdf PROPERTIES
IMPORTED_LOCATION ${libhpdf_location}
)
target_link_libraries(main libhpdf)
I've never worked with that particular library before, but skimming their CMakeLists.txt on GitHub it seems like libharu has optional dependencies on libpdf and zlib. Without knowing how you built your version of libharu I'm going to assume that both are needed.
Luckily, CMake comes with find-modules for both libpng and zlib, so adding the following should work:
find_package(PNG REQUIRED)
find_package(ZLIB REQUIRED)
set_target_properties(libhpdf
PROPERTIES
INTERFACE_LINK_LIBRARIES "ZLIB::ZLIB;PNG::PNG"
)
Looks like all you need to do is tell cmake to link libpng.

cmake cross-compilation with sysroot returns wrong include paths

I am having an issue when trying to cross-compile (for an arm target) and including packages through cmake.
Compilation works fine when compiled locally and also when cross-compiled without package dependencies.
My toolchain file is:
cmake_minimum_required(VERSION 3.3)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_FIND_ROOT_PATH /path/to/sysroot/)
set(CMAKE_SYSROOT /path/to/sysroot/)
set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
And in the CMakeFileList I have a few find_packages:
find_package(pack1 REQUIRED)
find_package(pack2 REQUIRED)
message("--${pack1_INCLUDE_DIRS}--")
message("--${pack2_INCLUDE_DIRS}--")
include_directories(${pack1_INCLUDE_DIRS})
include_directories(${pack2_INCLUDE_DIRS})
When displaying the include directories variables, only the first one in the list is prefixed with the sysroot path. I got something like this:
--/path/to/sysroot/usr/include;/usr/include;/usr/include/xmlrpcpp--
Of course the compilation failed, as some headers do not correspond to the target system.
Why is cmake not prefixing the entire list of directories (they are present in the sysroot folder)?
Aren't CMAKE_SYSROOT and set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) sufficient to get cmake to look only in this directory or am I missing something here?
Note: there is no difference with and without the set(CMAKE_FIND_ROOT_PATH /path/to/sysroot/) in the toolchain instructions.
Edit 1:
Explicitly setting the include paths (${pack1_INCLUDE_DIRS} and ${pack2_INCLUDE_DIRS}) to point only to the sysroot path does solve the compilation issue but then lead to a linker error (no explicit message) which suggests that 1)the extra /usr/include entries are the problem and 2) the ${pack1_LIBRARIES} variables are also incorrect.
Edit 2:
Following #Tsyvarev answer which explains why this cannot be directly achieved. One solution (a bit hacky) is to extract the name of the libraries to be included from the find_package and force cmake to look for them in the sysroot folder. The code below does work for my use case:
#Reset libraries towards sysroot only
foreach(lib_path ${pack1_LIBRARIES} ${pack2_LIBRARIES})
string(REPLACE "/" ";" lib_path_list ${lib_path}) #Breakdown path in list
list(REVERSE lib_path_list)
list(GET lib_path_list 0 lib) #Get last element (library name)
find_library(new_path ${lib} PATHS ${CMAKE_SYSROOT}) #Force to look for it in actual sysroot path
list(APPEND LIBRARIES ${new_path}) #Add it to LIBRARIES list
unset(new_path CACHE) #Clear variable to allow new search
endforeach()
Variable CMAKE_SYSROOT (and CMAKE_FIND_ROOT_PATH) re-roots only those paths, which are searched by different find_* commands: find_library, find_path, find_package(only search path for package file is modified).
E.g. if find_library searches under /usr/lib by default, then with CMAKE_SYSROOT set it will search under ${CMAKE_SYSROOT}/usr/lib instead.
However, if one calls
include_directories("/usr/include")
then the /usr/include path remain unchanged even when cross-compiling.
If find_package(pack1) is run in MODULE mode and uses Findpack1.cmake script for locate settings of pack1 package, then that Findpack1.cmake script would use find_library and find_path calls. Thus, setting CMAKE_SYSROOT would force this script to search libraries and headers under the sysroot and return appropriate paths.
But if find_package(pack1) is run in CONFIG mode and uses pack1Config.cmake script for locate settings of pack1 package, then that pack1Config.cmake could use absolute paths of the libraries and include directories. In that case CMake does NOT transform these absolute paths, so they become wrong when cross-compiling.
Actually, CMake provides mechanisms and rules which could help in writing relocatable XXXConfig.cmake scripts. But not all config scripts follow these rules.
If a project pack1 provides pack1Config.cmake script which is non-relocatable in terms of cross-compiling, then you need to build this project as cross-compiled with the same sysroot.
Below configuration won't recursively append all the sub directories to your include paths.
set(CMAKE_SYSROOT /path/to/sysroot/)
In your code the include "*.h" should be relatively against to /path/to/sysroot/.
Instead of using the toolchain file, try to pass --sysroot from your cmake command line which should always work.

CMake: how to specify different CMakeFileList?

We created 3 different CMakeFileList files (CMakeFileList.engine, CMakeFileList.data and CMakeFileList.fep) for different build options within the same project. Does CMake support specifying CMakeFileList file as an argument? If not, what's the best way to accomplish our task by leveraging cmake? Any suggestion is appreciated.
In general I've done such kind of things by using the option() cmake command and providing just a single CMakeLists.txt file (what to build is decided inside according to the options, i.e. you can then also build everything in a single cmake/make run).
Example:
# the defaults (can be overridden on the command line)
option(BUILD_ENGINE "Build the Engine" ON)
option(BUILD_DATA "Build the Data" ON)
option(BUILD_FEP "Build the Fep" OFF)
if (BUILD_ENGINE)
# commands to build the Engine target or include(CMakeFileList.engine)
endif()
if (BUILD_DATA)
# commands to build the Data target or include(CMakeFileList.data)
endif()
if (BUILD_FEP)
# commands to build the Fep target or include(CMakeFileList.fep)
endif()
Then you can have everything in a single CMakeLists.txt and build what is needed each time, might it be multiple packages (if different than the defaults, you can switch on/off on the cmake command line). Or include the separate cmake lists as well (and just to make sure that they will work together if everything needs to be build).
Create main CMakeLists.txt file and conditionally use command include for use component-specific parts:
set(BUILD_TARGET "" CACHE STRING "Target to build")
if(BUILD_TARGET STREQUAL "engine")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.engine)
elseif(BUILD_TARGET STREQUAL "data")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.data)
elseif(BUILD_TARGET STREQUAL "fep")
include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeFileList.fep)
else()
message(FATAL_ERROR "Incorrect BUILD_TARGET")
endif()