I'm brand new to the world of linux, cmake, and software/computers in general (I'm just learning how to work with the command line if that gives you an idea of where I'm at) yet somehow found myself working as a software intern (I'm an EE major). I've been tasked to build a software package using cmake and make and it is not going well. I'm making an effort to understand every single line within the top level CMakeLists.txt file associated with the package and am feeling incredibly incompetent. I've gone through some documentation about cmake in general and although I have a sense of how cmake uses CMakeLists.txt files there are some specifics that require clarification.
1) There are a number of variables that are being used and I would like to find out what their values are. However, not all of the variables within the CMakeLists.txt file are shown in the CMakeCache.txt file. For example, consider the following command shown below
SET(bioreader_common_LIBS
${bioreader_common_LIBS}
QtCore
QtScript
QtScriptTools
QtNetwork
QtGui
QtTest
QtXml
QtDBus
QtSql
mongoclient.a
bsd
ssl
crypto
boost_system
boost_thread
boost_filesystem
boost_program_options
)
Nowhere in the CMakeLists.txt file is the variable bioreader_common_LIBS defined and its not contained in the cache. It seems obvious that it's "value" is simply a list of libraries. Is there a way (maybe through the command line) to return the value of this variable?
2) After a google search about cmake variables I came across a wiki page detailing useful cmake variables (http://www.cmake.org/Wiki/CMake_Useful_Variables). I've been trying to find the value of a lot of these variables but with little luck. For example, one of the "useful variables" is CMAKE_LIBRARY_PATH. Although there is no reference to this variable within the CMakeLists.txt or CMakeCache.txt files, it seems as though it should still exist and have a value and yet I can't find it?
3) One of the biggest issues I've run into after running cmake and make is failing to link against certain libraries. For example, there is a required sql database driver plugin (mysql) which I believe exists as 12 .so files located in usr/lib/mysql/plugin. I realize that I need to modify the CMakeLists.txt file to include these libraries or perhaps add the path somewhere but I'm not sure how to do it. Even if I knew where to add the library I'm not sure how to name the library within the CMakeLists.txt (I thought the name of the driver was "mysql" but apparently that's technically not what you would refer to it as within the CMakeLists.txt file).
The CMakeLists.txt file I'm referring to is shown below. Sorry if this question seems stupid but I'm totally lost and feeling very frustrated.
cmake_minimum_required(VERSION 2.4)
if(COMMAND cmake_policy)
cmake_policy(SET CMP0003 NEW)
endif(COMMAND cmake_policy)
project (bioreader)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/;${CMAKE_MODULE_PATH}")
# Find Qt libraries
INCLUDE(FindQt4) # will find QMake moc includes and libraries for us
INCLUDE(${QT_USE_FILE})
FIND_PACKAGE(Qt4 REQUIRED)
LINK_DIRECTORIES(${QT_LIBRARY_DIR})
# Find MongoDB libraries
FIND_PACKAGE(MongoDB REQUIRED)
# Find Boost libraries
FIND_PACKAGE(Boost REQUIRED)
# Custom dependencies
INCLUDE(AddFileDependencies)
# Qt/Qtopia
include (${QT_USE_FILE})
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtCore/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtGui/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtNetwork/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtScript/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtScriptTools/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtTest/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtXml/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtDBus/)
INCLUDE_DIRECTORIES(${QT_INCLUDE_DIR}/QtSql/)
# Optimized?
OPTION (OPTIMIZED "Compile in the optimized mode?" ON)
OPTION (ATOM "Optimize for Intel Atom 32-bit?" OFF)
OPTION (PROFILING "Add profiling information to executable?" OFF)
ADD_DEFINITIONS (-Wall -fno-strict-aliasing -Wno-strict-aliasing)
IF(OPTIMIZED)
ADD_DEFINITIONS (-O3)
IF (ATOM)
ADD_DEFINITIONS (-m32 -march=core2 -mtune=pentium -mfpmath=sse)
ENDIF (ATOM)
ELSE(OPTIMIZED)
ADD_DEFINITIONS (-g)
ENDIF(OPTIMIZED)
IF(PROFILING)
ADD_DEFINITIONS (-pg)
ENDIF(PROFILING)
# Ban exceptions? (used only for testing/refactoring)
OPTION (KILL_EXCEPTIONS "Disable support for exceptions?" OFF)
IF (KILL_EXCEPTIONS)
ADD_DEFINITIONS(-fno-exceptions)
ENDIF(KILL_EXCEPTIONS)
# Attempt to compile the code with ALL warnings enabled (ie. "pedantic
# mode")?
OPTION (PEDANTIC_MODE "Compile with all compiler warnings enabled?" ON)
IF (PEDANTIC_MODE)
ADD_DEFINITIONS (-pedantic -Wno-long-long -DUSING_PEDANTIC_MODE)
ENDIF (PEDANTIC_MODE)
# Linear algebra library
OPTION (LAPACK "Use LAPACK linear algebra library" ON)
IF(LAPACK)
MESSAGE(STATUS "Building using LAPACK.")
ADD_DEFINITIONS (-DUSING_LAPACK)
ELSE(LAPACK)
MESSAGE(STATUS "Building WITHOUT using LAPACK.")
ENDIF(LAPACK)
# Package subdirectories. These statements load the CMakeLists.txt files
# from the subordinate directories.
##
add_subdirectory (src/IO)
add_subdirectory (src/Instrument)
add_subdirectory (src/Protocol)
add_subdirectory (src/Script)
add_subdirectory (src/Physical)
add_subdirectory (rc/data)
add_subdirectory (rc/jslib)
add_subdirectory (rc/gfx)
add_subdirectory (rc/svninfo)
# Set up generic includes
include_directories (${bioreader_SOURCE_DIR})
include_directories (${bioreader_SOURCE_DIR}/src)
link_directories (${bioreader_BINARY_DIR})
link_directories (/usr/lib)
##
include_directories(${Boost_INCLUDE_DIRS})
link_directories(${Boost_LIBRARY_DIRS})
SET (bioreader_common_LIBS
Instrument
rcjslibCore
rcjslibBioScript
rcjslibRobot
rcgfx
rcsvninfo
rcdata
Physical
PhysicalController
PhysicalUnit
Protocol
IO
IOqextserialport
)
SET(bioreader_common_LIBS
${bioreader_common_LIBS}
QtCore
QtScript
QtScriptTools
QtNetwork
QtGui
QtTest
QtXml
QtDBus
QtSql
mongoclient.a
bsd
ssl
crypto
boost_system
boost_thread
boost_filesystem
boost_program_options
)
IF(LAPACK)
SET (bioreader_common_LIBS
${bioreader_common_LIBS}
lapack
blas
gfortran)
ENDIF(LAPACK)
SET (bioreader_reader_LIBS
InstrumentBuilderReader
InstrumentBuilderUnitTest
ProtocolTest
InstrumentTest
PhysicalControllerTest
PhysicalUnitTest
InstrumentBioTest
InstrumentBio
Script
PhysicalPump
PhysicalElevator
PhysicalMotor
PhysicalRobot
${bioreader_common_LIBS}
)
##
## BIOSCALE_QUICKAPP APPNAME SOURCES src1.cpp src2.cpp LIBS
##
MACRO(BIOSCALE_QUICKAPP APPNAME)
SET(__MODE "SOURCES") # default
FOREACH(ARG ${ARGN})
IF( ${ARG} STREQUAL "SOURCES" )
SET(__MODE "SOURCES")
ELSEIF( ${ARG} STREQUAL "LIBS" )
SET(__MODE "LIBS")
ELSE( ${ARG} STREQUAL "SOURCES" )
##
## source
##
IF( ${__MODE} STREQUAL "SOURCES")
SET(${APPNAME}_SRCS ${${APPNAME}_SRCS} ${ARG}) # append to var
ENDIF( ${__MODE} STREQUAL "SOURCES")
##
## libs
##
IF( ${__MODE} STREQUAL "LIBS")
SET(${APPNAME}_LIBS ${${APPNAME}_LIBS} ${ARG}) # append to var
ENDIF( ${__MODE} STREQUAL "LIBS")
ENDIF( ${ARG} STREQUAL "SOURCES" )
ENDFOREACH(ARG)
QT4_AUTOMOC(${${APPNAME}_SRCS})
add_executable(${APPNAME} ${${APPNAME}_SRCS})
target_link_libraries(${APPNAME} ${${APPNAME}_LIBS})
ENDMACRO(BIOSCALE_QUICKAPP)
## Configuration for all targets
SET (bioreader_SRCS src/universal.cpp)
# Executable target for unit tests
BIOSCALE_QUICKAPP (unittest
SOURCES ${bioreader_SRCS}
LIBS ${bioreader_reader_LIBS})
# Executable target for reader
BIOSCALE_QUICKAPP (reader
SOURCES ${bioreader_SRCS}
LIBS ${bioreader_reader_LIBS})
IF(PROFILING)
SET_TARGET_PROPERTIES(reader PROPERTIES LINK_FLAGS -pg)
ENDIF(PROFILING)
This is a big post and I may not get to answering all of it in one shot. But, I will start at the top and try to answer what I come across. It seems like any understanding will get you farther than you are. Who knows, maybe I'll even answer the real question.
Q1) "Nowhere in the CMakeLists.txt file is the variable bioreader_common_LIBS defined "
A1)
SET(bioreader_common_LIBS ${bioreader_common_LIBS} QtCore QtScript .... )
is defining bioreader_common_LIBS. What the set call is doing is saying, "Set bioreader_common_LIBS to whatever it's currently defined as ( note the syntx ${ .. } which gives you the value of the variable. ) plus what ever follow, e.g. the Qt libs. You can see how that same type of line is used lowe in your file, that is appending to the current value of bioreader_common_LIBS
Q2) bioreader_common_LIBS defined and its not contained in the cache.
A2) To add it to the cache, the set call would look like ..
set(bioreader_common_LIBS ${bioreader_common_LIBS} lib1 lib2 CACHE STRING "")
Q3 A3) The 'Useful CMake variables' are set by CMake when appropriate and and won't exist in your CMakeLists.txt necessarily, unless you are setting them to something you need. Id you want to see the value of any variable, try, e.g.
message( "var_name: ${var_name}" )
to see the value of var_name. Those messages will print when you configure the project.
Q4) The rest ...
A4) One thing to remember is, every subdirectory has a CMakeLists.txt file in that controls configuring/building the libraries therein.
To find your libraries, follow the example of Boost in your CMakeLists. Use a find_package(MySQL REQUIRED) call with your correct package name. You can find the CMake supplied Modules in the CMake_install_directory/Modules. If there isn't one, then you'll need to write it. Find modules provide several useful variable when then succeed. They are described in the file usually, but roughly you'll get ( using MySQL as the example ) MySQL_FOUND - tells if CMake found it, MySQL_INCLUDE_DIR - the path the headers, MySQL_LIBRARIES and/or MySQL_LIBRARY - the names of the library(s), MySQL_LIB_DIR - the library directory.
Now, you see in your CMake file, include_directories, you can add
include_directories(${MySQL_INCLUDE_DIR})
to your project to add it to your -I of the compiler args. You can use
link_directories(${MySQL_LIB_DIR})
to add that directory for linking. Now, to link your library, call
target_link_libraries(my_lib ${MySQL_LIBRARIES})
One thing to notice, your current CMake file has some bad habits going on, namely, it explicitly names the libraries to linke. That is not recommended as it binds you pretty tightly to the whims of the library providers. The names can change, so you're better foo always using the variables you're given from the find modules. Also, I don't believe you ever need to add
link_directories (/usr/lib)
since that directory is usually already in the default search path, though I guess it won't hurt to have it.
That should get you moving in the right direction, I've typed for too much for one sitting.
Related
I have project name libtld where I first create a tool¹:
project(tld_parser)
add_executable(${PROJECT_NAME}
../tools/tldc.cpp
tld_compiler.cpp
tld_file.cpp
tld_strings.c
)
Then I use that tool to generate the tld_data.c file, which is a table with all the TLDs (in my newer version of the library, this is a copy of the RIFF/TLDS binary file which is to be compiled internally as a fallback). Here is the add_custom_command() I use to generate said file:
project(tld_data)
set(TLD_DATA_C ${PROJECT_BINARY_DIR}/tld_data.c)
file(GLOB_RECURSE TLD_FILES ${CMAKE_SOURCE_DIR}/conf/tlds/*.ini)
add_custom_command(
OUTPUT ${TLD_DATA_C}
COMMAND "tld_parser"
"--source"
"${CMAKE_SOURCE_DIR}/conf/tlds"
"--verify"
"--output-json"
"--include-offsets"
"--c-file"
"${TLD_DATA_C}"
"${PROJECT_BINARY_DIR}/tlds.tld"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
MAIN_DEPENDENCY tld_parser
DEPENDS ${TLD_FILES}
)
add_custom_target(${PROJECT_NAME} ALL DEPENDS ${TLD_DATA_C})
define_property(SOURCE
PROPERTY GENERATED
BRIEF_DOCS "The tld_data.c file is a table of all the TLDs defined in conf/tlds/... .ini files."
FULL_DOCS "In the new version, the tld_data.c is always generated on the fly since it can be done with just C/C++."
)
Next I want to generate the tld dynamic and static libraries.
set(LIBTLD_SOURCES
tld.cpp
tld_compiler.cpp
${TLD_DATA_C}
tld_domain_to_lowercase.c
tld_emails.cpp
tld_file.cpp
tld_object.cpp
tld_strings.c
)
##
## TLD library
##
project(tld)
configure_file(
tld.h.in
${PROJECT_BINARY_DIR}/tld.h
)
add_library(${PROJECT_NAME} SHARED
${LIBTLD_SOURCES}
)
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${LIBTLD_VERSION_MAJOR}.${LIBTLD_VERSION_MINOR}
SOVERSION ${LIBTLD_VERSION_MAJOR}
)
install(
TARGETS ${PROJECT_NAME}
LIBRARY DESTINATION lib
COMPONENT runtime
)
install(
FILES ${PROJECT_BINARY_DIR}/tld.h
DESTINATION include
COMPONENT development
)
##
## TLD static library
##
project(tld_static)
add_library(${PROJECT_NAME} STATIC
${LIBTLD_SOURCES}
)
add_dependencies(${PROJECT_NAME}
tld
)
# We need the -fPIC to use this library as extension of PHP, etc.
set_target_properties(tld_static PROPERTIES COMPILE_FLAGS -fPIC)
install(
TARGETS ${PROJECT_NAME}
ARCHIVE DESTINATION lib
COMPONENT development
)
I added the add_dependencies() to the tld_static project to depend on tld thinking that would help my case, but I still see two run of the tld_parser...
I also had a test that directly includes the tld_data.c file (that way I can directly verify that the tld() function returns what I would expect from the exact source data).
Adding the add_dependencies() to the test worked as expected. Instead of getting the tld_data.c file created three times, now it's only twice.
project(tld_internal_test)
add_executable(${PROJECT_NAME}
tld_internal_test.cpp
)
add_dependencies(${PROJECT_NAME}
tld
)
add_test(
NAME ${PROJECT_NAME}
COMMAND ${PROJECT_NAME}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
By killing the parallel build feature, it works as expected, however, there should be no good reasons for me to do that (see this cmake question for reference).
What I'm wondering, though, is:
How do you debug such an issue?
cmake's Makefile's are enormous, so reading those is quite a killer. If I knew what to search for, I suppose I could at least look at specific targets instead of trying to understand all that's happening in there (most of which has nothing to do with my issue).
¹ The code is in a branch at the moment. I'm hoping to have it all wrapped up soon at which point it will replace the main branch. The tld() function interface will not have changed very much. The implementation, however, will be quite different, more flexible, dynamically updatable.
I'm trying to build simple-web-server using a local standalone copy of asios. As I don't have the library installed, and I can't install it due to security restrictions, I've modified the cmakelists file just a bit, to tell it where to search for the include file. I can clearly see that it's finding the location, but CHECK_INCLUDE_FILE_CXX isn't finding it for some reason, even though I've added the directory with target_include_directories. What is the correct way to do this?
if(USE_STANDALONE_ASIO)
### BEGIN CUSTOM ADDITIONS ###
message(NOTICE "Searching for asio.hpp")
find_path(ASIO_PATH asio.hpp ./asio/include)
if(ASIO_PATH-NOTFOUND)
message(AUTHOR_WARNING "Asio not found in ./asio/include")
else()
message(NOTICE "asio.hpp found in ${ASIO_PATH}")
target_include_directories(simple-web-server INTERFACE ASIO_PATH)
endif()
### END CUSTOM ADDITIONS ###
find_package(Threads REQUIRED)
target_link_libraries(simple-web-server INTERFACE ${CMAKE_THREAD_LIBS_INIT})
target_include_directories
target_compile_definitions(simple-web-server INTERFACE USE_STANDALONE_ASIO)
include(CheckIncludeFileCXX)
CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO)
if(NOT HAVE_ASIO)
message(FATAL_ERROR "Standalone Asio not found")
endif()
Command target_include_directories and include_directories affects on compilation, but doesn't affect on checking headers via CHECK_INCLUDE_FILE_CXX.
For hint the macro CHECK_INCLUDE_FILE_CXX to search in additional include directories, set variable CMAKE_REQUIRED_INCLUDES:
list(APPEND CMAKE_REQUIRED_INCLUDES ${ASIO_PATH})
...
CHECK_INCLUDE_FILE_CXX(asio.hpp HAVE_ASIO)
This variable is described in the documentation:
CMAKE_REQUIRED_INCLUDES
a list of header search paths to pass to the compiler.
While internally CHECK_INCLUDE_FILE_CXX performs compilation, it compiles in other CMake project (via try_compile). That other project doesn't receive properties (like INCLUDE_DIRECTORIES) of the main project. Instead, a specific set of variables is explicitly passed from the main project, and CMAKE_REQUIRED_INCLUDES is among that variables.
I have a top-level CMakeLists.txt which includes another third party project from a subdirectory, like
add_subdirectory(ext/third_party/cmake)
third_party contains a library target which I want to build, but I want to modify some properties and want to avoid to modify the original CMake file. I do not want to link some of my own targets to that library, I'd rather want that third party library to be build with some modified properties and then put it into a custom output directory. So I do
set_target_properties(libthird_party PROPERTIES
# some properties that successfully get applied here
LIBRARY_OUTPUT_DIRECTORY "/my/output/dir")
I can see that other properties are successfully applied and the build is modified to my needs correctly, but the generated output library is not put into the directory I set. What could be the reason for that?
If this is a totally wrong or bad approach please also feel free to propose a better approach for my goal.
Figured it out myself with help from the comments. I was trying to modify a static library target, which is not affected by LIBRARY_OUTPUT_DIRECTORY (which only applies to dynamic libraries) but which needs the setting ARCHIVE_OUTPUT_DIRECTORY. So the corrected call is
set_target_properties(libthird_party PROPERTIES
# some properties that successfully get applied here
ARCHIVE_OUTPUT_DIRECTORY "/my/output/dir")
You have to deal with [LIBRARY, ARCHIVE, EXECUTABLE] x [Single, Multi]config generator x [Unix, Windows]way.
note: On Windows everything (.dll, .exe) is on the same directory while on Unix you generally have a bin and a lib directories.
include(GNUInstallDirs)
# Single config (e.g. makefile, ninja)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
else()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})
endif()
# For multi-config build system (e.g. xcode, msvc, ninja-multiconfig)
foreach(OUTPUTCONFIG IN LISTS CMAKE_CONFIGURATION_TYPES)
string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
if(UNIX)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_LIBDIR})
else()
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG}
${CMAKE_BINARY_DIR}/${OUTPUTCONFIG}/${CMAKE_INSTALL_BINDIR})
endif()
endforeach()
I am using cmake 3.8.1 with the FindSWIG und UseSWIG "extensions" for using swig as code generator. The "swig .i" file contains an include statement to a file in a different directory. The important part of my CMakeLists.txt looks like this:
...
find_package (TCL REQUIRED)
if (TCL_FOUND)
set (HAVE_TCL_H 1)
endif (TCL_FOUND)
find_package (SWIG )
if (SWIG_FOUND)
include(${SWIG_USE_FILE})
set (SWIG_FILE /home/steve/cmake_games/src/foo/bar/bar_swig.i)
set_property(SOURCE ${SWIG_FILE} PROPERTY CPLUSPLUS ON)
swig_add_library (bar_tclext LANGUAGE tcl SOURCES ${SWIG_FILE})
include_directories (/home/steve/cmake_games/src/foo/bar /home/steve/cmake_games/src/this/that)
set_target_properties(bar_tclext PROPERTIES LINKER_LANGUAGE CXX)
swig_link_libraries(bar_tclext ${TCL_LIBRARY})
endif (SWIG_FOUND)
cmake generates make files without a problem. Executing make however leads to the following error message
/home/steve/cmake_games/src/foo/bar/bar_swig.i:3: Error: Unable to find 'that.iih'
that.iih is located in /home/steve/cmake_games/src/this/that
Looking at the swig call in the generated Makefile I can see that swig is not called with an include path.
You might think that CMakeLists.txt with absolute paths is weird. True! This is the case due to the fact that the CMakeLists.txt are generated by scripts that take advantage of our conventions in directory topology and naming. We want keep enforcing this by generating the CMakeLists.txt as part of the "configure" process.
What do I need to change in the CMakeLists.txt?
you should try to setup CMAKE_SWIG_FLAGS in your case something like:
list(APPEND CMAKE_SWIG_FLAGS
"-I/home/steve/cmake_games/src/foo/bar"
"-I/home/steve/cmake_games/src/this/that")
e.g.: https://github.com/google/or-tools/blob/master/cmake/python.cmake#L57
ps: if you have compile definitions you should also pass them to SWIG by hand since you can't say to swig macro to get all the properties of an associated target.
Lets say I have a project with two independent subprojects. If I understood cmake correctly, the idea would be to have one root CMakeLists.txt defining a project(...) and then using add_subdirectory(...) to include the subprojects. Each subproject would have its own CMakeLists.txt defining its own project. This way projects can be build either together (using the root cmake file) or individually (using the subprojects cmake file).
I now would like to change the CMAKE_CONFIGURATION_TYPES. Should I do this in the root CMakeLists.txt or in each subproject, or both?
Changing it in the root would mean that building a subproject individually would offer the wrong configuration types; the other options would duplicate the cmake code. I think I'm missing something here.
Factorize out the code that sets up configuration-dependent settings. Create a file, say, SetUpConfigurations.cmake with this content:
if(NOT SET_UP_CONFIGURATIONS_DONE)
set(SET_UP_CONFIGURATIONS_DONE TRUE)
# No reason to set CMAKE_CONFIGURATION_TYPES if it's not a multiconfig generator
# Also no reason mess with CMAKE_BUILD_TYPE if it's a multiconfig generator.
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(isMultiConfig)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Profile" CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE)
message("Defaulting to release build.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY HELPSTRING "Choose the type of build")
# set the valid options for cmake-gui drop-down list
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;Profile")
endif()
# now set up the Profile configuration
set(CMAKE_C_FLAGS_PROFILE "...")
set(CMAKE_CXX_FLAGS_PROFILE "...")
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "...")
endif()
Then include(..) this file at the beginning of the CMakeLists.txt's.
You have two choices about where to put SetUpConfigurations.cmake, it depends on how you organize your projects, repositories:
The quick'n'dirty way: Copy and commit this script into each project that needs it. Its location will be fixed, relative to the CMakeLists.txt of the project. So you can include it, for example, with include(${CMAKE_CURRENT_SOURCE_DIR}/<...>/SetUpConfigurations.cmake)
The disciplined way: Maintain a repository with your custom CMake scripts, like this one. Each time you generate a project with the cmake command, you pass the path to this repository in the CMAKE_MODULE_PATH variable:
cmake -DCMAKE_MODULE_PATH=<dir-of-cmake-script-repo> ...
In this case include the script with include(SetUpConfigurations) (no .cmake extension).
A note about what a multiconfig generator is:
Xcode and Visual Studio are multiconfig generators. They respect the value of CMAKE_CONFIGURATION_TYPES but CMAKE_BUILD_TYPE has no effect since no concrete configuration is defined when the CMakeLists.txt is processed. It will be selected on the IDE's user interface later.
On the other hand, the makefile-style generators are not interested in CMAKE_CONFIGURATION_TYPES. CMAKE_BUILD_TYPE defines the configuration. It is a concrete value when the CMakeLists.txt file is processed but still: never make any decisions based on the value of CMAKE_BUILD_TYPE:
if(CMAKE_BUILD_TYPE STREQUAL "Release") # WRONG!
....
endif()
You project won't work as intended in multiconfig generators.
When use add_subdirectory into subproject dir, you propagate almost all variables into that subproject, which contradicts to "subproject independency".
Instead, it is better to build and install subproject using nested cmake call inside execute_process(). If you want to make some subproject's definitions available for top-level project, you need to "export" this definitions when subproject is installed. This question/answer post describes, how to do that.