How to add dependencies to a custom_target() after the fact? - cmake

I'm trying to have a top level target, we'll call it sdk_extraction that is tested/built every time, and had depends on a bunch of generated files that are basically copies or minor transformations of files in the tree.
I build up the list of files to export as a property string/list, then when it's all said and done take that list and iterate through it, creating custom_commands for each one and then adding a dependency between the output file and the sdk_extraction target.
function(finalize_sdk_export destination)
file(REAL_PATH ${destination} destination )
add_custom_target(sdk_target ALL
COMMENT "Exporting SDK"
)
get_property(sdk_source GLOBAL PROPERTY SDK_SOURCE )
string(REPLACE " " ";" sdk_source "${sdk_source}")
foreach (item ${sdk_source})
string(REPLACE ${destination} ${destination}/sdk target "${item}")
add_custom_command(
OUTPUT ${target}
DEPENDS ${item}
COMMAND ${CMAKE_COMMAND} -E copy ${item} ${target}
VERBATIM
)
add_dependencies(sdk_target ${target})
endforeach()
endfunction()
This fails with an error:
CMake Error at <redacted>/build_scripts/CMakeLists.txt:209 (add_dependencies):
The dependency target
"<redacted>/sdk/CMakeLists.txt"
of target "sdk_target" does not exist.
Call Stack (most recent call first):
<redacted>/CMakeLists.txt:78 (finalize_sdk_export)
Does this not work? I WAS able to do this:
function(finalize_sdk_export destination)
file(REAL_PATH ${destination} destination )
get_property(sdk_source GLOBAL PROPERTY SDK_SOURCE )
string(REPLACE " " ";" sdk_source "${sdk_source}")
foreach (item ${sdk_source})
string(REPLACE ${destination} ${destination}/sdk target "${item}")
add_custom_command(
OUTPUT ${target}
DEPENDS ${item}
COMMAND ${CMAKE_COMMAND} -E copy ${item} ${target}
VERBATIM
)
list(APPEND sdk_files ${target})
endforeach()
add_custom_target(sdk_target ALL
COMMENT "Exporting SDK"
DEPENDS ${sdk_files}
)
endfunction()
Where I make all the custom_commands and keep a list of each ones output, then make the call to add_custom_target() with them all specified in the DEPENDS parameter.
This works for now, but I'm still just testing and I'm unsure it'll hold up if I end up having hundreds/thousands of files in the export.
So, how am I supposed to do this correctly?
(FYI, I gave up with the built in cmake install/export functionality because we really want to have control over the directory structure and look/feel of the CMake files)

All files, from which a custom target depends, should be specified in the DEPENDS option for add_custom_target() call. There is no way to add dependencies from the files afterwards.
The command add_dependencies can add only dependencies between targets.
Unlike to add_dependencies, which may connect targets creates in different directories, all generated files in DEPENDS clause should be created by add_custom_command in the same directory only. That is, it is usually not
a problem to postpone creation of the target unless all dependent files are calculated.

Related

How to duplicate cmake target

I am writing function in my cmake project that needs to make two targets from one, and alter slightly one of them:
option(BORG_STRIP_TEST_BINARIES OFF "Strip symbols from test binaries to reduce disk space usage" )
function(add_borg_test target)
add_test(${target} ${target} --gtest_color=yes)
if(BORG_STRIP_TEST_BINARIES)
# copy target, but make it optional
duplicate_target(FROM ${target} TO ${target}_debug )
set_target_properties(${target}_debug PROPERTIES EXCLUDE_FROM_ALL TRUE)
# alter
target_link_options(${target} PRIVATE -s)
endif()
endfunction()
So this is supposed to work like this:
I create binary target that uses gtest. Set up all target_link_libraries and everything. Example name example-utests
instead add generic add_test, I use custom add_borg_test
now example-utests links with flag -s and is included in all target.
example-utests_debug is NOT included in all target but when requested explicitly, it links without -s.
How to implement duplicate_target from above code snippet?

Dealing with the separator in CMake

I'm trying to compile some Java code with CMake (I'm aware that Java is not really the use-case for CMake) and I want to provide the class paths for the files. The compilation should work on both Unix and Windows systems. The problem I have is with separating the different class paths. Using:
set(CLASS_PATH ${PATH1} ${PATH2})
message(STATUS "${CLASS_PATH}")
prints
<PATH1>;<PATH2>
But this happens on both Unix and Windows. So I have to manually add separators. The way I'm doing it is
if(${CMAKE_HOST_WIN32})
set(SEP "\;")
elseif(${CMAKE_HOST_UNIX})
set(SEP ":")
endif(${CMAKE_HOST_WIN32})
Is this really the best way to deal with separators? I feel like I'm missing something.
Update - MCVE
To describe my thought: FILE_LIST would be contain all the java files that I want to compile. I defined a custom function which I can call on this FILE_LIST and compile the files. Maybe I'm doing something wrong with the function parameters?
cmake_minimum_required(VERSION 3.11)
set(CLASS_PATH E:/tmp/cmake/separator C:/tmp/)
set(FILE_LIST 1.txt 2.txt 3.txt)
add_custom_target(war ALL)
function(compile_java clp)
foreach(java_file ${ARGN})
add_custom_command(
TARGET war
PRE_BUILD
COMMAND echo "${clp}" ${java_file}
)
endforeach(java_file)
endfunction()
compile_java("${CLASS_PATH}" ${FILE_LIST}) # I have to pass CLASS_PATH in quotes
So, based on comments, you want the path list as a single command-line argument, with a platform-specific separator. You can achieve this using string operations:
function(compile_java clp)
if(NOT CMAKE_HOST_WIN32)
string(REPLACE ";" ":" clp "${clp}")
endif()
foreach(java_file ${ARGN})
add_custom_command(
TARGET war
PRE_BUILD
COMMAND echo "${clp}" ${java_file}
)
endforeach(java_file)
endfunction()

CMake - copying/merging files with dependencies a change-checking

I have a big project with one executable, some plugins and web interface with some generated JSONs.
Therefore, after I compile executables and .so plugins, I'm doing following:
Merge all .js files into one big
Compile "generators" (set of macros and printfs to describe some structures in C++ code)
Run generators and generate JSON files (with some sed and jshon) processing
In install phase, and copy all of this and some other files to their destination directories (which should be created if doesn't exists).
But I don't know, how to use CMake to make correct dependencies and date-time checking. Actually, first step is made with:
FILE(GLOB WEB_INPUT_JS *.js)
FILE(WRITE scripts.js.tmp "")
FOREACH(SCRIPTFILE ${WEB_INPUT_JS})
FILE(READ ${SCRIPTFILE} CONTENTS)
FILE(APPEND scripts.js.tmp "${CONTENTS}")
ENDFOREACH()
CONFIGURE_FILE(scripts.js.tmp ${WEB_BUILD_PATH}/scripts.js COPYONLY)
But this doesn't create dependency in makefiles. I want to re-run this piece of "code", when some of ${WEB_INPUT_JS} files has been changed or ${WEB_BUILD_PATH}/scripts.js has been deleted.
Third step is made with series of
add_custom_command(TARGET gen_somedata POST_BUILD COMMAND gen_somedata | sed ${JSON_SED} | jshon > ${JSON_BUILD_PATH}/somedata.json)
install (FILES ${JSON_BUILD_PATH}/somedata.json ......nextfiles.... DESTINATION ${JSON_OUTPUT_PATH})
How is this done? Thanks much for your answers!
So I've finally found out, how to do some of this things.
Merging files is pretty tricky.
First, cmake "script" doing merging is needed (I will explain some lines later). I will name it "concat.cmake":
FUNCTION(CONCAT_FILES OUTPUT FILELIST)
FILE(WRITE ${OUTPUT} "")
FOREACH(SCRIPTFILE ${FILELIST})
FILE(READ ${SCRIPTFILE} CONTENTS)
FILE(APPEND ${OUTPUT} "${CONTENTS}")
ENDFOREACH()
ENDFUNCTION(CONCAT_FILES)
STRING(REPLACE "," ";" FILELIST ${FILELIST})
CONCAT_FILES(${OUTPUT} "${FILELIST}")
Then, when merging script is used as follows (write it into CMakeLists.txt):
1) First, make an file list (using globbing or by writing file list by hand).
FILE(GLOB INPUT_FILES_LIST *.js) # get list of JS files
2) The only way, how to pass a cmake list to other cmake script is creating file list separated by comma, then passing comma-separated list to external script. I've done this following way:
SET(FILELIST "")
FOREACH(ITEM ${INPUT_FILES_LIST})
SET(FILELIST "${FILELIST},${ITEM}") # append list item by ','
ENDFOREACH()
STRING(SUBSTRING ${JSFILES} 1 -1 JSFILES) # remove first ','
3) Now it's not problem to call merging script..
add_custom_command(OUTPUT some_output_file.ext
COMMAND ${CMAKE_COMMAND} -DFILELIST=${FILELIST} -DOUTPUT=some_output_frile.ext -P ${CMAKE_CURRENT_SOURCE_DIR}/concat.cmake
DEPENDS ${INPUT_FILES_LIST} VERBATIM )
The precedent code will correctly track changes in input files and output file will be generated when missing or input changes. Installation is just easy as
INSTALL (FILES "output.ext" DESTINATION /usr/share/...)

CMake Warning: You have called ADD_LIBRARY for library my_src without any source files

I'm trying to call add_library for all files with certain endings.
The dir structure is:
src
| - CMakeLists.txt (1)
| - main.cpp
| - gui
| - CMakeLists.txt (2)
| - some source and header files
So currently all cc files are in the gui directory.
(1) CMakeLists.txt:
file( GLOB_RECURSE my_sources *.cc )
message(STATUS "my_sources = ${my_sources}")
add_subdirectory( gui )
add_library( my_src ${my_SOURCES} )
target_link_libraries( my_src
my_gui
)
qt5_use_modules( my_src Core Gui Widgets)
(2) CMakeLists.txt:
file( GLOB my_gui_sources *.cc)
add_library( my_gui ${my_gui_sources} )
qt5_use_modules( my_gui Core Gui Widgets)
But I keep getting this output:
You have called ADD_LIBRARY for library my_src without any source files. This typically indicates a problem with your CMakeLists.txt file
-- my_sources = /home/bla/bla/src/gui/BorderLayout.cc;...;/home/bla/bla/my/src/gui/MainWindow.cc
-- my_gui_sources = /home/bla/bla/my/src/gui/BorderLayout.cc;...;/home/bla/bla/my/src/gui/MainWindow.cc
-- Configuring done
-- Generating done
-- Build files have been written to: /home/bla/bla/my/build
I know that I currently don't need the add_library in the first CMakeLists.txt, but later I will. I changed the first GLOB to GLOB_RECURSE, so that it finds at least anything.
For some reason your
file( GLOB my_gui_sources *.cc *.h)
Is not finding any file. To debug, you can print:
message(STATUS "my_gui_sources = ${my_gui_sources}")
Probably you want to use GLOB_RECURSE, which search in sub-directories:
file( GLOB_RECURSE my_gui_sources *.cc *.h)
Note that you don't need to add headers files to the source list.
Take care that you will have to rerun cmake every time you add a file to your project (cmake won't be called automatically, thing that instead happens if you touch one of the cmake files).
Link to documentation of command "file"
Edit:
The actual problem is that in your first CMakeLists.txt file you are using inconsistent naming for your variable (note that casing is important), therefore you have to change your add_library command to:
add_library( my_src ${my_sources} )
Note (off the records :-) ): the fact that casing is important for variable names might be confusing because, on the other hand, in cmake command names are case insensitive. It's also sometimes weird to notice that the character - (minus) might be used as part of the variable name: using _ (underscore) is most of the time preferable.

Add dependencies to a custom target

I need a way to add additional dependencies to a custom target. I have a macro which adds resource files to a particular project, used like this:
ADD_RESOURCES( ${TARGET} some/path pattern1 pattern2 )
ADD_RESOURCES( ${TARGET} another/path pattern1 )
I create a target called ${TARGET}_ASSETS and would like to attach the generation of all these resources to the one target. add_dependencies however only accepts other targets. So if I produce a file via a add_custom_command I cannot use that as a dependency.
The workaround might be to just create a new custom taget for each call to ADD_RESOURCES and then attached that to the ASSETS target. Each target requires a unique name however, and these is no way to generate this unique name from the parameters of ADD_RESOURCES.
One work-around is to postpone the generation of the ${target}_ASSETS custom targets until all dependencies have been set up with calls to ADD_RESOURCES.
Instead of immediately adding the dependencies to the custom target, the macro ADD_RESOURCES has to record the dependencies in a global variable, whose name depends on the target:
macro (ADD_RESOURCES _targetName)
set (_dependencies ${ARGN})
...
# record depencies in a target dependency variable
if (DEFINED ${_targetName}_Dependencies)
list (APPEND ${_targetName}_Dependencies ${_dependencies})
else()
set (${_targetName}_Dependencies ${_dependencies})
endif()
endmacro()
Then add another helper macro which determines all defined target dependency variables through reflection and sets up a custom target for each target:
macro (SETUP_ASSETS_TARGETS)
get_cmake_property(_vars VARIABLES)
foreach (_var ${_vars})
if (_var MATCHES "(.+)_Dependencies")
set (_targetName ${CMAKE_MATCH_1})
set (_targetDependencies ${${_var}})
message("${_targetName} depends on ${_targetDependencies}")
add_custom_target(${_targetName}_ASSETS DEPENDS ${_targetDependencies})
endif()
endforeach()
endmacro()
In your CMakeLists.txt add all necessary dependencies with calls to ADD_RESOURCES, then call the SETUP_ASSETS_TARGETS macro to have all custom targets defined.
ADD_RESOURCES( target1 some/path pattern1 pattern2 )
ADD_RESOURCES( target1 another/path pattern1 )
ADD_RESOURCES( target2 foo/bar pattern1 )
...
...
SETUP_ASSETS_TARGETS()
I know this is a late answer, but i post my solution for everyone who searches for this problem:
function(target_resources THIS)
if (NOT TARGET ${THIS}_res)
# this is just a pseudo command which can be appended later
add_custom_command(OUTPUT ${THIS}_dep COMMAND cd ${CMAKE_CURRENT_SOURCE_DIR})
# add a dependency with a target, as a command itself can not be a dependency
add_custom_target(${THIS}_res DEPENDS ${THIS}_dep)
add_dependencies(${THIS} ${THIS}_res)
endif ()
get_target_property(RUNTIME_OUTPUT_DIRECTORY ${THIS} RUNTIME_OUTPUT_DIRECTORY)
foreach (RES_FILE IN LISTS ARGN)
if (IS_ABSOLUTE ${RES_FILE})
file(RELATIVE_PATH PATH ${CMAKE_CURRENT_SOURCE_DIR} ${RES_FILE})
endif ()
# append the resource command with our resource
add_custom_command(OUTPUT ${THIS}_dep
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/${RES_FILE}
${RUNTIME_OUTPUT_DIRECTORY}/${RES_FILE}
APPEND)
endforeach ()
endfunction()
The benefit of this solution is that is does not rely on global variables nor the need to invoke a setup macro.