Problem with shell escaping when building from Xcode generated project

I had a problem using a cmake ExternalProject build command when using the Xcode generator and then building from inside Xcode. If one of the shell variables being used contained a special character that required escaping i.e. ~ then the generated Xcode build command produced a shell script that would not run.

I have tried to reproduce the scenario down to a small reproduction project.

The command line used to generate the Xcode project is
cmake -H. -Bbuild-xcode -G Xcode

The CMakeLists.txt file (there is also a dummy “test.cpp” file to allow project generation to proceed).

cmake_minimum_required(VERSION 2.8.12)

project(Test)

add_executable(Test test.cpp)

# This builds from inside Xcode
set(TEST "/Users/jake/test")
# This fails to build from inside Xcode
set(TEST "~/test")

add_custom_command(TARGET Test
    POST_BUILD
    COMMAND JAKE=${TEST} echo "JAKE" > /dev/null 2>&1
    )

The generated script in the Xcode project for the non-working scenario is:

set -e
if test "$CONFIGURATION" = "Debug"; then :
  cd /Users/jake/Workspace/personal/cmakeBug/build-xcode
  "JAKE=~/test" echo JAKE > /dev/null 2>&1
fi

and for the working scenario:

set -e
if test "$CONFIGURATION" = "Debug"; then :
  cd /Users/jake/Workspace/personal/cmakeBug/build-xcode
  JAKE=/Users/jake/test echo JAKE > /dev/null 2>&1
fi

You are using shell features in your command line (expansion of ~ and shell redirection), but CMake doesn’t guarantee any particular shell behavior (or even that a shell is used). If you want to use shell features, you should make your command run a shell. For example:

add_custom_command(TARGET Test
    POST_BUILD
    COMMAND bash -c "JAKE=${TEST} echo JAKE > /dev/null 2>&1"
    )

Thank you that is very helpful and explains the escaping differences.

Another option I found was this

add_custom_command(TARGET Test
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E env JAKE=${TEST} echo "JAKE" > /dev/null 2>&1
    )

For reference, the full example relates to capturing/setting the PATH environment variable when building an ExternalProject from Xcode on Apple Silicon.
In that scenario, Xcode does not include the brew default install location in the Xcode PATH environment variable (/opt/homebrew/bin).
On Apple Intel the default brew install is included in the Xcode PATH environment (/usr/local/bin)