When to use CMAKE_CURRENT_LIST_DIR? - cmake

I'm trying to figure out for which commands it is safe to just use a relative path like src/foo.c and when to better use ${CMAKE_CURRENT_LIST_DIR}/src/foo.c
The commands I need help with are:
add_library(myLib
src/foo.c)
# or
add_library(myLib
${CMAKE_CURRENT_LIST_DIR}/src/foo.c)
add_executable(myExe
src/bar.c)
# or
add_executable(myExe
${CMAKE_CURRENT_LIST_DIR}/src/bar.c)
target_include_directories(myLib
include)
# or
target_include_directories(myLib
${CMAKE_CURRENT_LIST_DIR}/include)
target_sources(myExe
src/foobar.c
src/baz.c)
# or
target_sources(myExe
${CMAKE_CURRENT_LIST_DIR}/src/foobar.c
${CMAKE_CURRENT_LIST_DIR}/src/baz.c)
I know that this should only be an issue when someone is using include() instead of add_subdirectory() in the top-level CMakeLists.txt. Would I even ever want to use include() instead of add_subdirectory() for a CMakeLists file containing not much more than the commands above and could maybe just don't care and use simple relative paths all the time?
Cheers,
andb

Related

CMake: Modifying library output directory of a target from a sub-project

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()

CMake: Best practices for differing lib paths with ExternalProject_Add

I have a small project whose CMakeLists.txt looks something like this:
include(ExternalProject)
ExternalProject_Add(gainput_project
PREFIX gainput
GIT_REPOSITORY https://github.com/jkuhlmann/gainput.git
GIT_TAG e21b15f0bc3dd3f1a745fe89a966a2457e940142
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(gainput_project SOURCE_DIR)
ExternalProject_Get_Property(gainput_project BINARY_DIR)
set(GAINPUT_SOURCE_DIR "${SOURCE_DIR}")
set(GAINPUT_BINARY_DIR "${BINARY_DIR}")
add_executable(demo main.cpp)
add_dependencies(demo gainput_project)
target_include_directories(demo PUBLIC
SYSTEM ${GAINPUT_SOURCE_DIR}/lib/include
)
# TODO: this is bad
if(WIN32)
target_link_libraries(demo PUBLIC
debug ${GAINPUT_BINARY_DIR}/lib/Debug/gainput-d.lib
optimized ${GAINPUT_BINARY_DIR}/lib/Release/gainput.lib
)
else()
target_link_libraries(demo PUBLIC
debug ${GAINPUT_BINARY_DIR}/lib/gainput-d.so
optimized ${GAINPUT_BINARY_DIR}/lib/gainput.so
)
endif()
Obviously, those calls to target_link_libraries are not ideal; they are very repetitive and I am explicitly writing paths and filenames that the build system should already know. What is the best way to handle the different output directories and filenames from the external project?
Is there a way I can query the external project to get the actual output dir? (e.g. via ExternalProject_Get_Property?) Is there some magical generator expression I can use? (ExternalProject_Get_Property(gainput_project RUNTIME_OUTPUT_DIRECTORY) does not work.)
It seems like find_library would work, except that the output library does not exist at configure time, so that fails.
Maybe I should set the external project's INSTALL_DIR to some hard-coded directory that I control and always use that? (Would it still get the Debug and Release subdirectories on Windows?)
Would the FetchContent module give me better access to the outputs of the included project? I don't see anything obvious in the docs there.
Idk, I've already tried quite a few ideas and nothing has worked so far. I feel like I may be missing something fundemental - this is the first time I've used ExternalProject. Any tips?
As #Tsyvarev suggested, I solved this by switching from ExternalProject to FetchContent and then using a generator expression to find the output file:
include(FetchContent)
FetchContent_Declare(gainput
PREFIX gainput
GIT_REPOSITORY https://github.com/jkuhlmann/gainput.git
GIT_TAG e21b15f0bc3dd3f1a745fe89a966a2457e940142
INSTALL_COMMAND ""
)
FetchContent_MakeAvailable(gainput)
add_executable(demo main.cpp)
add_dependencies(demo gainput)
target_include_directories(demo PUBLIC
SYSTEM ${gainput_SOURCE_DIR}/lib/include
)
target_link_libraries(demo PUBLIC
$<TARGET_LINKER_FILE:gainput>
)

How to pass variable to cpack?

I have a cmake project which one of the install targets is a collection of files. This files change depending on the configuration (Release, Debug...).
I would like to be able to install the files like so:
install(DIRECTORY $<TARGET_FILE_DIR:tgt>
DESTINATION bin
COMPONENT files)
But cmake does not support that. Generator variables do not apply to DIRECTORY. So I was wondering if there is a way to either save the directory somewhere. Either the cache or a file and then load it into cpack.
So I guess the question is how to pass a variable from cmake to cpack?
This is a rather late answer, but I happened upon this question trying to solve a somewhat different problem that could also be summarized as: "How do I pass a variable to CPack?" In my case, I was making this call from a customized version of CPackDeb.cmake copied to my workspace:
find_program(OPKG_CMD NAMES opkg-build HINTS "${OPKG_HINT}")
# ^^^^^^^^^^^^
# This is what I wanted to pass to CPack
I was setting OPKG_HINT in a file included from my top-level CMakeLists.txt, but it was not getting passed through to cpack; the above find_program() invocation was seeing an empty string for OPKG_HINT.
The solution turned out to be stupid simple: just prepend CPACK_ to the variable name!
If I do this in CMakeLists.txt:
set(CPACK_OPKG_HINT "${_sysroot_top}/aarch64-poky-linux/usr/bin")
then I can put this in my CPackDeb.cmake file and it works fine:
find_program(OPKG_CMD NAMES opkg-build HINTS "${CPACK_OPKG_HINT}")
Anyway, this wound up being a bit of an X-Y problem for the OP, but... if you really need to set a variable at CMake time in such a way that it's accessible to cpack, prefixing the variable name with CPACK_ seems to do the trick nicely...
The following setup work if you use a "single-configuration generators (such as make and Ninja)" and call CMake with
cmake -DCMAKE_BUILD_TYPE=Release <source_dir>
https://cmake.org/cmake/help/v3.0/variable/CMAKE_BUILD_TYPE.html
You can define the ${dir} variable in another way if you like.
IF (CMAKE_BUILD_TYPE STREQUAL "Release")
SET(dir release_dir)
ELSE()
SET(dir debug_dir)
ENDIF()
INSTALL(DIRECTORY ${dir} DESTINATION bin COMPONENT files)
Until now this seems to be the best answer (from someone on the cmake mail list)
install(DIRECTORY path/to/Debug/dir
DESTINATION bin
CONFIGURATIONS Debug
COMPONENT files
)
install(DIRECTORY path/to/Release/dir
DESTINATION bin
CONFIGURATIONS Release
COMPONENT files
)
CMake 3.5 supports generator expressions for the DIRECTORY arguments. See installing directories.

Multiple CMakeLists

Suppose I have a directory structure as follows:
/main.cpp
/CMakeLists.txt
/foo/foo.cpp
/foo/CMakeLists.txt
where my /CMakeLists.txt file contains the following:
project(Test)
add_subdirectory("foo")
add_executable(Test main.cpp foo/foo.cpp)
target_link_libraries(Test ${OpenNI_LIB})
and my /foo/CMakeLists.txt file contains the following:
find_library(OpenNI REQUIRED)
When I use the line add_subdirectory("foo") in the first CMakeLists.txt, what actually happens? Does it search for a second CMakeLists.txt file in foo, and add the contents to the first? Will any variables defined the second file be available in the first? And specifically in this example, will the variable ${OpenNI_LIB} be recognised in the first, given that it is defined in the second?
Thanks.
Does it search for a second CMakeLists.txt file in foo
Yes, it does
and add the contents to the first?
No it doesn't. It performs a number of configuring actions such as finding libraries etc and a generates separate Makefile and/or other build-time artifacts.
And specifically in this example, will the variable ${OpenNI_LIB} be recognised in the first, given that it is defined in the second?
no, unless such a construction
find_library(OpenNI REQUIRED) # this sets variables for OpenNI
# in the context of foo/CMakeLists.txt
set(OpenNI_LIB ${OpenNI_LIB} PARENT_SCOPE) # this copies ${OpenNI_LIB}
# into the context of /CMakeLists.txt
is used in foo/CMakeLists.txt
By default variables defined in a subdirectory's CMakeLists.txt are also defined in the subdirectory's subdirectories, that is if foo/ in turn contained bar/ with its own CMakeLists.txt, then within bar/'s CMakeLists.txt ${OpenNI_LIB} would be set.
P.S. message(STATUS "Some message with ${VAR}") in doubtful places of CMakeLists.txt is your friend. Just look into cmake output.

cmake use one cmakelist.txt for a project with subdirectories

i like to structure my code in multiple subdirs but i dont want to create a new cmakelist.txt in each new subdir.
my folder structure is something like this:
project
>cmakelist.txt
>build
>src
>main.cpp
>multiple_subdirs_or_(c|h)pp_files_with_more_subdirs_or_(c|h)pp_files
my cmakelist.txt looks like this:
...
file(GLOB_RECURSE cpps RELATIVE ${CMAKE_CURRENT_LIST_DIR} "src/*.cpp")
file(GLOB_RECURSE hpps RELATIVE ${CMAKE_CURRENT_LIST_DIR} "src/*.hpp")
#remove files with main
list(REMOVE_ITEM cpps "src/test.cpp")
#bins
add_executable(test src/test.cpp src/test.cpp ${hpps} ${cpps})
#same problem if this is used instead of the other add_executable
add_library(foo OBJECT ${cpps} ${hpps})
add_executable(test src/test.cpp $<TARGET_OBJECTS:foo>)
the problem with my file:
source files created after the execution of cmake are not compiled and the build fails if they are used.
as predicted by http://www.cmake.org/cmake/help/v3.0/command/file.html in section GLOB:
We do not recommend using GLOB to collect a list of source files from
your source tree. If no CMakeLists.txt file changes when a source is
added or removed then the generated build system cannot know when to
ask CMake to regenerate.
the question: is it possible to use a single cmakelist.txt for a project with multiple sub directories? (without the problems of file(GLOB ...) )
You have two totally unrelated things here.
First, can you use only a single CMakeLists.txt file for your whole project? Yes, of course you can (although I'd personally not go this way after a project has reached a certain size), and you're already doing this.
Second, the problem with GLOB. You already quoted the part of the documentation where it states what problems the use of GLOB has. This cannot really be avoided at the moment if you want to continue using GLOB, as this is part of the cmake design where they distinguish between what is done during configure and build time. The alternative is to list all files manually. Whether you do this in a single CMakeLists.txt file in your projects main directory, or in multiple files across your subdirectories does not matter.
To answer your question: yes, it is possible to handle a project with multiple sub-directories and one CMakeLists.txt. I have two considerations for you to take into account:
I strongly recommend you not using file(GLOB ...) for sources.
You have to list the files manually. For example (src/ is the source-subdirectory):
set(cpps src/file1.cpp src/file2.cpp src/file3.cpp)