cmake boost find_depedency Config - c++

I ran into this issue when trying to generate a Config for my build and the consume the target upstream while using different boost COMPONENTS. It feels like I'm missing something or maybe even misunderstood how I'm supposed to be using the Config.cmake file to generate targets for an upstream package. That you have to specify find_dependency again seems...off.
In the below SomeProjectConfig.cmake file I have to check whether an upstream package did load the necessary targets for SomeProject to work, if it didn't I need to call find boost with the necessary components again.
# SomeProjectConfig.cmake.in
# Avoid repeatedly including the targets
if(NOT TARGET SomeProject::SomeProject)
# Provide path for package module scripts, CMAKE_CURRENT_LIST_DIR is the
# directory of the currently executing cmake file.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
include(CMakeFindDependencyMacro)
# --------------------------------------------------------------------------
# Boost
# The way I choose to go about it is to simply check if the dependency boost
# targets are defined - if they are we don't have to do anything and just let
# everything pass through. However if they are missing we need to set
# Boost_FOUND back to FALSE so it'll resolve the dependencies...
list(APPEND SomeProject_Boost_COMPONENTS system thread)
foreach(_comp ${SomeProject_Boost_COMPONENTS})
if(NOT TARGET Boost::${_comp})
set(Boost_FOUND 0)
break()
endif()
endforeach()
# Additionally we record the BOOST_ROOT - but we try to respect the upstream
# package if specifies it.
if(NOT BOOST_ROOT)
set(BOOST_ROOT "#BOOST_ROOT#"
endif()
# We want to handle this quietly, upstream packages might want to include more
# componentes and if we ask for components first Boost will handle it as if
# we've already done all the necessary work.
find_dependency(Boost 1.55 QUIET REQUIRED COMPONENTS ${SomeProject_Boost_COMPONENTS})
# We can never leave without setting Boost_FOUND to FALSE - if the upstream
# package has find_package(Boost) after find_package(SomeProject) it'll
# break if Boost_FOUND is TRUE
set(Boost_FOUND 0)
# // Boost
# --------------------------------------------------------------------------
find_dependency(SomeOtherDependency 1.0 QUIET REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/SomeProjectTargets.cmake")
# Clean up module path after we're done
list(REMOVE_AT CMAKE_MODULE_PATH -1)
endif()
The problem with the above as I see it is that we're leaving Boost_FOUND set to 0 and from my point of view it's really hard to control, it also feels like I'm actually not understanding the Config package process correctly.
# CMakeLists.txt upstream
find_package(Boost 1.55 REQUIRED COMPONENTS filesystem)
set(SomePackage_DIR "/path/to/Config/location")
find_package(SomePackage REQUIRED)
add_library(UpstreamTarget SHARED ${sources})
target_link_libraries(UpstreamTarget PUBLIC Boost::filesystem SomeProject::SomeProject)
The above will work, but it leaves me with a bad itch - it feels like I'm doing it wrong.

Turns out it was my use of the find_dependency macro, replacing it with find_package gave me the correct results without workarounds.
If we dive into the CMakeFindDependencyMacro.cmake it becomes quite clear what the issue is.
macro(find_dependency dep)
if (NOT ${dep}_FOUND)
...
If the package is already found it will stop the execution right there and won't append the additional components. Meaning you could have the find_package(SomePackage) call before any additional find_package(Boost) upstream, but not the other way around.
I don't think this is a problem for isolated packages but once you have components that you might need to append to an already existing target find_dependency seems to break due to the if statement. Judging from another answer in regards to find_dependency it looks like it would mostly be for diagnostic messages telling the user that the problem comes from a *Config.cmake file than anything else.
for completeness here's a working example of the config file where I removed the workarounds:
# SomeProjectConfig.cmake.in
# Avoid repeatedly including the targets
if(NOT TARGET SomeProject::SomeProject)
# Provide path for package module scripts, CMAKE_CURRENT_LIST_DIR is the
# directory of the currently executing cmake file.
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR})
include(CMakeFindDependencyMacro)
# Additionally we record the BOOST_ROOT - but we try to respect the upstream
# package if it specifies it.
if(NOT BOOST_ROOT)
set(BOOST_ROOT "#BOOST_ROOT#"
endif()
find_package(Boost 1.55 QUIET REQUIRED COMPONENTS system thread)
find_dependency(SomeOtherDependency 1.0 QUIET REQUIRED)
include("${CMAKE_CURRENT_LIST_DIR}/SomeProjectTargets.cmake")
# Clean up module path after we're done
list(REMOVE_AT CMAKE_MODULE_PATH -1)
endif()

Related

Do I need a Shared library in a CMAKE project with several subdirectories? How do I do it?

I need to implement a StatisticsLogger library on to a C++/C solution built with cmake and compiled with VS#2010. The project uses the ADTF API, since is built for the ADTF framework.
The solution is composed by different modules/projects, which include their own CMakeLists.txt.
My doubt/problem is regarding the library and Cmake, I need it to be accesible to every single module, but it can't be copied, them all should access the same StatisticsLogger library, that I have implemented with a Singleton.
When I run the framework, the concurrent execution accesses StatsLogger constructor once on each module, like if I had created one StatsLogger on each module, making it unable to trace together all the data I want to log and difficulting file handling.
This is how I added the library in CMakeLists.txt:
add_library(loggerModule
${DSTD_DIR}/dstdfloat.h
${DSTD_DIR}/dstdint.h
${DSTD_DIR}/dstdbool.h
${SUPT_DIR}/logg/statslogger.h
${SUPT_DIR}/logg/statslogger_c_connector.h
${SUPT_DIR}/logg/statslogger_c_connector.cpp
${SUPT_DIR}/logg/statslogger.cpp
)
#set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION})
#set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h)
link_libraries(loggerModule)
It would seem that adding SHARED property to the command add_library would do the job, but Im not capable of getting it working. It returns several link problems.
So, regarding my doubt, Is this the way to get the desired funcionality, to make the library SHARED? What am I doing wrong?
Main CMakeLists.txt:
# CMAKE for test filter build
cmake_minimum_required(VERSION 2.8.4 FATAL_ERROR)
# Set default install prefix
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR})
# Set project name
project(${PROJECT_NAME})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Environment checks
find_package(ADTF 2.14.2 REQUIRED)
IF (NOT DEFINED ADTF_FOUND)
MESSAGE( FATAL_ERROR "ADTF was NOT found!!!")
ENDIF()
find_package(ADTF_DISPLAY_TOOLBOX REQUIRED)
IF (NOT DEFINED ADTF_DISPLAY_TOOLBOX_FOUND)
MESSAGE( FATAL_ERROR "ADTF_DISPLAY_TOOLBOX was NOT found!!!")
ENDIF()
set(COMPLETE_PROJECT_BINARY_OUTPUT_DIR ${CMAKE_SOURCE_DIR}/${PROJECT_BINARY_OUTPUT_DIRECTORY})
# Set path to sw module dependencies
set(COMMON_DIR ${CMAKE_SOURCE_DIR}/common)
set(DSTD_DIR ${CMAKE_SOURCE_DIR}/common/dstd)
set(INTF_DIR ${CMAKE_SOURCE_DIR}/common/intf)
set(SUPT_DIR ${CMAKE_SOURCE_DIR}/common/supt)
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
#option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
include_directories(${DSTD_DIR})
include_directories(${SUPT_DIR}/logg)
add_library(loggerModule SHARED
${DSTD_DIR}/dstdfloat.h
${DSTD_DIR}/dstdint.h
${DSTD_DIR}/dstdbool.h
${SUPT_DIR}/logg/statslogger.h
${SUPT_DIR}/logg/statslogger_c_connector.h
${SUPT_DIR}/logg/statslogger_c_connector.cpp
${SUPT_DIR}/logg/statslogger.cpp
)
#set_target_properties(loggerModule PROPERTIES VERSION ${PROJECT_VERSION})
#set_target_properties(loggerModule PROPERTIES PUBLIC_HEADER include/mylib.h)
link_libraries(loggerModule)
# Set commands for BB nodes generation
# ...and dependencies for generation
# Go into sub-directory with filter sources
add_subdirectory(${CMAKE_SOURCE_DIR}/tool/adtf/af_acca)
#[33 other add_subdirectories commands]
Example subdirectory CMakeLists.txt:
# External required components have to be provided with path variables
# Internal required components have to be connected to the source file list
if(NOT DEFINED DSTD_DIR)
message( FATAL_ERROR "AF_CDAS requires DSTD_DIR" )
endif()
if(NOT DEFINED INTF_DIR)
message( FATAL_ERROR "AF_CDAS requires INTF_DIR" )
endif()
if(NOT DEFINED SUPT_DIR)
message( FATAL_ERROR "AF_CDAS requires SUPT_DIR" )
endif()
set(CDAS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../)
include_directories(${COMMON_DIR})
[some more includes]
#
# CDAS FILES
#
set(CDAS_FILES
${CDAS_DIR}/src/cdas/cdas.c
${CDAS_DIR}/src/cdas/cdas.h)
source_group("cdas\\files" FILES ${CDAS_FILES})
#
# CDAS ADTF WRAPPER
#
set(CDAS_ADTF_WRAPPER
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas_conf.h
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.h
${CDAS_DIR}/tool/adtf/af_cdas/af_cdas.cpp)
source_group("cdas\\adtf" FILES ${CDAS_ADTF_WRAPPER})
adtf_add_filter(CDAS
# List source and header files of your filter and its required components
# ${MAIN_FILES}
${CDAS_FILES}
${CDAS_ADTF_WRAPPER}
)
# stdafx.h workaround
if (MSVC)
set_target_properties(CDAS PROPERTIES COMPILE_FLAGS "/Y-")
endif(MSVC)
# MANDATORY PATH SETTING !!!
install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/debug CONFIGURATIONS Debug)
install (TARGETS CDAS DESTINATION ${COMPLETE_PROJECT_BINARY_OUTPUT_DIR}/release CONFIGURATIONS Release)
adtf_set_folder(CDAS filter)
When using this files, I get the following error:
LINK : fatal error LNK1181: cannot open input file '......\Release\loggerModule.lib' [C:\Users\inno\Desktop\rad
ar_processing\test\adtf\build\win64_vc100\tool\adtf\af_aoca\AOCA.vcxproj]
There isn't any loggerModule.lib in the Release folder, but a loggerModule.dll. In the Debug folder there is a .lib, but copying it to Release won't solve the problem.
I never got too familiar with Cmake, and I am trying to learn it, so I don't know what is going on here, if I am doing something wrong, or my approach wasn't good from the beggining.

What is the best way of using arrow parquet in more modern cmake?

Below is the solution that worked for me, but not sure if it is the best way to do this. I used brew to install it. vcpkg does not work at the moment, unfortunately. What I don't like about this solution is that I need to set Parquet_DIR and find_package(Parquet) separately.
set(Parquet_DIR /usr/local/lib/cmake/arrow)
find_package(Arrow CONFIG REQUIRED)
find_package(Parquet CONFIG REQUIRED)
target_link_libraries(database PRIVATE arrow_shared parquet_shared)
You can pass PATHS to search to find_package.
You may also want to prevent searching in other places by passing NO_DEFAULT_PATH.
See find_package documentation
find_package(Arrow CONFIG REQUIRED)
find_package(Parquet CONFIG REQUIRED
PATHS /usr/local/lib/cmake/arrow
NO_DEFAULT_PATH
)
target_link_libraries(database PRIVATE arrow_shared parquet_shared)
(The above snippet assumes that Arrow does not depend on the Parquet package.)
As fabian's workaround, you'd use find_package(Parquet CONFIG REQUIRED PATHS ${Arrow_DIR} NO_DEFAULT_PATH) to avoid absolute path, which is more flexiable.
I manage to build parquet only without using find_package or ExternalProject_Add. Here is my CMakeLists.txt:
# FetchContent and populate...
# Setting your CMake flags...
set(ARROW_SIMD_LEVEL "NONE" CACHE STRING "" FORCE)
set(ARROW_OPTIONAL_INSTALL OFF)
set(Thrift_SOURCE "BUNDLED")
set(BOOST_SOURCE "BUNDLED")
set(ARROW_BUILD_TESTS OFF)
set(ARROW_PARQUET ON)
set(PARQUET_MINIMAL_DEPENDENCY OFF)
include(CMakePackageConfigHelpers)
function(install)
endfunction()
add_subdirectory(${arrow_SOURCE_DIR}/cpp ${arrow_BINARY_DIR})
add_dependencies(parquet_objlib thrift_ep boost_ep) # Had to add this line. Not sure why the proper dependencies are not there.

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: Enable features based on the result of multiple find_package() calls

I have a project that contains a visual debugger that I want to build only if a group of packages were found. I don't want to force a user that just want to build the base application to install all the debugger dependencies, so I don't mark then as REQUIRED when running find_package().
Right now my build looks like this:
./CMakeList
find_package(gazebo)
find_package(OpenGL)
find_package(GLUT)
add_subdirectory(debugger)
./debugger/CMakeList
if(NOT gazebo_FOUND OR NOT OpenGL_FOUND OR NOT GLUT_FOUND)
return()
endif()
This works just fine, but as the list of dependencies for the debugger grows, using this if to enable/disable the debugger build looks clumsy.
Is there a better alternative to enable the build of this sub-directory base on the result of find_package()?
The debugger is not the only thing in my project that I enable/disable this way, so I need a generic solution for enabling features based on the packages found.
CMake has no already-made a macro or a function to check list of variables at once.
But it is not a difficult to write a function which do that. E.g.:
# Usage: feature_check(FEATURE_VAR REQUIRED_VAR [REQUIRED_VAR2 ...])
#
# Set FEATURE_VAR to TRUE if all required vars are TRUE,
# Otherwise set FEATURE_VAR to FALSE.
function(feature_check FEATURE_VAR REQUIRED_VAR)
foreach(var ${REQUIRED_VAR} ${ARGN})
if(NOT ${var})
set(${FEATURE_VAR} "FALSE" PARENT_SCOPE)
return()
endif(NOT ${var})
endforeach(var ${REQUIRED_VAR} ${ARGN})
set(${FEATURE_VAR} "TRUE" PARENT_SCOPE)
endfunction(feature_check FEATURE_VAR REQUIRED_VAR)
So, in you case you may use
# In CMakeList.txt, after find_package()
feature_check(USE_DEBUGGER gazebo_FOUND OpenGL_FOUND GLUT_FOUND)
# In debugger/CMakeList.txt
if(NOT USE_DEBUGGER)
return()
endif(NOT USE_DEBUGGER)
...
Turning my comments into an answer
I see three alternatives:
Using FindPkgConfig module to combine the search for packages into a single call/result variable? Something like:
pkg_check_modules(DEBUGGER_DEPS gazebo gl glu)
would - on platforms with pkg-config - also make the find_package() calls obsolete by using the results of the above call:
target_compile_options(debugger PUBLIC ${DEBUGGER_DEPS_CFLAGS})
target_link_libraries(debugger PUBLIC ${DEBUGGER_DEPS_LDFLAGS})
Using CMakeDependentOption module to combine the boolean values into a single option to check with something like
CMAKE_DEPENDENT_OPTION(
BUILD_DEBUGGER "Build debugger" ON
"gazebo_FOUND;OpenGL_FOUND;GLUT_FOUND" OFF
)
Or - if the project is huge - maybe just write my own my_target_link_libraries() function which includes the necessary if (TARGET ...) dependency checks and removes the main target from all/default build if it fails:
function(my_target_link_libraries _target)
foreach(_dep IN ITEMS ${ARGN})
if (TARGET _dep)
target_link_libraries(${_target} ${_dep})
else()
message(STATUS "No target '${_dep}' found. Disabling '${_target}' build")
set_target_properties(${_target} PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
endif()
endforach()
endfunction()
my_target_link_libraries(debugger GLUT::GLUT ...)

How to get a header only library portably from version control using CMake?

For a typical C++ header only library located on, e.g., github, at: https://github.com/username/library_name,
has a directory structure with an include/library_name folder like:
include/library_name
containing all the library sources. This is typically installed by users to, e.g., under Linux: /usr/local/include/library_name.
I need a cmake script for using the library in external projects portably (across Linux, MacOs, BSD, Windows).
It should:
find if the library is installed, if the version is over a threshold, use the installed library
otherwise, get the library from github, configure it as an external project, and put it in the system include path so that it can be used by the project as if it were installed.
What is the correct way of achieving this with CMake?
I finally got it to work, this FindLibraryName.cmake tries to find the library and if it doesn't find it, gets it from github and sets it up as an external project:
# Find the Library_Name include directory
# The following variables are set if Library_Name is found.
# Library_Name_FOUND - True when the Library_Name include directory is found.
# Library_Name_INCLUDE_DIRS - The path to where the poco include files are.
# If Library_Name is not found, Library_Name_FOUND is set to false.
find_package(PkgConfig)
# Allow the user can specify the include directory manually:
if(NOT EXISTS "${LIBRARY_NAME_INCLUDE_DIR}")
find_path(LIBRARY_NAME_INCLUDE_DIR
NAMES library_name/library_name.hpp
DOC "Library_Name library header files"
)
endif()
if(EXISTS "${LIBRARY_NAME_INCLUDE_DIR}")
include(FindPackageHandleStandardArgs)
mark_as_advanced(LIBRARY_NAME_INCLUDE_DIR)
else()
include(ExternalProject)
ExternalProject_Add(library_name
GIT_REPOSITORY https://github.com/username/library_name.git
TIMEOUT 5
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}
PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
INSTALL_COMMAND "" # Disable install step, is a header only lib!
)
# Specify include dir
ExternalProject_Get_Property(library_name source_dir)
set(LIBRARY_NAME_INCLUDE_DIRS ${source_dir}/include)
endif()
if(EXISTS "${LIBRARY_NAME_INCLUDE_DIR}")
set(Library_Name_FOUND 1)
else()
set(Library_Name_FOUND 0)
endif()
Then in the projects CMakeLists.txt, just add:
find_package(Library_Name)
include_directories(${LIBRARY_NAME_INCLUDE_DIRS})
You can replace GIT with SVN above and provide the URL of an svn repo and it will work as well. Other version control systems are also available.