add_custom_command quotes arguments incorrectly
CMake version: 3.11.1
Platform: GNU/Linux
Generators: Unix Makefiles, Ninja
There are two issues involving quoting of arguments to the command in an add_custom_command
statement, at least when targetting make
and ninja
build tools.
1. Verbatim inappropriately quotes arguments, adds backslashes.
Quoting the documentation for VERBATIM
:
All arguments to the commands will be escaped properly for the build tool so that the invoked command receives each argument unchanged.
For make
(excluding special handling of a terminal backslash) and ninja
, this means that each dollar sign should be escaped with a second dollar sign.
In practice, CMake will:
- escape a dollar sign with another dollar sign, correctly, and
- add additional double quotes around each item in the argument list that contains a space, dollar sign, semicolon, or some other shell-specific characters, and
- backslash-escape each pair of escaped dollar signs.
Example:
The CMakeLists.txt
file
project(example)
cmake_minimum_required(VERSION 3.11)
add_custom_command(OUTPUT command VERBATIM COMMAND test FOO = "$FOO")
add_custom_target(target DEPENDS command)
generates a make target:
% sed -n '/^command:/,+3p' CMakeFiles/target.dir/build.make
command:
@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --blue --bold --progress-dir=/home/sam/cm3/b/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Generating command"
test FOO = "\$$FOO"
The test, which should check if the shell variable FOO
has the value FOO
fails:
% env FOO=FOO make target
Scanning dependencies of target target
[100%] Generating command
make[3]: *** [CMakeFiles/target.dir/build.make:61: command] Error 1
make[2]: *** [CMakeFiles/Makefile2:68: CMakeFiles/target.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/target.dir/rule] Error 2
make: *** [Makefile:118: target] Error 2
Manually removing the incorrect quoting and backslash produces the expected result.
% sed -i '/test FOO/s/=.*/= $$FOO/' CMakeFiles/target.dir/build.make
% sed -n '/^command:/,+3p' CMakeFiles/target.dir/build.make
command:
@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --blue --bold --progress-dir=/home/sam/cm3/b/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Generating command"
test FOO = $$FOO
% env FOO=FOO make target
[100%] Generating command
[100%] Built target target
This causes problems for compound shell commands, e.g.
project(example)
cmake_minimum_required(VERSION 3.11)
add_custom_command(OUTPUT command VERBATIM COMMAND "echo a; echo b")
add_custom_target(target DEPENDS command)
will fail at build time:
% make target
Scanning dependencies of target target
[100%] Generating command
/bin/sh: echo a; echo b: command not found
make[3]: *** [CMakeFiles/target.dir/build.make:61: command] Error 127
make[2]: *** [CMakeFiles/Makefile2:68: CMakeFiles/target.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/target.dir/rule] Error 2
make: *** [Makefile:118: target] Error 2
Other variants, e.g. COMMAND echo a; echo b
, etc. will also fail. It seems impossible to incorporate a literal semicolon or dollar sign without also generating double quotes and possibly a backslash.
2. Without verbatim, spaces in arguments are improperly escaped.
Leaving the Makefile-escaping to the user, there is improper handling of spaces in arguments: they will always garner a backslash in the generated rule.
Example:
project(example)
cmake_minimum_required(VERSION 3.11)
set(a_b "a b")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/a_b" "$a_b")
add_custom_command(OUTPUT command COMMAND test "'${a_b}'" = "\"`cat a_b`\"")
add_custom_target(target DEPENDS command)
generates:
% sed -n '/^command:/,+3p' CMakeFiles/target.dir/build.make
command:
@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --blue --bold --progress-dir=/home/sam/cm3/b/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Generating command"
test 'a\ b' = "`cat\ a_b`"
which will naturally fail:
% make target
Scanning dependencies of target target
[100%] Generating command
/bin/sh: cat a_b: command not found
make[3]: *** [CMakeFiles/target.dir/build.make:61: command] Error 1
make[2]: *** [CMakeFiles/Makefile2:68: CMakeFiles/target.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/target.dir/rule] Error 2
make: *** [Makefile:118: target] Error 2
The backtick expression can be resolved with the following unnatural syntax, relying on the space-qoting: e.g.
add_custom_command(OUTPUT command COMMAND test "${a_b}" = "\"`cat" "a_b`\"")
gives
test a\ b = "`cat a_b`"
in the Makefile.
This escaping though is inconsistent: it doesn't escape non-space special characters, e.g.
project(example)
cmake_minimum_required(VERSION 3.11)
set(a_b "a()b")
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/a_b" "$a_b")
add_custom_command(OUTPUT command COMMAND test "${a_b}" = "\"`cat a_b`\"")
add_custom_target(target DEPENDS command)
will fail:
% make target
[100%] Generating command
/bin/sh: -c: line 0: syntax error near unexpected token `('
/bin/sh: -c: line 0: `test a()b = "`cat a_b`"'
make[3]: *** [CMakeFiles/target.dir/build.make:61: command] Error 1
make[2]: *** [CMakeFiles/Makefile2:68: CMakeFiles/target.dir/all] Error 2
make[1]: *** [CMakeFiles/Makefile2:75: CMakeFiles/target.dir/rule] Error 2
make: *** [Makefile:118: target] Error 2
Expected behaviour
There should not be any automatic escape of shell-specific characters; if there were some it should be a consistent across all arguments, but this would eliminate the possibility of using important features such as output redirection with add_custom_command
. A separate facility for escaping a string for a shell would have utility. The current procedure is both undocumented and inconsistent.
The VERBATIM
option should do only what the documentation claims, that is, perform the appropriate escaping for the build tool and no more.