cmake: Multiple builds from the same source subdir possible? - build

I have a subdirectory containing Python language bindings of the project, and I want to build it for several Python version (Python 2 and 3). How can I do that?
I thought to create two bindirs from the same source first:
add_subdirectory(python, python2)
add_subdirectory(python, python3)
[edit] However, they need specific parameters, and so I tried to set them in advance:
set(PYTHON_INCLUDE_PATH /usr/include/python2.7)
[...]
add_subdirectory(python, python2)
set(PYTHON_INCLUDE_PATH /usr/include/python3.6)
[...]
add_subdirectory(python, python3)
However, then Cmake complains about duplicated target names.
How can I solve this? Or is there a better way to run multiple builds from the same source?

The CMakeLists.txt file in the python subdir can add multiple targets via add_executable or add_library or (add_custom_target and add_custom_command). Each target added in this manner will need a different target name so it would probably look something like this:
add_libary(my_python2_bindings <list_of_sources>)
add_library(my_python3_bindings <another_list_of_sources>)
The drawback is that both can't use the same name as the target name, which is what the ultimate file name will be based on. If that's a requirement you could use set the target properties for the output name and for where it is created.
project(my_python_bindings)
add_libary(my_python2_bindings <list_of_sources>)
set_target_properties(my_python2_bindings PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
set_target_properties(my_python2_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python2")
add_library(my_python3_bindings <another_list_of_sources>)
set_target_properties(my_python3_bindings PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
set_target_properties(my_python3_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python3")
Edit: for many python versions do something like this in the CMakeLists.txt in the python subdir you would have a list containing all python versions PYTHON_VERSION_LIST and then
foreach(version ${PYTHON_VERSION_LIST})
add_library(my_python${version}_bindings <list_of_sources>)
set_target_properties(my_python${version}_bindings PROPERTIES OUTPUT_NAME "${PROJECT_NAME}")
set_target_properties(my_python${version}_bindings PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/python3")
endforeach()
But now you need to add an if clause for different source lists for each version. In that case its probably easier to just expand my original example to be 2.7, 3.5, and 3.6 instead of only 2 and 3.
Also, if each version is genuinely different like it sounds like they are, you probably want to think hard about giving them all the same filename.

Related

What's a simple straightforward find_package set of files for CMake

I'm new(ish) to CMake (meaning off and on over a few years I've been forced to use it even though it's something that's made me question my career path).
I have a folder with an include folder, and a lib folder containing .lib files and their corresponding .dlls for a dependency I'll call "mydep". I need to provide the infrastructure files, mydep-config.cmake, mydep-target.cmake, etc, which will add the includes folders and .lib files to the command line for compiling and linking and then move the .dlls to a specific location.
Can anyone point me to a simple example anywhere on the net that illustrates the way to do this? The CMake documentation is utterly useless.
Thanks!
There are basically 2 files you need to put in the correct location on the file system. Let's assume the library is to import is called ExternLib with version 1.2.3 and compiled for 64 bit. With the and you've got it stored in your repo as
external_dependencies/lib/ExternLib.lib
external_dependencies/bin/ExternLib.dll
external_dependencies/include/ExternLib/ExternHeader.hpp
external_dependencies/include/ExternLib/...
...
and you want the include paths used in your project to be
#include "ExternLib/ExternHeader.hpp"
First of all we'll use the file names externlib-config.cmake and externlib-version-config.cmake to allow the user to use arbitrary case when using find_package.
externlib-version-config.cmake
This file is used to check, if the version of the config script is compatible with the cmake configuration trying to find the package. Adding this file allows you to place multiple versions of the library to be found for different target architectures (e.g. Windows x64, Win32, linux x86_64, ...). find_package won't read the externlib-config.cmake in the same directory, if the version is marked as non-compatible via the version file.
set(PACKAGE_VERSION "1.2.3")
# basically ignore the version passed
set(PACKAGE_VERSION_COMPATIBLE TRUE) # consider every version compatible
#usually you'd add logic for ignoring any unspecified parts of the version here instead of treating unspecified version parts as 0
# we'll keep it simple though
if(PACKAGE_FIND_VERSION VERSION_EQUAL PACKAGE_VERSION)
set(PACKAGE_VERSION_EXACT TRUE) # make version as exact match
endif()
if(NOT WIN32)
set(PACKAGE_VERSION_COMPATIBLE FALSE) # disallow older version
else()
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows") # note: we'll ignore the possibility of find_package before the first project() command
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8") # check for 64 bit
set(PACKAGE_VERSION_UNSUITABLE TRUE)
endif()
externlib-config.cmake
Here we'll put the creation of the actual library in addition to providing any functions/macros/variables the user should have access to. We'll use ExternLib::ExternLib as target name, but you can simply replace this by any name of your choosing unique in your project.
if(NOT TARGET ExternLib::ExternLib) # prevent recreation on multiple uses of find_package
add_library(ExternLib::ExternLib SHARED IMPORTED)
get_filename_component(EXTERNLIB_BASE_DIR ${CMAKE_CURRENT_LISTS_DIR}/../../.. ABSOLUTE)
set(EXTERNLIB_BINARY_DIR ${EXTERNLIB_BASE_DIR} CACHE INTERNAL "Directory containing the dlls for ExternLib")
# add info about locations of the dll/lib files
set_target_properties(ExternLib::ExternLib PROPERTIES
IMPORTED_LOCATION "$CACHE{EXTERNLIB_BINARY_DIR}/ExternLib.dll"
IMPORTED_IMPLIB "${EXTERNLIB_BASE_DIR}/lib/ExternLib.lib"
)
# add include directory information
target_include_directories(ExternLib::ExternLib INTERFACE "${EXTERNLIB_BASE_DIR}/include")
# add dependencies, if required; you may need to use find_package to locate those
# target_link_libraries(ExternLib::ExternLib INTERFACE ...)
endif()
# any helper function/macro definitions should go here, since they may need to get reintroduced
Note: I did ignore the possibility of components of a package being specified here. Any components specifying components for find_package are simply ignored for the script above.
Placement of the files
You'll need to place the files in one of some possible paths below one of the directories mentioned in CMAKE_PREFIX_PATH or in one of the default dirs, see find_package Config Mode Search Procedure.
Note: we'll use a path that isn't documented for windows, since this path would also work for linux. (Boost is doing this too.)
lib/cmake/ExternLib-x64-1.2.3
(You could choose a different suffix to lib/cmake/ExternLib or just use lib/cmake/ExternLib. The search procedure picks up on any directory names which starts with the package name ignoring case, if it expects the lib name.)
Place both the files in this directory. externlib-config.cmake assumes lib is external_dependencies/lib here. Otherwise you may need to adjust EXTERNLIB_BASE_DIR accordingly.
Usage
We'll assume the CMakeLists.txt file is placed in the same directory as external_dependencies
project(...)
...
add_executable(my_exe ...)
...
# this allows the user to pass directories to be searched first via -D option during configuration
list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/external_dependencies)
find_package(ExternLib REQUIRED)
target_link_libraries(my_exe PRIVATE ExternLib::ExternLib)
# allow vs debugger to find the dll without copying the dlls around
set_target_properties(my_exe PROPERTIES
VS_DEBUGGER_ENVIRONMENT "PATH=${EXTERNLIB_BINARY_DIR};$ENV{PATH}"
)

Multiple conanfile.py management

Let's say I have 2 different conanfile.py in a project and I'm calling conan install two times to install their dependencies. I'm having trouble while adding them to cmake.
If I use basic setup
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
it only includes latest one. Is it possible to include multiple conanbuildinfo.cmake files ?
If you have 2 completely separate projects, you can have 2 different conanfiles and put the generated files in different folders:
$ conan install conanfile1.py --install-folder=folder1
$ conan install conanfile2.py --install-folder=folder2
Then in your first project:
include(<...>/folder1/conanbuildinfo.cmake)
conan_basic_setup()
And in your second project:
include(<...>/folder2/conanbuildinfo.cmake)
conan_basic_setup()
You would need to define some consistent convention to locate the generated files for each project.
Note, however, that if the different modules are intended to use together, like linked together lately, if you don't use the same dependencies and same versions, you will probably get linking or runtime errors in your global application. If the modules are related and you want to use the same versions of the dependencies, then you definitely want to use just 1 conanfile with all dependencies defined in it.
Note that there are different ways to define the specific dependencies that you want, even if you use only 1 conanfile:
You can use the TARGETS of the cmake generator:
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup(TARGETS)```
add_library(mylib1 ... <sources>)
target_link_libraries(mylib1 PUBLIC CONAN_PKG::Dep1 CONAN_PKG::Dep2)
add_library(mylib2 ... <sources>)
target_link_libraries(mylib2 PUBLIC CONAN_PKG::Dep3 CONAN_PKG::Dep4)
The cmake_find_package generators also generate one findXXXX.cmake file for each package in the dependency graph. You can use the find_package(XXXX) and later the results, specifying different dependencies. The cmake_find_package_multi generator is recommended.

CMake not building a library when added as a subdirectory

I added the xgboost library as a git submodule of my project and I'm trying to add it to cmake as a subdirectory. Unfortunately it's not working. A simple hello world project with the following CMakeLists.txt replicates the error that I'm getting.
cmake_minimum_required(VERSION 3.2)
project(foo)
add_subdirectory(xgboost)
add_executable(${PROJECT_NAME} foo.cpp)
target_link_libraries(${PROJECT_NAME} xgboost)
After building the library there is nothing in the xgboost/lib directory so I get the following error.
clang: error: no such file or directory:
'/Users/.../myproject/xgboost/lib/libxgboost.dylib'
I think that the problem is generated in their CMakeLists file since they have two different targets. Maybe cmake is choosing the wrong target but I'm not familiar enough with cmake to figure it out. The following code is from xgboost's CMakeLists.
# Executable
add_executable(runxgboost $<TARGET_OBJECTS:objxgboost> src/cli_main.cc)
set_target_properties(runxgboost PROPERTIES
OUTPUT_NAME xgboost
)
set_output_directory(runxgboost ${PROJECT_SOURCE_DIR})
target_link_libraries(runxgboost ${LINK_LIBRARIES})
# Shared library
add_library(xgboost SHARED $<TARGET_OBJECTS:objxgboost>)
target_link_libraries(xgboost ${LINK_LIBRARIES})
set_output_directory(xgboost ${PROJECT_SOURCE_DIR}/lib)
#Ensure these two targets do not build simultaneously, as they produce outputs with conflicting names
add_dependencies(xgboost runxgboost)
My questions in order of importance are:
Is there any way to fix it without modifying xgboost's CMakeLists.txt file?
Is it reasonable to try to add xgboost to my project as a git submodule?
Is there any reason cmake is not instructing to build the library?
Note: There were several edits to this question since I tried to narrow down the problem and to provide more information.
(I would love to ask for few things beforehand in the comment section, but I have too low reputation to do so, so I will just give it a shot ;))
I have few suspects, and one of them is ${CMAKE_SOURCE_DIR} of the submodule's root CMakeLists.txt. Although the paths are set properly when you run that CMakeLists.txt alone, cmake gets confused the moment you add it as your subdirectory. Have you looked into another directories for your output binaries?
First I would suggest testing this hypothesis, and then I would suggest writing similar, but separate CMakeLists.txt file for xgboost library, and then substitute it in the project temporarily. Unfortunately the CMakeLists.txt filename is hardcoded and there is no possibility to have two files of that kind in one directory; so it seems that the answer to 1) is, that you rather have to change the file.
For the 2): as long as it does not require huge additional logic in your CMakeLists.txt, it makes sense. Other viable option is to create an install target, which you can use to install your xgboost library locally (using CMAKE_INSTALL_PREFIX(doc) variable), and then add the installation path to your CMAKE_LIBRARY_PATH(doc).

CMakeLists.txt and two .cmake files

I have two *.cmake files, lets say nacl.cmake and pnacl.cmake in my ./CMake/ folder. How do I cmake using the specific one using my CMakeLists.txt?
Make an option to let user switch between them:
option(USE_PNACL "Use PNaCl instead fo NaCl" TRUE)
if(USE_PNACL)
include(CMake/PNacl.cmake)
else()
include(CMake/Nacl.cmake)
endif()
They sound like two alternative toolchains. You can chose between different toolchains during configuration with the CMAKE_TOOLCHAIN_FILE option:
cmake -DCMAKE_TOOLCHAIN_FILE=<project-folder>/CMake/PNacl.cmake <project-folder>
If you don't specify a toolchain file, the build system will target the build machine.

cmake - find_library - custom library location

I'm currently trying to get CMake running for my project (on windows). I want to use a custom location where all libraries are installed. To inform CMake about that path I tried to do that:
set(CMAKE_PREFIX_PATH D:/develop/cmake/libs)
But when I try to find the library with
find_library(CURL_LIBRARY NAMES curl curllib libcurl_imp curllib_static)
CMake can't find it.
When I set my prefix path to
set(CMAKE_PREFIX_PATH D:/develop/cmake/libs/curl)
... the library is located.
So my question is:
How can I configure CMake properly to work with a directory structore at a custom location which looks like that:
D:/develop/cmake/libs/
-> libA
-> include
-> lib
-> libB
-> include
-> lib
-> ...
-> include
-> lib
In "include" lie the public headers and in "lib" are the compiled libraries.
edit:
The current workaround for me is, to do this before i search for libraries:
set(CUSTOM_LIBRARY_PATH D:/develop/cmake/libs)
file(GLOB sub-dir ${CUSTOM_LIBRARY_PATH}/*)
foreach(dir ${sub-dir})
if(IS_DIRECTORY ${dir})
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH};${dir})
endif()
endforeach()
But that way the default module for boost wont find it until it because the directory structore of boost is a bit different.
boost -> include -> boost-1_50 -> *.hpp
When I move the content if "boost-1_50" to "include" the library can be found but that way it's not possible to handle multiple versions right?
The simplest solution may be to add HINTS to each find_* request.
For example:
find_library(CURL_LIBRARY
NAMES curl curllib libcurl_imp curllib_static
HINTS "${CMAKE_PREFIX_PATH}/curl/lib"
)
For Boost I would strongly recommend using the FindBoost standard module and setting the BOOST_DIR variable to point to your Boost libraries.
I saw that two people put that question to their favorites so I will try to answer the solution which works for me:
Instead of using find modules I'm writing configuration files for all libraries which are installed. Those files are extremly simple and can also be used to set non-standard variables. CMake will (at least on windows) search for those configuration files in
CMAKE_PREFIX_PATH/<<package_name>>-<<version>>/<<package_name>>-config.cmake
(which can be set through an environment variable).
So for example the boost configuration is in the path
CMAKE_PREFIX_PATH/boost-1_50/boost-config.cmake
In that configuration you can set variables. My config file for boost looks like that:
set(boost_INCLUDE_DIRS ${boost_DIR}/include)
set(boost_LIBRARY_DIR ${boost_DIR}/lib)
foreach(component ${boost_FIND_COMPONENTS})
set(boost_LIBRARIES ${boost_LIBRARIES} debug ${boost_LIBRARY_DIR}/libboost_${component}-vc110-mt-gd-1_50.lib)
set(boost_LIBRARIES ${boost_LIBRARIES} optimized ${boost_LIBRARY_DIR}/libboost_${component}-vc110-mt-1_50.lib)
endforeach()
add_definitions( -D_WIN32_WINNT=0x0501 )
Pretty straight forward + it's possible to shrink the size of the config files even more when you write some helper functions. The only issue I have with this setup is that I havn't found a way to give config files a priority over find modules - so you need to remove the find modules.
Hope this this is helpful for other people.
Use CMAKE_PREFIX_PATH by adding multiple paths (separated by semicolons and no white spaces). You can set it as an environmental variable to avoid having absolute paths in your cmake configuration files
Notice that cmake will look for config file in any of the following folders
where is any of the path in CMAKE_PREFIX_PATH and name is the name of the library you are looking for
<prefix>/ (W)
<prefix>/(cmake|CMake)/ (W)
<prefix>/<name>*/ (W)
<prefix>/<name>*/(cmake|CMake)/ (W)
<prefix>/(lib/<arch>|lib|share)/cmake/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/ (U)
<prefix>/(lib/<arch>|lib|share)/<name>*/(cmake|CMake)/ (U)
In your case you need to add to CMAKE_PREFIX_PATH the following two paths:
D:/develop/cmake/libs/libA;D:/develop/cmake/libB
There is no way to automatically set CMAKE_PREFIX_PATH in a way you want. I see following ways to solve this problem:
Put all libraries files in the same dir. That is, include/ would contain headers for all libs, lib/ - binaries, etc. FYI, this is common layout for most UNIX-like systems.
Set global environment variable CMAKE_PREFIX_PATH to D:/develop/cmake/libs/libA;D:/develop/cmake/libs/libB;.... When you run CMake, it would aautomatically pick up this env var and populate it's own CMAKE_PREFIX_PATH.
Write a wrapper .bat script, which would call cmake command with -D CMAKE_PREFIX_PATH=... argument.
You have one extra level of nesting.
CMAKE will search under $CMAKE_PREFIX_PATH/include for headers and $CMAKE_PREFIX_PATH/libs for libraries.
From CMAKE documentation:
For each path in the CMAKE_PREFIX_PATH list, CMake will check
"PATH/include" and "PATH" when FIND_PATH() is called, "PATH/bin" and
"PATH" when FIND_PROGRAM() is called, and "PATH/lib and "PATH" when
FIND_LIBRARY() is called.
I've encountered a similar scenario. I solved it by adding in this following code just before find_library():
set(CMAKE_PREFIX_PATH /the/custom/path/to/your/lib/)
then it can find the library location.