CMake FeatureSummary remove some packages from the list - cmake

I have a project where I look for spdlog and, if not found, I fetch it from git using FetchContent. However I also use FeatureSummary, so when spdlog is not found, it's reported by FeatureSummary.
I don't want that FeatureSummary reports it, as I am going to download the package from git, so makes no sense to report it as not found.
However, I have some other packages which are optional and I search for them using find_package(foo QUIET), so excluding the QUIET packages from the FeatureSummary will also disable this optional packages, and I don't want that.
How can I remove spdlog from the list of not found packages that are going to be listed by FeatureSummary?
find_package(spdlog QUIET)
if (NOT TARGET spdlog::spdlog)
message(STATUS "spdlog was not found. Fetching from git")
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.8.5
)
set(SPDLOG_INSTALL ON CACHE BOOL "Install spdlog" FORCE)
FetchContent_MakeAvailable(spdlog)
endif()

Related

cmake not rebuilding a non-download external project after manually editing its sources

I'm working on some modifications to the openEMS project. This project uses cmake to build all of its components. The top level CMakeLists.txt file contains the following:
# ...
ExternalProject_Add( openEMS
DEPENDS fparser CSXCAD
SOURCE_DIR ${PROJECT_SOURCE_DIR}/openEMS
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DFPARSER_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DCSXCAD_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DWITH_MPI=${WITH_MPI} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
)
# ...
Inside the openEMS directory, there's another CMakeLists.txt with the following:
# ...
set(SOURCES
openems.cpp
)
# ...
add_library( openEMS SHARED ${SOURCES})
# ...
After building the project successfully once, make does not rebuild anything when, for example, openems.cpp is modified. Why?
$ mkdir build
$ cd build
$ cmake -DBUILD_APPCSXCAD=NO
$ make
[builds all files]
$ touch ../openEMS/openems.cpp
$ make
[ 33%] Built target fparser
[ 66%] Built target CSXCAD
[100%] Built target openEMS
(noting is built)
I have checked and the modification date of openems.cpp is newer than the target. Even deleting the produced library files and binaries, both in the install directory and in the build directory, does not cause it to rebuild anything. The only way I can get it to rebuild is by deleting everything in the build directory and re-running cmake which, of course, rebuilds everything.
This looks like a case of the following. Quoting from the docs for ExternalProject_Add at the section titled "Build Step Options":
BUILD_ALWAYS <bool>
Enabling this option forces the build step to always be run. This can be the easiest way to robustly ensure that the external project's own build dependencies are evaluated rather than relying on the default success timestamp-based method. This option is not normally needed unless developers are expected to modify something the external project's build depends on in a way that is not detectable via the step target dependencies (e.g. SOURCE_DIR is used without a download method and developers might modify the sources in SOURCE_DIR).
If that's the case, the solution would be to add the BUILD_ALWAYS argument to the ExternalProject_Add call like.
ExternalProject_Add( openEMS
DEPENDS fparser CSXCAD
SOURCE_DIR ${PROJECT_SOURCE_DIR}/openEMS
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} -DFPARSER_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DCSXCAD_ROOT_DIR=${CMAKE_INSTALL_PREFIX} -DWITH_MPI=${WITH_MPI} -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}
BUILD_ALWAYS TRUE
)
If you confirm that this solves the issue, you might want to raise this as an issue to the maintainers of openEMS.
Also note that since the external project there is using CMake as a buildsystem, you could also add the CONFIGURE_HANDLED_BY_BUILD TRUE to the argument list. See the docs for more info.
Edit: The asker opened a GitHub Pull-Request.

Building a CMake project with OpenSSL as dependency

I would like to build my project with OpenSSL from sources. I am using a modern CMake with FetchContent feature. So far, I have no trouble using FetchContent with CMake external projects, but OpenSSL does not use CMake.
My try so far:
FetchContent_Declare(
openssl
GIT_REPOSITORY https://github.com/openssl/openssl.git
GIT_TAG origin/master
CONFIGURE_COMMAND "./Configure"
BUILD_COMMAND "make"
TEST_COMMAND "make test"
)
...
FetchContent_MakeAvailable(openssl)
but this does not make anything in the main project and of course compilation fails for executables requiring lib openssl.
Could you please help me to figure out if it's possible to automatically build the openssl lib for my program ? I would like to avoid usage of existing non official wrappers of openssl with cmake.
Thanks a lot in advance
Stephane
I think that you are mixing FetchContent and ExternalProject.
FetchContent will download (and possibly patch) your subproject, typically to use it like a "git submodule", and ExternalProject will not only download it, but also build it. From the FetchContent docs:
In addition to the above explicit options, any other unrecognized options are passed through unmodified to ExternalProject_Add() to perform the download, patch and update steps. The following options are explicitly prohibited (they are disabled by the FetchContent_Populate() command):
CONFIGURE_COMMAND
BUILD_COMMAND
INSTALL_COMMAND
TEST_COMMAND
Now, there are two things with ExternalProject_Add():
It will run at build time. I like to use it as a cross-platform script to help users build the dependencies from source, but they have to run that script as a prerequisite, and then build my project by referencing to the result of this one.
It will build and install the project locally, so your main project that depends on it will have to find it, e.g. by pointing CMAKE_PREFIX_PATH to the install path of the dependency.
Also, you could make it such that the configure step of your main project runs ExternalProject_add in a separate process, but I think that it quickly makes it more complex and ends up not helping users so much.
All that to say that there is no silver bullet when it comes to dependencies, and for a dependency like OpenSSL, the simplest may be to give instructions explaining how to apt install openssl or brew install openssl, and then use find_package(OpenSSL REQUIRED) in your CMakeLists.txt!
This one is tricky, because OpenSSL does not use CMake, it has a custom build script, and has a complex behavior with glibc. As #JonasVautherin said, you cannot use FetchContent_Declare, you must use ExternalProject_Add:
set(OPENSSL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl-src) # default path by CMake
set(OPENSSL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/openssl)
set(OPENSSL_INCLUDE_DIR ${OPENSSL_INSTALL_DIR}/include)
set(OPENSSL_CONFIGURE_COMMAND ${OPENSSL_SOURCE_DIR}/config)
ExternalProject_Add(
OpenSSL
SOURCE_DIR ${OPENSSL_SOURCE_DIR}
GIT_REPOSITORY https://github.com/openssl/openssl.git
GIT_TAG OpenSSL_1_1_1n
USES_TERMINAL_DOWNLOAD TRUE
CONFIGURE_COMMAND
${OPENSSL_CONFIGURE_COMMAND}
--prefix=${OPENSSL_INSTALL_DIR}
--openssldir=${OPENSSL_INSTALL_DIR}
BUILD_COMMAND make
TEST_COMMAND ""
INSTALL_COMMAND make install
INSTALL_DIR ${OPENSSL_INSTALL_DIR}
)
After doing that, you cannot include or link it with your other targets yet. To do that, you still need to declare the library, as if it was found using find_package, i.e. as if FindOpenSSL.cmake was used:
# We cannot use find_library because ExternalProject_Add() is performed at build time.
# And to please the property INTERFACE_INCLUDE_DIRECTORIES,
# we make the include directory in advance.
file(MAKE_DIRECTORY ${OPENSSL_INCLUDE_DIR})
add_library(OpenSSL::SSL STATIC IMPORTED GLOBAL)
set_property(TARGET OpenSSL::SSL PROPERTY IMPORTED_LOCATION ${OPENSSL_INSTALL_DIR}/lib/libssl.${OPENSSL_LIBRARY_SUFFIX})
set_property(TARGET OpenSSL::SSL PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR})
add_dependencies(OpenSSL::SSL OpenSSL)
add_library(OpenSSL::Crypto STATIC IMPORTED GLOBAL)
set_property(TARGET OpenSSL::Crypto PROPERTY IMPORTED_LOCATION ${OPENSSL_INSTALL_DIR}/lib/libcrypto.${OPENSSL_LIBRARY_SUFFIX})
set_property(TARGET OpenSSL::Crypto PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${OPENSSL_INCLUDE_DIR})
add_dependencies(OpenSSL::Crypto OpenSSL)
Now you can include the OpenSSL and link with it normally.

Disable install for FetchContent

Let's say I have the following code:
include(FetchContent)
FetchContent_Declare(cmark
GIT_REPOSITORY https://github.com/commonmark/cmark.git
GIT_TAG 0.29.0
)
FetchContent_MakeAvailable(cmark)
target_link_libraries(hello_world cmark::cmark_static)
install(TARGETS hello_world DESTINATION bin)
That works correctly, but whenever I run make install, it also installs all the cmark files (like include/cmark_version.h, lib/pkgconfig/libcmark.pc, etc).
Is there any way to disable installing files from packages with FetchContent?
The macro FetchContent_MakeAvailable includes subproject with use of add_subdirectory command. And this command has as special option - EXCLUDE_FROM_ALL - for disable inner install calls.
So, you may replace call FetchContent_MakeAvailable with:
FetchContent_GetProperties(cmark)
if(NOT cmark_POPULATED)
FetchContent_Populate(cmark)
add_subdirectory(${cmark_SOURCE_DIR} ${cmark_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
(This is actually an exact alternative to FetchContent_GetProperties call noted in the FetchContent documentation but with additional EXCLUDE_FROM_ALL parameter.)

How to find static version of zlib in CMake?

I'm on cmake version 3.12.1 and want to build a static executable that uses ZLIB. I have both the static (libz.a) and shared (libz.so) libraries on my machine. How can I tell find_package(ZLIB) to return the static version? Maybe there's another way to find libz.a as well?
My present workaround is to specify:
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
Then:
target_link_libraries (my_binary z lib1 lib2)
Critique on this approach is also welcome!
As of CMake 3.24, use: set(ZLIB_USE_STATIC_LIBS "ON")
Source
Your approach is valid given the limitations of the CMake module called by find_package(ZLIB), specifically FindZLIB.cmake. While other FindXXX.cmake modules have a special option for grabbing static libraries, the zlib module does not.
There are already a few questions on SO about this topic, but some are older than others, so there are a few options.
You can instead apply the -static flag on a more granular level (rather than editing the global CMAKE_EXE_LINKER_FLAGS variable) by adding it to your target_link_libraries call. This way it will apply only to that target -- useful if you are building other non-static targets.
You could also tell CMake to search for static libraries explicitly by setting CMAKE_FIND_LIBRARY_SUFFIXES. When find_package is called, CMake can search for libraries ending in .a using this:
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
find_package(ZLIB REQUIRED)
If you have control over installing zlib, for example, you are installing dependencies in a Continuous Integration setup, I would recommend to just remove the zlib dynamic library.
zlib doesn't have the option to build statically or dynamically, it automatically generates both versions. However FindZlib.cmake prioritizes the dynamic version.
I find the following approach to be better in case you don't have access to modify third parties repositories CMakeLists.txt that needs zlib:
if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(_compiler_is_msvc ON)
endif()
option(ZLIB_FORCE_STATIC "Remove the dynamic libraries after zlib install" ON)
mark_as_advanced(ZLIB_FORCE_STATIC)
set(OUTPUT_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR} CACHE PATH "Base folder where builds and source folder will be installed: i.e. OUTPUT_BUILD_DIR/zlib")
if(_compiler_is_msvc)
set(ZLIB_GIT_TAG cacf7f1d4e3d44d871b605da3b647f07d718623f) # Version 1.2.11
message(STATUS "ZLIB_VERSION: ${ZLIB_GIT_TAG} : Version 1.2.11")
set(ZLIB_BUILD_DIR ${OUTPUT_BUILD_DIR}/zlib-build)
set(ZLIB_INSTALL_DIR ${OUTPUT_BUILD_DIR}/zlib)
set(ZLIB_SRC_FOLDER_NAME zlib-src)
set(ZLIB_SRC_DIR ${OUTPUT_BUILD_DIR}/${ZLIB_SRC_FOLDER_NAME})
set(ZLIB_GIT_REPOSITORY "https://github.com/madler/zlib")
ExternalProject_Add(ep_zlib
GIT_REPOSITORY ${ZLIB_GIT_REPOSITORY}
GIT_TAG ${ZLIB_GIT_TAG}
# GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
CMAKE_GENERATOR ${CMAKE_GENERATOR}
SOURCE_DIR ${ZLIB_SRC_DIR}
BINARY_DIR ${ZLIB_BUILD_DIR}
CMAKE_ARGS
-DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER}
-DCMAKE_BUILD_TYPE:STRING=${SGEXT_CMAKE_BUILD_TYPE}
-DBUILD_SHARED_LIBS:BOOL=OFF
-DCMAKE_INSTALL_PREFIX=${ZLIB_INSTALL_DIR}
)
if(ZLIB_FORCE_STATIC)
ExternalProject_Add_Step(
ep_zlib zlib_remove_dll
COMMENT "Remove zlib.lib and zlib.dll, leaves only zlibstatic.lib"
DEPENDEES install
COMMAND ${CMAKE_COMMAND} -E remove -f ${ZLIB_INSTALL_DIR}/lib/zlib.lib ${ZLIB_INSTALL_DIR}/bin/zlib.dll
)
endif()
endif()
The last step removes the dynamic version, so the default FindZLIB will find the static library.
The best solution I found was to name the library explicitly when calling CMake:
cmake -DZLIB_LIBRARY=/usr/lib/x86_64-linux-gnu/libz.a /path/to/source
I would not recommend the solution proposed by #phcerdan because in my case the installed shared library was colliding with an already installed version, so the only solution was to make sure it never gets installed in the first place. The key idea is to disable completely the targets installation using SKIP_INSTALL_LIBRARIES, and instead to "install" the static library manually. Nonetheless, my solution is quite similar:
EXTERNALPROJECT_ADD(zlib_external
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG v1.2.11
CMAKE_ARGS
-DSKIP_INSTALL_FILES=ON # Disable install of manual and pkgconfig files
-DSKIP_INSTALL_LIBRARIES=ON # Do not install libraries automatically. It will be handled manually to avoid installing shared libs
-DBUILD_SHARED_LIBS=OFF
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX}
-DCMAKE_C_FLAGS:STRING=${CMAKE_COMPILE_FLAGS_EXTERNAL}
${EXTERNALPROJECT_BUILD_TYPE_CMD}
INSTALL_DIR ${CMAKE_INSTALL_PREFIX}
)
if(NOT WIN32)
set(zlib_BUILD_LIB_PATH "<BINARY_DIR>/libz.a")
set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/libz.a")
else()
set(zlib_BUILD_LIB_PATH "<BINARY_DIR>/Release/zlibstatic.lib")
set(zlib_PATH "${CMAKE_INSTALL_PREFIX}/lib/zlibstatic.lib")
endif()
ExternalProject_Add_Step(
zlib_external zlib_install_static_only
COMMENT "Manually installing only static library"
DEPENDEES install
COMMAND ${CMAKE_COMMAND} -E copy ${zlib_BUILD_LIB_PATH} ${zlib_PATH}
)

"make dist" equivalent in CMake

According to FAQ, CMake doesn't create a make dist target and source package can be created using CPack. But CPack just makes a tarball of the source directory with all files that don't match patterns in CPACK_SOURCE_IGNORE_FILES.
On the other hand, make dist generated by autotools bundles only files it knows about, mostly sources needed for compilation.
Anyone has a smart way of making a source package with only files that are specified in CMakeLists.txt (and its dependencies)?
I've been thinking about this for a while and I won't pretend I can simulate a make dist without having this directly supported by CMake itself.
The problem is that you can add a lot of file dependencies with CMake on the one side (e.g. to pre-build libraries) and on the other side CMake does not know about dependencies directly checked by the generated build environment itself (e.g. any header dependencies).
So here is a code that just collects all CMakeList.txt and source files given with any build targets:
function(make_dist_creator _variable _access _value _current_list_file _stack)
if (_access STREQUAL "MODIFIED_ACCESS")
# Check if we are finished (end of main CMakeLists.txt)
if (NOT _current_list_file)
get_property(_subdirs GLOBAL PROPERTY MAKE_DIST_DIRECTORIES)
list(REMOVE_DUPLICATES _subdirs)
foreach(_subdir IN LISTS _subdirs)
list(APPEND _make_dist_sources "${_subdir}/CMakeLists.txt")
get_property(_targets DIRECTORY "${_subdir}" PROPERTY BUILDSYSTEM_TARGETS)
foreach(_target IN LISTS _targets)
get_property(_sources TARGET "${_target}" PROPERTY SOURCES)
foreach(_source IN LISTS _sources)
list(APPEND _make_dist_sources "${_subdir}/${_source}")
endforeach()
endforeach()
endforeach()
add_custom_target(
dist
COMMAND "${CMAKE_COMMAND}" -E tar zcvf "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.tar.gz" -- ${_make_dist_sources}
COMMENT "Make distribution ${PROJECT_NAME}.tar.gz"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
message("_make_dist_sources = ${_make_dist_sources}")
else()
# else collect subdirectories in my source dir
file(RELATIVE_PATH _dir_rel "${CMAKE_SOURCE_DIR}" "${_value}")
if (NOT _dir_rel MATCHES "\.\.")
set_property(GLOBAL APPEND PROPERTY MAKE_DIST_DIRECTORIES "${_value}")
endif()
endif()
endif()
endfunction()
variable_watch("CMAKE_CURRENT_LIST_DIR" make_dist_creator)
Note: The used BUILDSYSTEM_TARGETS property needs at least CMake version 3.7
I see the code above as an starting point and prove of concept. You could add libraries, headers, etc. on a need-by basis, but you should probably just tweak cpack to do your bidding.
As a starting point see e.g. the link #usr1234567 provided in the comments.
References
Get all source files a target depends on in CMake
Simon is correct above but does not give a full answer. With git you can generate a compatible tar ball archive with the git archive command.
This example with the version is compatible with make dist of yesteryear.
git archive --format=tar.gz -o my-repo-0.01.tar.gz --prefix=my-repo-0.01/ master
See: https://gist.github.com/simonw/a44af92b4b255981161eacc304417368