CMake: install(FILES ...) for a file containing a list of files - cmake

Consider I have a simple text file containing a list of (absolute) file paths.
Is there any easy way to make CMake install those files (as if install(FILES ...) was used)?
Directory structure (minus some constant leading path components) should be maintained.
Right now the only option I could come up with was to use
install(CODE "execute_process(COMMAND my_script.sh)")
and do it using normal shell commands, but that seems to defeat the purpose of using a build system in the first place...

I believe this would do the trick:
# 'filename' is the file that contains a list ';' separated paths relative to that input file
function(install_my_files filename)
file(READ ${filename} relative_paths)
get_filename_component(parent_directory ${filename} DIRECTORY) # parent directory of input file
foreach(relative_path ${relative_paths})
get_filename_component(relative_directory ${relative_path} DIRECTORY)
install(FILES "${parent_directory}/${relative_path}" DESTINATION ${relative_directory})
endforeach()
endfunction()

Related

Debug CMake find_library

Is it possible to debug find_library from CMake?
What I want is a list of considered paths. My use case is a call like
find_library (FOO_LIBRARY
NAMES foo foo.so.0)
and there is /lib64/libfoo.so.0 on my system. However CMake does not find it. I checked that FIND_LIBRARY_USE_LIB64_PATHS is set to TRUE.
With CMake 3.17 this got added:
The “CMAKE_FIND_DEBUG_MODE” variable was introduced to print extra
find call information during the cmake run to standard error. Output
is designed for human consumption and not for parsing.
So you pass either -DCMAKE_FIND_DEBUG_MODE=ON or --debug-find to your CMake command.
Here is an example output when searching for libFOO:
find_library considered the following locations:
/usr/local/lib64/(lib)FOO(\.so|\.a)
/usr/local/lib/(lib)FOO(\.so|\.a)
/usr/local/lib64/(lib)FOO(\.so|\.a)
/usr/local/lib/(lib)FOO(\.so|\.a)
/usr/local/lib64/(lib)FOO(\.so|\.a)
/usr/local/(lib)FOO(\.so|\.a)
/usr/lib64/(lib)FOO(\.so|\.a)
/usr/lib/(lib)FOO(\.so|\.a)
/usr/lib64/(lib)FOO(\.so|\.a)
/usr/lib/(lib)FOO(\.so|\.a)
/usr/lib64/(lib)FOO(\.so|\.a)
/usr/(lib)FOO(\.so|\.a)
/lib64/(lib)FOO(\.so|\.a)
/lib/(lib)FOO(\.so|\.a)
/opt/(lib)FOO(\.so|\.a)
The item was not found.
I know this isn't a complete answer, but I had the same problem and found that it was necessary to add debug logging to find_library in the CMake source code. I submitted a pull request (work in progress) to add this to mainstream CMake.
I eliminated some possible sources of errors by logging an error message with some relevant details if find_library fails, like this:
set(libssl_names
ssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}
ssl${_OPENSSL_MSVC_RT_MODE}
ssl
ssleay32${_OPENSSL_MSVC_RT_MODE}
ssleay32
)
find_library(SSL_EAY_DEBUG
NAMES ${libssl_names}
NAMES_PER_DIR
PATHS ${OPENSSL_ROOT_DIR}
PATH_SUFFIXES ${_OPENSSL_PATH_SUFFIXES}
NO_DEFAULT_PATH
)
if(NOT SSL_EAY_DEBUG)
message(FATAL_ERROR "OPENSSL_ROOT_DIR is set to '${OPENSSL_ROOT_DIR}', but did not find any file matching ${OPENSSL_ROOT_DIR}/{${_OPENSSL_PATH_SUFFIXES}}/${CMAKE_FIND_LIBRARY_PREFIXES}{${libssl_names}}${CMAKE_FIND_LIBRARY_SUFFIXES}")
endif()
Which outputs something like:
CMake Error at CMakeLists.txt:526 (message):
OPENSSL_ROOT_DIR is set to '../../Install/openssl/', but did not find any
file matching
../../Install/openssl//{lib/VC/static;VC/static;lib}/lib{ssl64MT;sslMT;ssl;ssleay32MT;ssleay32}.a
Apart from semicolons (instead of commas) in the {braced} portions of the above pattern, and regexes if multiple CMAKE_FIND_LIBRARY_SUFFIXES are configured (e.g. .lib .a on Windows), this is the correct form for shell expansion to a list of paths, which you can pass to ls to check for their existence:
$ ls ../../Install/openssl//{lib/VC/static,VC/static,lib}/lib{ssl64MT,sslMT,ssl,ssleay32MT,ssleay32}.a
ls: ../../Install/openssl//VC/static/libssl.a: No such file or directory
ls: ../../Install/openssl//VC/static/libssl64MT.a: No such file or directory
ls: ../../Install/openssl//VC/static/libsslMT.a: No such file or directory
ls: ../../Install/openssl//VC/static/libssleay32.a: No such file or directory
ls: ../../Install/openssl//VC/static/libssleay32MT.a: No such file or directory
ls: ../../Install/openssl//lib/VC/static/libssl.a: No such file or directory
ls: ../../Install/openssl//lib/VC/static/libssl64MT.a: No such file or directory
ls: ../../Install/openssl//lib/VC/static/libsslMT.a: No such file or directory
ls: ../../Install/openssl//lib/VC/static/libssleay32.a: No such file or directory
ls: ../../Install/openssl//lib/VC/static/libssleay32MT.a: No such file or directory
ls: ../../Install/openssl//lib/libssl64MT.a: No such file or directory
ls: ../../Install/openssl//lib/libsslMT.a: No such file or directory
ls: ../../Install/openssl//lib/libssleay32.a: No such file or directory
ls: ../../Install/openssl//lib/libssleay32MT.a: No such file or directory
../../Install/openssl//lib/libssl.a
It's not obvious (at least to me) that:
relative paths (like ../../Install above) are actually relative to the original source directory (not the current project build directory, which CMake calls CMAKE_BINARY_DIR. (So you should run ls from there, not your build directory).
FIND_LIBRARY_USE_LIB64_PATHS, which is often ON by default, results in your original paths being replaced by mangled ones (lib -> lib64) (not just supplemented by additional search paths, but completely replaced).
Other, even less well-known properties such as FIND_LIBRARY_USE_LIB32_PATHS result in similar mangling (lib -> lib32).
This mangling can be disabled with set(CMAKE_FIND_LIBRARY_CUSTOM_LIB_SUFFIX "").

CMake qt5_add_translation, how to specify the output path?

I use qt5_add_translation to run lrelease and generate the .qm files. By default the .qm files are put at the root level of the build dir, no matter where you put the .ts files in the source dir.
How can I specify a subdir for those files in the build ?
Set a property on the .ts files before calling the Qt macro :
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION your_output_path)
Where TS_FILES contains the list of the .ts files and your_output_path is the path where to put the .qm files (relative to the build directory or absolute).
Because the macro will retrieve the property to make the path of the .qm files (tested with Qt 5.9) :
get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION)
if(output_location)
file(MAKE_DIRECTORY "${output_location}")
set(qm "${output_location}/${qm}.qm")
else()
set(qm "${CMAKE_CURRENT_BINARY_DIR}/${qm}.qm")
endif()
Use manual call of lrelease and lupdate utilities
set(TS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/translations")
set(TS_FILES
"${TS_DIR}/${PROJECT_NAME}_ru_RU.ts"
)
find_program(LUPDATE_EXECUTABLE lupdate)
find_program(LRELEASE_EXECUTABLE lrelease)
foreach(_ts_file ${TS_FILES})
execute_process(
COMMAND ${LUPDATE_EXECUTABLE} -recursive ${CMAKE_SOURCE_DIR} -ts ${_ts_file})
execute_process(
COMMAND ${LRELEASE_EXECUTABLE} ${_ts_file})
endforeach()

Add all files under a folder to a CMake glob?

I've just read this:
CMake - Automatically add all files in a folder to a target?
With the answer suggesting a file glob, e.g.:
file(GLOB "*.h" "*.cpp")
now, what if I want my target to depend on all files of a certain type under a certain folder - which might be within multiple subfolders? I tried using
execute_process(COMMAND find src/baz/ -name "*.cpp" OUTPUT_VARIABLE BAR)
and then
add_executable(foo ${BAR}
but this gives me the error:
Cannot find source file:
src/baz/some/file/here
src/baz/some/other_file/here
src/baz/some/other_file/here2
(yes, with that spacing.)
What am I doing wrong here?
Turning my comment into an answer
If you want to add recursive searching for files use file(GLOB_RECURSE ...)
file(GLOB_RECURSE source_list "*.cpp" "*.hpp")
Your second example would translate into
file(GLOB_RECURSE BAR "src/baz/*.cpp")
References
file(...)
Is it better to specify source files with GLOB or each file individually in CMake?

CMake: adding custom resources to build directory

I am making a small program which requires an image file foo.bmp to run
so i can compile the program but to run it, i have to copy foo.bmp to 'build' subdirectory manually
what command should i use in CMakeLists.txt to automatically add foo.bmp to build subdirectory as the program compiles?
In case of this might help, I tried another solution using file command. There is the option COPY that simply copy a file or directory from source to dest.
Like this:
FILE(COPY yourImg.png DESTINATION "${CMAKE_BINARY_DIR}")
Relative path also works for destination (You can simply use . for instance)
Doc reference: https://cmake.org/cmake/help/v3.0/command/file.html
To do that you should use add_custom_command to generate build rules for file you needs in the build directory. Then add dependencies from your targets to those files: CMake only build something if it's needed by a target.
You should also make sure to only copy files if you're not building from the source directory.
Something like this:
project(foo)
cmake_minimum_required(VERSION 2.8)
# we don't want to copy if we're building in the source dir
if (NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# list of files for which we add a copy rule
set(data_SHADOW yourimg.png)
foreach(item IN LISTS data_SHADOW)
message(STATUS ${item})
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${item}"
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${item}" "${CMAKE_CURRENT_BINARY_DIR}/${item}"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${item}"
)
endforeach()
endif()
# files are only copied if a target depends on them
add_custom_target(data-target ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/yourimg.png")
In this case I'm using a "ALL" custom target with a dependency on the yourimg.png file to force the copy, but you can also add dependency from one of your existing targets.

How can I install a hierarchy of files using CMake?

I've created a list of files using:
file(GLOB_RECURSE DEPLOY_FILES "${PROJECT_SOURCE_DIR}/install/*")
I want to install all of these files in /usr/myproject/, but I want to maintain the file tree on the installed folder:
install/junk
install/other/junk2
install/other/junk3
If I use:
install(FILES ${DEPLOY_FILES} DESTINATION "usr/myproject")
All the files end up in /usr/myproject as:
/usr/myproject/junk
/usr/myproject/junk2
/usr/myproject/junk3
How can I make the install command keep track of relative paths?
I've worked around the issue by doing it manually in a for loop:
set(BASE "${PROJECT_SOURCE_DIR}/install")
foreach(ITEM ${DEPLOY_FILES})
get_filename_component(ITEM_PATH ${ITEM} PATH)
string(REPLACE ${BASE} "" ITEM_PATH ${ITEM_PATH})
install(FILES ${ITEM} DESTINATION "usr/myproject${ITEM_PATH}")
endforeach()
...but this is annoying to do. Surely there's an easier way?
(I can't see anything in the install documentation though...)
The simplest way to install everything from a given directory is:
install(DIRECTORY mydir/ DESTINATION dir/newname)
Trailing '/' is significant.
Without it mydir would be installed to newname/mydir.
From the CMake documentation:
The last component of each directory name is appended to the destination
directory but a trailing slash may be used to avoid this because it
leaves the last component empty.
I am assuming you have a list of files, let's say INCLUDE_FILES. Possibly a selection of files spread over a number of subdirectories, e.g. header files from across the source tree, as opposed to everything in a single subdir like in the other answers.
You can loop over the file list and use get_filename_component() to extract the directory part, then use that in a subsequent install() to set the DESTINATION subdirectory:
foreach ( file ${INCLUDE_FILES} )
get_filename_component( dir ${file} DIRECTORY )
install( FILES ${file} DESTINATION include/${dir} )
endforeach()
Done. ;-)
Edit: If all the files you want to install that way match a particular file pattern -- e.g. "all header files" -- then brno's answer has the edge over this one.
Use:
INSTALL( DIRECTORY <directory> DESTINATION usr/myproject )
(See here for details: http://www.cmake.org/cmake/help/v2.8.8/cmake.html#command:install)
INSTALL( DIRECTORY ... ) preserves the directory structure. But, if you use install as <directory>, you would end up with usr/myproject/install/.... which is not what you want.
There are two ways to do this:
Use INSTALL( FILES .... DESTINATION usr/myproject) to install the files that lie directly in install/, then use INSTALL( DIRECTORY .... DESTINATION usr/myproject) and manually list the directories to install.
Use your globbing command in your original post, and then determine which entries are files, which are directories, move the directory entries to a separate list, feed the lists to INSTALL( FILES ...) and INSTALL( DIRECTORY ...), respectively.
file(GLOB DEPLOY_FILES_AND_DIRS "${PROJECT_SOURCE_DIR}/install/*")
foreach(ITEM ${DEPLOY_FILES_AND_DIRS})
IF( IS_DIRECTORY "${ITEM}" )
LIST( APPEND DIRS_TO_DEPLOY "${ITEM}" )
ELSE()
LIST( APPEND FILES_TO_DEPLOY "${ITEM}" )
ENDIF()
endforeach()
INSTALL( FILES ${FILES_TO_DEPLOY} DESTINATION usr/myproject )
INSTALL( DIRECTORY ${DIRS_TO_DEPLOY} DESTINATION usr/myproject )
Note: Depending on the type of files you install, other INSTALL( ...) commands might be more appropriate (for example, INSTALL( TARGETS .... ) to install your libraries/executables.
Since globbing is not recommended, and running loops in CMakeLists.txt files is clunky, a pattern matching option of DIRECTORY did the trick for me.
install(DIRECTORY src/ DESTINATION include FILES_MATCHING PATTERN "*.h")
This took the entire folder structure inside src/ and reproduced it within <INSTALL_DIR>/include, header files only.