cmake_parse_arguments storing empty strings - cmake

I'm trying to use the function signature variation of cmake_parse_arguments(), mirroring the example for macros:
include(CMakeParseArguments)
set(prefix PREFIX_)
set(${prefix}VAR "foo")
message(DEBUG " value of " "${prefix}VAR" " is " ${${prefix}VAR})
function(func)
set(prefix ARG_)
set(options OPTION)
set(oneValueArgs VALUE)
set(multiValueArgs MULTIVALUE)
cmake_parse_arguments(PARSE_ARGV 0 "${prefix}" "${options}" "${oneValueArgs}" "${multiValueArgs}")
message(DEBUG ${${prefix}UNPARSED_ARGUMENTS})
message(DEBUG ${${prefix}OPTION})
message(DEBUG ${${prefix}VALUE})
message(DEBUG ${${prefix}MULTIVALUE})
endfunction(func)
func(VALUE 42 MULTIVALUE "foo" "bar" "baz")
The message output is:
DEBUG value of PREFIX_VAR is foo
DEBUG
DEBUG
DEBUG
DEBUG
The output is only empty strings. What's going on?

From the documentation for cmake_parse_arguments:
cmake_parse_arguments will consider for each of the keywords listed in <options>, <one_value_keywords> and <multi_value_keywords> a variable composed of the given <prefix> followed by "_" and the name of the respective keyword.
For example, one of the variables populated after running this method is <prefix>_UNPARSED_ARGUMENTS. Because your prefix already contains an underscore, it gets expanded to ARG__UNPARSED_ARGUMENTS (with two underscores after ARG). Thus, adding the extra underscore to your message() calls yields the parsed values:
include(CMakeParseArguments)
set(prefix PREFIX_)
set(${prefix}VAR "foo")
message(DEBUG " value of " "${prefix}VAR" " is " ${${prefix}VAR})
function(func)
set(prefix ARG_)
set(options OPTION)
set(oneValueArgs VALUE)
set(multiValueArgs MULTIVALUE)
cmake_parse_arguments(PARSE_ARGV 0 "${prefix}" "${options}" "${oneValueArgs}" "${multiValueArgs}")
# Add additional underscore to the variables expanded here!
message(DEBUG ${${prefix}_UNPARSED_ARGUMENTS})
message(DEBUG ${${prefix}_OPTION})
message(DEBUG ${${prefix}_VALUE})
message(DEBUG ${${prefix}_MULTIVALUE})
endfunction(func)
func(VALUE 42 MULTIVALUE "foo" "bar" "baz")
This prints the following:
DEBUG value of PREFIX_VAR is foo
DEBUG
DEBUG FALSE
DEBUG 42
DEBUG foobarbaz

Related

why value of cmake function parameter is literal?

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)

CMake. Cann't get target output file name

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

How to add empty element to CMake list?

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

How to pass list variable in CMake's execute_process?

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

Change file paths

I want to change [%a/b] to [%a/c].
Basically, the same as Change path or refinement, but with file! instead:
I want to change the a/b inside a block to a/c
test: [a/b]
In this case, either change next test/1 'c or test/1/2: 'c works.
But not when test is a file!:
>> test: [%a/b]
== [%a/b]
>> test/1
== %a/b
>> test/1/2 ; can't access 2nd value
== %a/b/2
>> next first test ; not quite what you expect
== %/b
Trying to change it gives not something you'd expect:
>> change next test/1 'c
== %b
>> test
== [%acb]
You are confusing path! and file! series, they can look similar, but their nature are very different.
A path! is a collection of values (often word! values) separated by a slash symbol, a file! is a collection of char! values. Slash characters in file! series are just characters, so file! has no knowledge about any sub-structures. It has (mostly) the semantics of string! series, while path! has the semantics of a block! series.
Now that this is cleared, about the test/1/2 result, path notation on a file! series has a different behavior than on string!, it will do a smart concatenation instead of acting as an accessor. It's called smart because it will nicely handle extra slash characters present in left and right parts. For example:
>> file: %/index.html
== %/index.html
>> path: %www/
== %www/
>> path/file
== %www/file
>> path/:file
== %www/index.html
Same path notation rule applies to url! series too:
>> url: http://red-lang.org
== http://red-lang.org
>> url/index.html
== http://red-lang.org/index.html
>> file: %/index.html
== %/index.html
>> url/:file
== http://red-lang.org/index.html
So for changing the nested content of test: [%a/b], as file! behaves basically as string!, you can use any available method for strings to modify it. For example:
>> test: [%a/b]
== [%a/b]
>> change skip test/1 2 %c
== %""
>> test
== [%a/c]
>> change next find test/1 slash "d"
== %""
>> test
== [%a/d]
>> parse test/1 [thru slash change skip "e"]
== true
>> test
== [%a/e]
Files are string types and can be manipulated in the same way you'd modify a string. For example:
test: [%a/b]
replace test/1 %/b %/c
This is because file! is an any-string!, not any-array!
>> any-string? %a/c
== true
>> any-array? 'a/c
== true
So the directory separator '/' in a file! doesn't mean anything special with the action CHANGE. So 'a', '/', and 'b' in %a/b are treated the same way, and the interpreter isn't trying to parse it into a two segment file path [a b].
While for a path!, because it's an array, each component is a rebol value, and the interpreter knows that. For instance, 'bcd' in a/bcd will be seen as a whole (a word!), instead of three characters 'b', 'c' and 'd'.
I agree that the file! being an any-string! is not convenient.
Here is a maybe cumbersome solution, but suitable for directories treating them as files
test/1: to-file head change skip split-path test/1 1 %c