I want to execute a CMake command in a subdirectory with execute_process, and also pass some cache variables as -D options.
If the variable is of type string, it works. However, if the variable is a list, the typical method of passing a list in command line does not seem to work.
I tried all of the combinations listed in that answer. I even tried to join mylist with "\\;" or "\\\\;". However, the execute_process seems to always unpack the '-DVal2=a\\;b\\;c\\;' or '-DVal2=a;b;c' to -Dval2=a b c.
How can I prevent this? Only -DVal2=a\\;b\\;c works, but it's very annoying.
set(
mylist
a
b
c
)
set(
cmake_args
"-DVal1=abc"
"'-DVal2=${mylist}'" #does not work, the execute_process will unpack it into seperated args
)
execute_process(
COMMAND ${CMAKE_COMMAND} ${cmake_args} ${CMAKE_SOURCE_DIR}/subproject
OUTPUT_FILE ${CMAKE_BINARY_DIR}/config.log
ERROR_FILE ${CMAKE_BINARY_DIR}/config.log
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/subproject
RESULT_VARIABLE config_result
)
Before passing the list, run this line on it:
string(REPLACE ";" "\\;" escaped_list "${my_list}")
and then pass escaped_list. On the other end, it will have
the exact same value as my_list.
For example,
set(my_list "a\;b" "c" "d")
string(REPLACE ";" "\\;" escaped_list "${my_list}")
execute_process(COMMAND ${CMAKE_COMMAND} -Dmy_list=${escaped_list} -P test.cmake)
(Tested with cmake 3.17).
This also works when assigning first to cmake_args and passing that.
For example,
test1.cmake
# Construction.
set(my_list "a\;b" "c" "d")
set(other_list "e" "f\;g" "h")
# For debugging purposes.
message("my_list = \"${my_list}\".")
foreach(arg ${my_list})
message("-> ${arg}")
endforeach()
message("other_list = \"${other_list}\".")
foreach(arg ${other_list})
message("-> ${arg}")
endforeach()
# Encoding.
string(REPLACE ";" "\\;" escaped_list "${my_list}")
message("escaped_list = \"${escaped_list}\".")
string(REPLACE ";" "\\;" other_escaped_list "${other_list}")
message("other_escaped_list = \"${other_escaped_list}\".")
set(cmake_args "-Dother_list=${other_escaped_list}" "-Dmy_list=${escaped_list}")
execute_process(
COMMAND
${CMAKE_COMMAND} ${cmake_args} -P test2.cmake
)
test2.cmake
# For debugging purpose.
message("my_list = \"${my_list}\".")
foreach(arg ${my_list})
message("-> ${arg}")
endforeach()
message("other_list = \"${other_list}\".")
foreach(arg ${other_list})
message("-> ${arg}")
endforeach()
Output of running cmake -P test1.cmake :
my_list = "a\;b;c;d".
-> a;b
-> c
-> d
other_list = "e;f\;g;h".
-> e
-> f;g
-> h
escaped_list = "a\\;b\;c\;d".
other_escaped_list = "e\;f\\;g\;h".
my_list = "a\;b;c;d".
-> a;b
-> c
-> d
other_list = "e;f\;g;h".
-> e
-> f;g
-> h
Please observe closely where double quotes were and weren't used.
I think you need to escape the ; character which is the default separator for lists in CMake, but it's not clear how you do it so that it doesn't work for you.
So, try something like this
set(mylist_str "")
foreach(item ${mylist})
string(APPEND mylist_str ${item} "\;")
endforeach()
# this is for debugging
message(STATUS "List as string: ${mylist_str}")
set(cmake_args
"-DVal1=abc"
"-DVal2=${mylist_str}"
"-DVal3=\"${mylist_str}\"" # this has quotes around it
)
# this is for debugging
foreach(item ${cmake_args})
message(STATUS "A list item: ${item}")
endforeach()
Related
cmake_minimum_required(VERSION 3.9)
project(test)
set(MY_VAR "XXXXXX")
function(echo var)
message(STATUS "var = ${var}")
endfunction()
echo(MY_VAR) # output MY_VAR
why the output is MY_VAR, it should be XXXXXX, is cmake function parameter doesn't pass by value/reference but literal?
it seems that vairable in cmake function call is treated as string, it is a so wired design. when we need to read and modify a variable in function, we have to wirte in this ugly way?
set(MY_VAR "XXXXXX")
function(echo var)
message(STATUS "var = ${${var}}")
set(${var} "new value")
endfunction()
echo(MY_VAR)
(Update with working solution at bottom)
From what I can tell, generating a dependency graph with custom targets is supported as of CMake 3.17. However, when attempting to do so, I get no dependency mapping between my targets and I get this warning:
CMake Warning:
Manually-specified variables were not used by the project:
GRAPHVIZ_CUSTOM_TARGETS
I stumbled upon this SO answer, but it hasn't helped much. The next-closest thing I could find was an incomplete Merge Request to support some different use cases.
I'm running this on Windows, but I don't know if that could be the issue.
So, I'm stumped and could use some help before I file an issue against KitWare... since I think I am missing something really obvious. Any help would be so appreciated.
CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(
gviz
LANGUAGES NONE
)
add_custom_command(
OUTPUT foo.txt
COMMAND cmake --version > foo.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_command(
OUTPUT bar.txt
COMMAND cmake --version > bar.txt
DEPENDS foo.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(
foo
DEPENDS foo.txt
)
add_custom_target(
bar
DEPENDS bar.txt
)
Command ran from console, and output:
Console Output:
cmake_graphviz> cmake --graphviz=group.dot -BNBuild -GNinja . -DGRAPHVIZ_CUSTOM_TARGETS=TRUE
-- Configuring done
-- Generating done
Generate graphviz: cmake_graphviz/group.dot
CMake Warning:
Manually-specified variables were not used by the project:
GRAPHVIZ_CUSTOM_TARGETS
-- Build files have been written to: cmake_graphviz/NBuild
And finally, the group.dot file:
digraph "gviz" {
node [
fontsize = "12"
];
subgraph clusterLegend {
label = "Legend";
color = black;
edge [ style = invis ];
legendNode0 [ label = "Executable", shape = egg ];
legendNode1 [ label = "Static Library", shape = octagon ];
legendNode2 [ label = "Shared Library", shape = doubleoctagon ];
legendNode3 [ label = "Module Library", shape = tripleoctagon ];
legendNode4 [ label = "Interface Library", shape = pentagon ];
legendNode5 [ label = "Object Library", shape = hexagon ];
legendNode6 [ label = "Unknown Library", shape = septagon ];
legendNode7 [ label = "Custom Target", shape = box ];
legendNode0 -> legendNode1 [ style = solid ];
legendNode0 -> legendNode2 [ style = solid ];
legendNode0 -> legendNode3;
legendNode1 -> legendNode4 [ label = "Interface", style = dashed ];
legendNode2 -> legendNode5 [ label = "Private", style = dotted ];
legendNode3 -> legendNode6 [ style = solid ];
legendNode0 -> legendNode7;
}
}
Working solution:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.18)
project(
gviz
LANGUAGES NONE
)
# Because I don't like cmake files flooding my root directory
file(COPY cmake/CMakeGraphVizOptions.cmake DESTINATION ${CMAKE_BINARY_DIR})
add_custom_command(
OUTPUT foo.txt
COMMAND cmake --version > foo.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_command(
OUTPUT bar.txt
COMMAND cmake --version > bar.txt
DEPENDS foo.txt
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
add_custom_target(
foo
DEPENDS foo.txt
)
add_custom_target(bar)
add_dependencies(bar foo)
cmake/CMakeGraphVizOptions.cmake
# This file sets some options to control GraphViz graphs.
set(GRAPHVIZ_GRAPH_NAME "MyGraph")
set(GRAPHVIZ_CUSTOM_TARGETS TRUE)
set(GRAPHVIZ_NODE_PREFIX "blah")
File List output:
group.dot
group.dot.bar
group.dot.foo
group.dot.bar.dependers
group.dot.bar.dependers
Now, the only issue I have is it doesn't seem to link dependencies via file outputs, but that is outside of the scope of this question :)
In the linked documentation, it states:
The look and content of the generated graphs can be controlled using the file CMakeGraphVizOptions.cmake. This file is first searched in CMAKE_BINARY_DIR, and then in CMAKE_SOURCE_DIR. If found, the variables set in it are used to adjust options for the generated Graphviz files.
So, instead of setting GRAPHVIZ_CUSTOM_TARGETS in the cmake command line, set it in a file called CMakeGraphVizOptions.cmake, and add this file to your top-level source directory.
An example CMakeGraphVizOptions.cmake:
# This file sets some options to control GraphViz graphs.
set(GRAPHVIZ_GRAPH_NAME "MyGraph")
set(GRAPHVIZ_CUSTOM_TARGETS TRUE)
I use CMake 3.10. Previously I've used 3.5.
According manual i can use $ to get some path to output file. In fact anything isn't printed.
Status is "-- lib_location == $" I looked into examples of
But if I use next construction, it works well.
add_custom_target(
testTartgetFile ALL
COMMAND ${CMAKE_COMMAND} -E echo "$<TARGET_FILE:tgt1>"
VERBATIM
)
The question is how to get target object? I need it for further handling, not for print out.
My code:
cmake_minimum_required(VERSION 3.0)
project(libtest_project)
function(add_txbundle)
set(options NONE)
set(oneValueArgs TARGET)
set(multiValueArgs EXTRA_MAPPINGS DEPENDENCIES)
set(txPrefix "TxBundle")
cmake_parse_arguments(${txPrefix} "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
message(status " TARGET ${${txPrefix}_TARGET}")
message(status " EXTRA_MAPPINS ${TxBundle_EXTRA_MAPPINGS}")
set(TxBundleTarget "${${txPrefix}_TARGET}.txbundle")
set(TxParentTarget "${${txPrefix}_TARGET}")
message(status " TX TARGET ${TxBundleTarget}")
#..... some actions ...
endfunction(add_txbundle)
add_library(testlb SHARED testlib.cpp)
message (STATUS "lib_location == $<TARGET_FILE:testlb>")
add_txbundle(TARGET testlb EXTRA_MAPPINGS "1:1")
There is CMake script:
unset(FOO)
list(APPEND FOO "")
list(APPEND FOO "")
list(APPEND FOO "")
list(APPEND FOO "")
list(LENGTH FOO SIZE)
message(FATAL_ERROR ${SIZE})
Expectation - 4. Reality - 0
I've found a workaround, but it looks like a kludge:
set(FOO "dummy")
set(FOO "${FOO};")
set(FOO "${FOO};")
set(FOO "${FOO};")
set(FOO "${FOO};")
list(REMOVE_AT FOO 0)
message(FATAL_ERROR ${SIZE})
The example a little bit simpler than my task, so, please, don't suggest writing set(FOO ";;;"):) I get elements from outside, and some of them may be empty. Something like this:
function(do_smth LIST_WITH_EMPTY_ITEMS)
unset(RESULT)
foreach(X IN LISTS LIST_WITH_EMPTY_ITEMS)
if(${CONDITION})
list(APPEND RESULT "${X}")
endif()
endforeach()
endfunction()
It doesn't seem to work with empty lists, but if you initialize the list with some dummy value, as you did in your second example set(FOO "dummy"), you can continue to use FOO as a list and also append empty elements to it. It's basically your desired approach and workaround combined:
set(FOO "dummy")
list(APPEND FOO "")
list(APPEND FOO "")
list(APPEND FOO "")
list(APPEND FOO "")
list(POP_FRONT FOO)
list(LENGTH FOO SIZE)
message("content: (${FOO})")
message("sizt: ${SIZE}")
The output of this is:
content: (;;;)
size: 4
With CMake, how can I get a list of all the source files which go into an executable target, including all sources in all targets this executable depends on?
We have a pattern in the code base where initializer callers are generated by the build system based on file names and paths in the source tree. So I need the full path (or relative to source root) to all source files an executable target depends on.
Here is my piece of code to get one target's link dependencies:
function(target_link_libraries _target)
set(_mode "PUBLIC")
foreach(_arg IN LISTS ARGN)
if (_arg MATCHES "INTERFACE|PUBLIC|PRIVATE|LINK_PRIVATE|LINK_PUBLIC|LINK_INTERFACE_LIBRARIES")
set(_mode "${_arg}")
else()
if (NOT _arg MATCHES "debug|optimized|general")
set_property(GLOBAL APPEND PROPERTY GlobalTargetDepends${_target} ${_arg})
endif()
endif()
endforeach()
_target_link_libraries(${_target} ${ARGN})
endfunction()
function(get_link_dependencies _target _listvar)
set(_worklist ${${_listvar}})
if (TARGET ${_target})
list(APPEND _worklist ${_target})
get_property(_dependencies GLOBAL PROPERTY GlobalTargetDepends${_target})
foreach(_dependency IN LISTS _dependencies)
if (NOT _dependency IN_LIST _worklist)
get_link_dependencies(${_dependency} _worklist)
endif()
endforeach()
set(${_listvar} "${_worklist}" PARENT_SCOPE)
endif()
endfunction()
For older CMake versions (prior to 3.4), you will need to replace the IN_LIST check with a list(FIND ...) call:
[...]
list(FIND _worklist ${_dependency} _idx)
if (${_idx} EQUAL -1)
get_link_dependencies(${_dependency} _worklist)
endif()
[...]
And here is the test code I've used:
cmake_minimum_required(VERSION 3.4)
project(GetSources)
cmake_policy(SET CMP0057 NEW)
[... include functions posted above ...]
file(WRITE a.cc "")
add_library(A STATIC a.cc)
file(WRITE b.cc "")
add_library(B STATIC b.cc)
file(WRITE main.cc "int main() { return 0; }")
add_executable(${PROJECT_NAME} main.cc)
target_link_libraries(B A)
target_link_libraries(${PROJECT_NAME} B)
get_link_dependencies(${PROJECT_NAME} _deps)
foreach(_dep IN LISTS _deps)
get_target_property(_srcs ${_dep} SOURCES)
get_target_property(_src_dir ${_dep} SOURCE_DIR)
foreach(_src IN LISTS _srcs)
message("${_src_dir}/${_src}")
endforeach()
endforeach()
References
Recursive list of LINK_LIBRARIES in CMake