Search code examples
bashshellcmakemingw-w64msys2

How do I properly invoke MSYS2 Bash commands using CMake execute_process()?


Problem Description

I am having trouble setting up a CMake external_process() command that executes a MSYS2 bash command. When I am in the MSYS2 shell if I run the command $ bash -v ./bootstrap.sh the command works correctly. But if I run the CMake script in the MSYS2 shell using $ cmake -P Run_bash_command.cmake the command errors out part way through the process. An important piece of information I found in the CMake Documentation makes me think that I am not invoking bash correctly or missing an environmental variable:

CMake executes the child process using operating system APIs directly. All arguments are passed VERBATIM to the child process. No intermediate shell is used, so shell operators such as > are treated as normal arguments.

I would like to be able to do this command using CMake if possible as this problem is part of a much larger CMake superbuild project. If there is another approach to solving the problem I am open to suggestions as long as I can include it into the automation of the superbuild project. Any help will be appreciated.

Run_bash_command.cmake contents:

SET( ENV{MSYSTEM} MINGW64 )
SET( DIR_CONTAINING_BOOTSTRAP_SH C:/bash_test )
SET( BASH_COMMAND_TO_RUN bash -v ./bootstrap.sh )

EXECUTE_PROCESS( COMMAND ${BASH_COMMAND_TO_RUN}
          WORKING_DIRECTORY ${DIR_CONTAINING_BOOTSTRAP_SH} RESULT_VARIABLE command_result )

IF( NOT "${command_result}" STREQUAL "0" )
    MESSAGE( FATAL_ERROR "Error: command_result='${command_result}'" )
ENDIF()

Environment Setup

  • I followed the directions to setup MSYS2 64bit and added the mingw-w64 toolchain as well as cmake using the command pacman -S base-devel git mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain
  • To run the commands I use the MinGW-w64 Win64 Shell that is installed with MSYS2
  • The file bootstrap.sh comes from the libusb github repository and the folder c:/bash_test contains a clone of the master branch

Output

$ bash -v ./bootstrap.sh output:

$ bash -v ./bootstrap.sh
#!/bin/sh

if ! test -d m4 ; then
    mkdir m4
fi
autoreconf -ivf || exit 1
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
autoreconf: configure.ac: tracing
autoreconf: running: libtoolize --copy --force
libtoolize: putting auxiliary files in '.'.
libtoolize: copying file './ltmain.sh''
...<clipped output due to length>...
configure.ac:29: installing './install-sh'
configure.ac:29: installing './missing'
examples/Makefile.am: installing './depcomp'
autoreconf: Leaving directory `.'

$ cmake -P Run_bash_command.cmake output:

$ cmake -P Run_bash_command.cmake
#!/bin/sh

if ! test -d m4 ; then
    mkdir m4
fi
autoreconf -ivf || exit 1
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
aclocal-1.15: error: aclocal: file '/msys64/usr/share/aclocal/xsize.m4' does not exist
autoreconf: aclocal failed with exit status: 1
CMake Error at Run_bash_command.cmake:10 (MESSAGE):
  Error: command_result='1'

Things I have tried:

  • Substituting bash -l -c but this causes the shell to default to the home directory and then it is unable to find the file bootstrap.sh
  • Verified the correct version of bash is found by inspecting my environmental PATH varaiable
  • Verified MSYS2 and its packages are up to date
  • Using sh instead of bash
  • Calling autoreconf -ivf directly, but the same issue occurs
  • Using Unix style paths instead of Windows style

Solution

  • I was able to fix the issue using the code below.

    Run_bash_command.cmake contents:

    SET( DIR_CONTAINING_BOOTSTRAP_SH /C/bash_test )
    SET( BASH_COMMAND_TO_RUN bash -l -c "cd ${DIR_CONTAINING_BOOTSTRAP_SH} && sh ./bootstrap.sh" )
    
    EXECUTE_PROCESS( COMMAND ${BASH_COMMAND_TO_RUN}
                    WORKING_DIRECTORY ${DIR_CONTAINING_BOOTSTRAP_SH} RESULT_VARIABLE command_result )
    
    IF( NOT "${command_result}" STREQUAL "0" )
        MESSAGE( FATAL_ERROR "Error: command_result='${command_result}'" )
    ENDIF()
    

    Important Notes:

    • Using bash -l causes the shell to default to the home directory, to get around this I added a change directory cd <path> command to get us back to the directory where we want to issue the bash command.
    • Using -c causes bash to read a command from a string. Since we want to issue two commands, one for change directory and one for running the shell script, we need to use && to chain the commands together as well as use "quotation marks" to make sure the entire command is read properly as a string.