17

I'm trying to create a custom command that runs with some environment variables, such as LDFLAGS, whose value needs to be quoted if it contains spaces:

LDFLAGS="-Lmydir -Lmyotherdir"

I cannot find a way to include this argument in a CMake custom command, due to CMake's escaping rules. Here's what I've tried so far:

COMMAND LDFLAGS="-Ldir -Ldir2" echo blah VERBATIM)

yields "LDFLAGS=\"-Ldir -Ldir2\"" echo blah

COMMAND LDFLAGS=\"-Ldir -Ldir2\" echo blah VERBATIM)

yields LDFLAGS=\"-Ldir -Ldir2\" echo blah

It seems I either get the whole string quoted, or the escaped quotes don't resolve when used as part of the command.

I would appreciate either a way to include the literal double-quote or as an alternative a better way to set environment variables for a command. Please note that I'm still on CMake 2.8, so I don't have the new "env" command available in 3.2.

Note that this is not a duplicate of When to quote variables? as none of those quoting methods work for this particular case.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Devin Lane
  • 964
  • 1
  • 7
  • 14
  • Have you tried using the string with the escaped quotationmarks in a variable and passing that variable to custom_command? – usr1234567 Sep 07 '16 at 11:36
  • @usr1234567 Yep. I've done all manner of strings, lists, quoted strings, etc. All result in either the entire thing quoted, or the backslash making it all the way to the shell. – Devin Lane Sep 07 '16 at 11:38
  • 2
    Related: http://stackoverflow.com/questions/35029277/how-to-modify-environment-variables-passed-to-custom-cmake-target/35032051. – Tsyvarev Sep 07 '16 at 12:23

5 Answers5

10

You need three backslashes. I needed this recently to get a preprocessor define from PkgConfig and apply it to my C++ flags:

pkg_get_variable(SHADERDIR movit shaderdir)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSHADERDIR=\\\"${SHADERDIR}\\\"")
ulatekh
  • 1,311
  • 1
  • 14
  • 19
8

The obvious choice - often recommended when hitting the boundaries of COMMAND especially with older versions of CMake - is to use an external script.

I just wanted to add some simple COMMAND only variations that do work and won't need a shell, but are - I have to admit - still partly platform dependent.

  • One example would be to put only the quoted part into a variable:

    set(vars_as_string "-Ldir -Ldir2")
    add_custom_target(
        QuotedEnvVar
        COMMAND env LD_FLAGS=${vars_as_string} | grep LD_FLAGS
    )
    

    Which actually does escape the space and not the quotes.

  • Another example would be to add it with escaped quotes as a "launcher" rule:

    add_custom_target(
        LauncherEnvVar
        COMMAND env | grep LD_FLAGS
    )
    set_target_properties(
        LauncherEnvVar 
        PROPERTIES RULE_LAUNCH_CUSTOM "env LD_FLAGS=\"-Ldir -Ldir2\""
    )
    

Edit: Added examples for multiple quoted arguments without the need of escaping quotes

  • Another example would be to "hide some of the complexity" in a function and - if you want to add this to all your custom command calls - use the global/directory RULE_LAUNCH_CUSTOM property:

    function(set_env)
        get_property(_env GLOBAL PROPERTY RULE_LAUNCH_CUSTOM)
        if (NOT _env)
            set_property(GLOBAL PROPERTY RULE_LAUNCH_CUSTOM "env")
        endif()
        foreach(_arg IN LISTS ARGN)
            set_property(GLOBAL APPEND_STRING PROPERTY RULE_LAUNCH_CUSTOM " ${_arg}")
        endforeach()
    endfunction(set_env)
    
    set_env(LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")
    
    add_custom_target(
        MultipleEnvVar
        COMMAND env | grep -E 'LDFLAGS|CFLAGS'
    )
    

Alternative (for CMake >= 3.0)

  • I think what we actually are looking for here (besides the cmake -E env ...) is named Bracket Argument and does allow any character without the need of adding backslashes:

    set_property(
        GLOBAL PROPERTY 
            RULE_LAUNCH_CUSTOM [=[env LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb"]=]
    )
    add_custom_target(
        MultipleEnvVarNew
        COMMAND env | grep -E 'LDFLAGS|CFLAGS'
    )
    

References

Community
  • 1
  • 1
Florian
  • 39,996
  • 9
  • 133
  • 149
  • Thank you @Florian, I was looking for functionality exactly ike `RULE_LAUNCH_CUSTOM` but didn't know where to look. I've edited my answer to add one additional tweak needed if you have multiple such `ARG="value"` style arguments. I'd be happy to remove my answer entirely if want to add that tweak to yours as well. – Devin Lane Sep 09 '16 at 04:44
  • @DevinLane You're welcome. I've added some other examples, but please keep yours also. The possibilities - as usual when you do work with script languages - are endless here. I've used the "converting lists to space separated string" part also in the past and its totally valid. I'm just trying to avoid it because of the selfish reason that it needs so much explaining it to people who see it for the first time. – Florian Sep 11 '16 at 19:50
  • Was unaware of Bracket Arguments. Just what I was looking for. – E-rich Nov 30 '21 at 17:01
1

Florian's answer is wrong on several counts:

  • Putting the quoted part in a variable makes no difference.
  • You should definitely use VERBATIM. It fixes platform-specific quoting bugs.
  • You definitely shouldn't use RULE_LAUNCH_CUSTOM for this. It isn't intended for this and only works with some generators.
  • You shouldn't use env as the command. It isn't available on Windows.

It turns out the real reason OPs code doesn't work is that CMake always fully quotes the first word after COMMAND because it's supposed to be the name of an executable. You simply shouldn't put environment variables first.

For example:

add_custom_command(
    OUTPUT q1.txt
    COMMAND ENV_VAR="a b" echo "hello" > q1.txt
    VERBATIM
)

add_custom_target(q1 ALL DEPENDS q1.txt)
$ VERBOSE=1 make
...
"ENV_VAR=\"a b\"" echo hello > q1.txt
/bin/sh: ENV_VAR="a b": command not found

So how do you pass an environment variable with spaces? Simple.

add_custom_command(
    OUTPUT q1.txt
    COMMAND ${CMAKE_COMMAND} -E env ENV_VAR="a b" echo "hello" > q1.txt
    VERBATIM
)
Timmmm
  • 88,195
  • 71
  • 364
  • 509
0

Ok, I removed my original answer as the one proposed by @Florian is better. There is one additional tweak needed for multiple quoted args. Consider a list of environment variables as such:

set(my_env_vars LDFLAGS="-Ldir1 -Ldir2" CFLAGS="-Idira -Idirb")

In order to produce the desired expansion, convert to string and then replace ; with a space.

set(my_env_string "${my_env_vars}") #produces LDFLAGS="...";CFLAGS="..."
string(REPLACE ";" " " my_env_string "${my_env_string}")

Then you can proceed with @Florian's brilliant answer and add the custom launch rule. If you need semicolons in your string then you'll need to convert them to something else first.

Note that in this case I didn't need to launch with env:

set_target_properties(mytarget PROPERTIES RULE_LAUNCH_CUSTOM "${my_env_string}")

This of course depends on your shell.

On second thought, my original answer is below as I also have a case where I don't have access to the target name.

set(my_env LDFLAGS=\"-Ldir -Ldir2" CFLAGS=\"-Idira -Idirb\")
add_custom_command(COMMAND sh -c "${my_env} grep LDFLAGS" VERBATIM)

This technique still requires that the semicolons from the list->string conversion be replaced.

Devin Lane
  • 964
  • 1
  • 7
  • 14
  • @usr1234567 If I understand you correctly, you mean to do `set(flags LDFLAGS=\"-Ldir1 -Ldir2\")` and then `add_custom_command(COMMAND ${flags} grep LDFLAGS VERBATIM)`. For me this produces `LDFLAGS=\"-Ldir;-Ldir2\" grep LDFLAGS`, ie the escapes appear literally in the command. If you quote it, then you get `"LDFLAGS=\"-Ldir;-Ldir2\"" grep LDFLAGS` – Devin Lane Sep 09 '16 at 04:51
0

Some folks suggest to use ${CMAKE_COMMAND} and pass your executable as an argument, e.g:

COMMAND ${CMAKE_COMMAND} -E env "$(WindowsSdkDir)/bin/x64/makecert.exe" ...

That worked for me.

Artyom Chirkov
  • 233
  • 1
  • 10