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}
)
Related
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.
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.)
I have a project which depends on FreeType, and uses CMake as build system. CMake has a FindFreeType built-in module which is supposed to be used like this, see for example this other SO question:
find_package(Freetype REQUIRED)
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})
Since CMake 3.10, there is also the Freetype::Freetype imported target so we can avoid the target_include_directories:
find_package(Freetype REQUIRED)
target_link_libraries(mylib Freetype::Freetype)
This worked great on Ubuntu 18.04 with FreeType installed via apt install libfreetype6-dev. I assume it also works on macOS when the package is installed via homebrew (I haven't tested yet).
However, on Windows, I wish to allow developers to depend on a vcpkg-installed FreeType:
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
.\vcpkg install freetype:x64-windows
Which they would target by running the following CMake command:
cmake .. -G "Visual Studio 15 2017" -A x64 -DCMAKE_TOOLCHAIN_FILE=C:/Users/Boris/vcpkg/scripts/buildsystems/vcpkg.cmake
Unfortunately, the above CMake command won't work with any of the two CMakeLists.txt at the beginning of this question, because the proper way to find and link to FreeType when it is installed via vcpkg is the following:
find_package(freetype CONFIG REQUIRED) # `Freetype` works too, but vcpkg doc recommends `freetype`
target_link_libraries(mylib freetype) # Here, all-lowercase is required
In particular, the freetype-config.cmake config file provided by vcpkg defines the target freetype (not Freetype::Freetype like the builtin find module), and doesn't define any of the FREETYPE_LIBRARIES or FREETYPE_INCLUDE_DIRS variables.
What would be a proper way to keep my CMakeLists.txt compatible with both "traditional" ways of finding FreeType, but also vcpkg?
Assuming pre-CMake 3.10, I'm thinking of something along the lines of:
if(DEFINED VCPKG_TARGET_TRIPLET)
find_package(freetype CONFIG REQUIRED)
set(FREETYPE_LIBRARIES freetype)
set(FREETYPE_INCLUDE_DIRS "")
else()
find_package(Freetype REQUIRED)
endif()
target_link_libraries(mylib ${FREETYPE_LIBRARIES})
target_include_directories(mylib PRIVATE ${FREETYPE_INCLUDE_DIRS})
Would that seem like good practice? Any better idea?
It feels ugly, and besides, there is always the possibility of a developer wanting to use vcpkg for some other dependencies but not for FreeType (e.g., explicitly providing FREETYPE_DIR instead), so this trick wouldn't even be enough in all situations, and we would need another CMake option like MYLIB_IGNORE_VCPKG_FREETYPE which starts to be even uglier.
Would that seem like good practice? Any better idea?
No be agnostic about a possible package manager.
Do the following:
find_package(Freetype CONFIG) # should find freetype-config.cmake if available
find_package(Freetype REQUIRED) # Will not be executed if Freetype_FOUND ist already set
# if you do not want two find_package calls consider using CMAKE_FIND_PACKAGE_PREFER_CONFIG
Then test if the target Freetype::Freetype or freetype exists
if(TARGET freetype AND NOT TARGET Freetype::Freetype)
add_library(Freetype::Freetype ALIAS freetype) # target freetype is defined by freetype-targets.cmake
# might need to add freetype to global scope if cmake errors here
# alternativly if the above does not work for you you can use
# add_library(Freetype::Freetype INTERFACE IMPORTED)
# target_link_libraries(Freetype::Freetype INTERFACE freetype)
endif()
if(NOT TARGET Freetype::Freetype)
# insert error here
# or create the target correctly (see cmakes newer FindFreetype.cmake)
endif()
target_link_libraries(mylib PRIVATE Freetype::Freetype)
if you don't want to alias the target you could also define a variable called FREETYPE_TARGET and set it to the correct target for linking against.
In older versions of vcpkg (< Jan 2020, see vcpkg#9311), there used to be the following message when installing Freetype:
The package freetype is compatible with built-in CMake targets:
find_package(Freetype REQUIRED)
target_link_libraries(main PRIVATE Freetype::Freetype)
In current versions, they instead recommend find_package(freetype CONFIG REQUIRED), which ensures that the config package vcpkg/<...>/freetype-config.cmake takes precedence over the CMake's built-in module package FindFreetype.cmake.
However, using the built-in module package still works correctly: it will find the vcpkg-installed library, and will define a Freetype::Freetype target (if CMake >= 3.10) rather than a freetype target.
Note that by default, find_package(Freetype REQUIRED) searches first for module packages, then for config packages. However, users can sets CMAKE_FIND_PACKAGE_PREFER_CONFIG, which would use the config package instead. Therefore, a robust approach to ensure that Freetype::Freetype is defined is to do the following:
find_package(Freetype MODULE REQUIRED)
target_link_libraries(mylib Freetype::Freetype)
However, if CMake < 3.10, this still doesn't define a Freetype::Freetype target. One option in this case is to vendor a copy of a more recent version of FindFreetype.cmake in your CMAKE_MODULE_PATH.
For even more robustness, you could even call it Find<Mylib>Freetype.cmake, in the unlikely case where your CMakeLists.txt is used as a subdirectory of another project which also modifies CMAKE_MODULE_PATH and provides an incompatible FindFreetype.cmake.
I am trying to link my local version of gflags (which is in ~/mylibs/gflags) while compiling google-test. In gtest's CMakeLists.txt, it's using find_package:
if (WITH_GFLAGS)
find_package (gflags 2.2.0)
if (gflags_FOUND)
set (HAVE_LIB_GFLAGS 1)
determine_gflags_namespace (gflags_NAMESPACE)
endif (gflags_FOUND)
endif (WITH_GFLAGS)
I don't want to modify the CMakeLists.txt file. Is there anyway to tell cmake to find the package in my folder ?
Thanks!
The gflags documentation describes specifying the location of a non-standard installation when using CMake:
The gflags_DIR variable must be set to the <prefix>/lib/cmake/gflags directory containing the gflags-config.cmake file
In your case, this might look like:
cmake -Dgflags_DIR=/home/blackball/mylibs/gflags/lib/cmake/gflags [...]
Since you are integrating gflags as subproject in a super cmake, you basically want find_package() to be a no-op if you have it in your "meta-project" source dir. This can be done by overloading the find_package() command.
Top CMakeLists.txt:
# Use find_package everywhere, no-op if it's a subproject
macro(find_package)
if(NOT TARGET ${ARGV0} AND NOT TARGET ${ARGV0}::${ARGV0})
_find_package(${ARGV})
else()
if(TARGET ${ARGV0})
get_target_property(TGT_VER ${ARGV0} VERSION)
set(TGT ${ARGV0})
else()
get_target_property(TGT_VER ${ARGV0}::${ARGV0} VERSION)
set(TGT ${ARGV0}::${ARGV0})
endif()
message(STATUS "Found ${ARGV0}: CMake Target ${TGT} (found version \"${TGT_VER}\")")
set(${ARGV0}_FOUND TRUE)
endif()
endmacro()
note: for gflags you may need to "force" gflags_NAMESPACE also since gflags it's a source not a binary (cf glog issue ToDo)
I'm a new guy to c++ and cmake here. I decided to test out cLion and cMake. I'm trying to write a simple email client for the command line. Other sources told me that the best way to implement a POP3 and SMTP functions would be to use POCO. Unfortunately, cMake is giving me trouble. The version that came with CLion is 3.2 but the version that my machine is running is 2.8.
~$ cmake --version
cmake version 2.8.12.2
First problem. I thought that I could bypass this by just installing POCO and doing the same thing that I used for openssl which I also had to download.
cMakeList.txt:
cmake_minimum_required(VERSION 3.0)
project(Email_Reader)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
#included paths for openssl and POCO.
INCLUDE_DIRECTORIES("/usr/include/openssl")
INCLUDE_DIRECTORIES("/usr/local/include/Poco/Net")
set(SOURCE_FILES main.cpp)
add_executable(Email_Reader ${SOURCE_FILES})
The documentation for POCO tells me that I need at least 3.0 to work but I feel I have 2 different cMakes on my machine. Can you help me, please?
You can get the latest CMake release from: http://www.cmake.org/download/
For Linux, it's this archive: http://www.cmake.org/files/v3.2/cmake-3.2.2.tar.gz
An easy way to use it is to put the extracted files in /opt/cmake/cmake-3.2 then create the following aliases (e.g. in ~/.bash_aliases:
alias ccmake3='/opt/cmake/cmake-3.2/bin/ccmake'
alias cmake3='/opt/cmake/cmake-3.2/bin/cmake'
alias cmake3-gui='/opt/cmake/cmake-3.2/bin/cmake-gui'
alias cpack3='/opt/cmake/cmake-3.2/bin/cpack'
alias ctest3='/opt/cmake/cmake-3.2/bin/ctest'
Then, make sure that you have properly built and installed POCO.
The Getting Started page has all the information you need for doing that. But, basically, you should get the sources from here and extract them somehwere:
wget http://pocoproject.org/releases/poco-1.6.0/poco-1.6.0.tar.gz
tar xvfz poco-1.6.0.tar.gz
cd poco-1.6.0
mkdir -p cmake_build cmake_install/debug cmake_install/release
cd cmake_build
cmake3-gui ..
In the CMake 3 GUI, press Configure. In the new window, keep the default option Unix Makefiles and click on Finish. An error message should appear (which is fine), click Ok.
To build the Debug version, set the following:
CMAKE_BUILD_TYPE : Debug
CMAKE_INSTALL_PREFIX : the absolute path to "cmake_install/debug"
To get you started quickly with POCO, unckeck all the options, except for the following, they have to be enabled:
ENABLE_JSON
ENABLE_NET
ENABLE_UTIL
ENABLE_XML
POCO_STATIC
(You can consider the other options later if you need to...)
Quit the GUI, then build/install POCO:
make clean
make -j8
make install
Now, POCO should be installed in cmake_install/debug. To build/install the other versions, just do the same procedure, but replace Debug in CMAKE_BUILD_TYPE with Release, RelWithDebInfo or MinSizeRel (cf. CMake's doc) (also, you'll have to change the install directories)
Finally, you can use POCO in you C++ projects.
For instance, your CMakeLists.txt should look like this:
cmake_minimum_required(VERSION 3.0)
project(Email_Reader)
# define the project
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(SOURCE_FILES main.cpp)
add_executable(Email_Reader ${SOURCE_FILES})
# set the POCO paths and libs
set(POCO_PREFIX "/path/to/cmake_install/debug") # the directory containing "include" and "lib"
set(POCO_INCLUDE_DIR "${POCO_PREFIX}/include")
set(POCO_LIB_DIR "${POCO_PREFIX}/lib")
set(POCO_LIBS "${POCO_LIB_DIR}/libPocoNetd.a"
"${POCO_LIB_DIR}/libPocoUtild.a"
"${POCO_LIB_DIR}/libPocoJSONd.a"
"${POCO_LIB_DIR}/libPocoXMLd.a"
"${POCO_LIB_DIR}/libPocoFoundationd.a"
"pthread")
# set the include path for the app
target_include_directories(Email_Reader PRIVATE "${POCO_INCLUDE_DIR}")
# link the app against POCO
target_link_libraries(Email_Reader "${POCO_LIBS}")
My CMakeLists.txt for using Poco looks like this:
cmake_minimum_required(VERSION 3.10.0)
project(MyProject VERSION 0.1.0)
find_package(Poco REQUIRED COMPONENTS Foundation Net Zip )
add_executable(my_exe main.cpp)
target_link_libraries(my_exe PUBLIC Poco::Foundation Poco::Zip Poco::Net)
This configuration automatically add the needed include directories and libraries.
The Foundation component is mandatory, it seems it provides the include directories.
Don't add Poco to target_link_libraries, the linker will then look for a 'Poco' library.