how do you escape an nmake command line inside cmake? - windows

I am trying to build zlib on windows as a dependency in my cmake project.
I have come up with the following which works fine:
file(WRITE ${CMAKE_BINARY_DIR}/zlib.nmake
AS=ml64\n
LOC="-DASMV -DASMINF -DNDEBUG -I."\n
OBJA="inffasx64.obj gvmat64.obj inffas8664.obj"\n
-f win32/Makefile.msc\n
zlib.lib\n)
ExternalProject_Add(
zlib
GIT_REPOSITORY https://github.com/kyotov/zlib.git
GIT_TAG mt
PREFIX ${CMAKE_BINARY_DIR}
CONFIGURE_COMMAND ""
BUILD_COMMAND ${CMAKE_COMMAND} -E chdir <SOURCE_DIR> nmake #${CMAKE_BINARY_DIR}/zlib.nmake
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/zlib.h <INSTALL_DIR>/install/include/zlib.h
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/zconf.h <INSTALL_DIR>/install/include/zconf.h
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/zlib.lib <INSTALL_DIR>/install/lib/zlib.lib
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/zlib.pdb <INSTALL_DIR>/install/lib/zlib.pdb
)
I would really prefer if I can inline the call to nmake and not have to resort to passing # parameter.
I spent about 15-30 minutes trying to escape the double quotes in various ways but could not get it done.
Google did not teach me anything either...
Anybody has any smart ideas?

Related

How to extract all file in a .tar.gz to specific folder with cmake add_custom_target?

To decrease library size, used cmake -E tar "zcvf" "lib.tar.gz" out.lib in cmake to automatically create .tar.gz then lib.tar.gz is moved to folder f, ie. f/lib.tar.gz.
However, to use this library in another project, it's needed to extract to specific folder. The command without cmake is clear: tar -xzf f/lib.tar.gz -C target-folder.
The problem is that, in cmake, how to combine cmake -E tar "xzf" f/lib.tar.gz with -C option? Commands such as cmake -E tar "xzf" f/lib.tar.gz -C f/ will result error.
Ideal script would be sth. like
add_custom_target(PrepareLib
COMMAND ${CMAKE_COMMAND} -E tar "xzf" command....
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ## note, this line must not be modified
)
Tools version: Visual Studio 2017, Windows 10 and CMake 3.17.1.

CMake add_custom_command replaces slash with backslash on windows

I'm trying to create a conan-package (hint: the names contain forward-slashes) as a postbuild operation with something like this:
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
WORKING_DIRECTORY ${PROJECT_DIR}
COMMAND conan export-pkg .. "gtest/1.10.0#shared/testing" -f -pr ${CONAN_PROFILE}
COMMENT "Uploading conan package..."
)
On Linux/Ubuntu/WSL this works fine.
conan export-pkg . 'gtest/1.10.0#shared/testing' -f -pr './platforms/platform_linux-x86-clang/conan_profile/linux-x86-clang'
On Windows the executed command looks like this:
conan export-pkg . 'gtest\1.10.0#shared\testing' -f -pr .\platforms\platform_win-x86-clang\conan_profile\win-x86-clang
How do I keep cmake from replacing / with \? Is there a setting for this?
You can use the file(TO_CMAKE_PATH "<path>" <variable>) command. It force the path to have a format with / separator.
In your case you have to put gtest/1.10.0#shared/testing in a variable with the command :
file(TO_CMAKE_PATH "gtest/1.10.0#shared/testing" GTEST_PATH)
file(TO_CMAKE_PATH ${CONAN_PROFILE} CONAN_PROFILE)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
WORKING_DIRECTORY ${PROJECT_DIR}
COMMAND conan export-pkg .. ${GTEST_PATH} -f -pr ${CONAN_PROFILE}
COMMENT "Uploading conan package..."
)

How can I concatenate several variables to a command and execute it? [duplicate]

This question already has answers here:
Reading quoted/escaped arguments correctly from a string
(4 answers)
Closed 3 years ago.
For building applications I use CMake.
I parse my CMake command in a script with several variables:
CMAKE_COMMAND=" \
$CMAKE \
-G "$Build_TOOL_CMAKE" \
[...]"
After parsing that command I print it with printf for control:
printf "\n${CMAKE_COMMAND}\n"
And then I eventually execute the command with:
$CMAKE_COMMAND
This script worked as long as I had "Ninja" as Generator (the -G option --> BUILD_TOOL_CMAKE="Ninja") as it does have no space characters in it.
Now I want to use "Eclipse CDT4 - Unix Makefiles" as Generator. I tried several ways of escaping the space characters, e.g.:
CMAKE_COMMAND=" \
$CMAKE \
-G \"$Build_TOOL_CMAKE\" \
[...]"
or
CMAKE_COMMAND=" \
$CMAKE \
-G '$Build_TOOL_CMAKE' \
[...]"
The printed command became correct in some cases, but the execution of it resulted in errors like:
CMake Error: Could not create named generator Eclipse
or
CMake Error: Could not create named generator "Eclipse
This leads to the assumption that something is wrong with escaping the spaces, but the printf works:
cmake.exe -G"Eclipse CDT4 - Unix Makefiles" -S sourcefolder -B buildfolder # printf $CMAKE_COMMAND
Did I miss something? Maybe the executing merely by the variable ($CMAKE_COMMAND) is also the wrong approach.
You can use the CMake's configure_file command: documentation
Place all the variables in command.in and write it to command.
configure_file(command.in, command)
The file command.in can looks like this:
${CMAKE_COMMAND} -G ${Build_TOOL_CMAKE}

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
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.

Use shell command as INSTALL_COMMAND to ExternalProject_Add

Is it possible to use any shell command for the INSTALL_COMMAND phase of cmake's ExternalProject_Add? e.g.
ExternalProject_Add(leveldb
GIT_REPOSITORY git#github.com:google/leveldb.git
GIT_TAG v1.18
CONFIGURE_COMMAND ./build_detect_platform build.settings .
BUILD_COMMAND make -j 8
BUILD_IN_SOURCE 1
INSTALL_COMMAND ""
)
# INSTALL_COMMAND "mkdir -p ${CMAKE_BINARY_DIR}/lib/ \
# && find . \( -name \"*${CMAKE_SHARED_LIBRARY_SUFFIX}\" -or -name \"*${CMAKE_STATIC_LIBRARY_SUFFIX}\" \
# -exec cp {} ${CMAKE_BINARY_DIR}/lib/\;\) \
# && cp -r ./include ${CMAKE_BINARY_DIR}")
I commented out the INSTALL_COMMAND I want to use, find and cp don't seem to be allowed, "No such file or directory, error 127" is the result of using this.
Direct Answer:
install(CODE "execute_process(...)")
The SCRIPT and CODE signature:
install([[SCRIPT <file>] [CODE <code>]] [...])
The SCRIPT form will invoke the given CMake script files during installation. If the script file name is a relative path it will be interpreted with respect to the current source directory. The CODE form will invoke the given CMake code during installation. Code is specified as a single argument inside a double-quoted string. For example, the code
install(CODE "MESSAGE(\"Sample install message.\")")
will print a message during installation.
Food for thought:
You might be in a hurry to get something done quickly. Do consider this if you have time or you can get back to it and fix it.
One of the main reasons why people love cmake is because of it's cross platform nature. Any project when properly coded with this aspect in mind will work with either on Linux or Windows or any other operating supported system. The various generators will work happily if the developer had this in mind. My suggestion is to convert the shell commands into cmake in a cross platform way, put them in a separate *.cmake file and execute them using cmake -E option.
Here is an extract from a working project that I had worked on in the past.
project_build_steps.cmake
message(VAR1=${VAR1}) # These variables can be passed from the invocation place
message(VAR2=${VAR2}) # You can use them in the build steps
if("${BUILD_STEP}" STREQUAL "patch")
message("BUILD_STEP: patch")
# Put your patch steps using cmake
endif()
if("${BUILD_STEP}" STREQUAL "configure")
message("BUILD_STEP: configure")
# Put your configure steps using cmake
endif()
if("${BUILD_STEP}" STREQUAL "build")
message("BUILD_STEP: build")
# Put your build steps using cmake
endif()
if("${BUILD_STEP}" STREQUAL "install")
message("BUILD_STEP: install")
# Put your install steps using cmake
endif()
CMakeLists.txt (Option 1)
set(CMAKE_COMMAND /usr/bin/cmake)
set(PROJECT_BUILD_STEPS_FILE project_build_steps.cmake)
ExternalProject_Add(
project_name
SOURCE_DIR /path/to/project/source
PATCH_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=patch -P ${PROJECT_BUILD_STEPS_FILE}
CONFIGURE_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=configure -P ${PROJECT_BUILD_STEPS_FILE}
BUILD_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=build -P ${PROJECT_BUILD_STEPS_FILE}
INSTALL_COMMAND ${CMAKE_COMMAND} -DBUILD_STEP=install -P ${PROJECT_BUILD_STEPS_FILE}
)
If you do not want to use ExternalProject_Add you can use something like following. This will also give you individual build targets like make project_patch, make project_configure, make project_build, make project_install.
CMakeLists.txt (Option 2)
set(CMAKE_COMMAND /usr/bin/cmake)
set(PROJECT_BUILD_STEPS_FILE project_build_steps.cmake)
set(STAMP_FILE_PROJECT_PATCH .project_patch_done)
add_custom_command(
OUTPUT ${STAMP_FILE_PROJECT_PATCH}
COMMAND ${CMAKE_COMMAND} -DVAR1=value1 -DSTEP=patch -P ${PROJECT_BUILD_STEPS_FILE}
COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_PATCH}
)
add_custom_target(project_patch DEPENDS ${STAMP_FILE_PROJECT_PATCH})
set(STAMP_FILE_PROJECT_CONFIGURE .project_configure_done)
add_custom_command(
OUTPUT ${STAMP_FILE_PROJECT_CONFIGURE}
COMMAND ${CMAKE_COMMAND} -DSTEP=configure -P ${PROJECT_BUILD_STEPS_FILE}
COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_CONFIGURE}
)
add_custom_target(project_configure DEPENDS project_patch ${STAMP_FILE_PROJECT_CONFIGURE})
set(STAMP_FILE_PROJECT_BUILD .project_build_done)
add_custom_command(
OUTPUT ${STAMP_FILE_PROJECT_BUILD}
COMMAND ${CMAKE_COMMAND} -DSTEP=build -P ${PROJECT_BUILD_STEPS_FILE}
COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_BUILD}
VERBATIM
)
add_custom_target(project_build DEPENDS project_configure ${STAMP_FILE_PROJECT_BUILD})
set(STAMP_FILE_PROJECT_INSTALL .project_install_done)
add_custom_command(
OUTPUT ${STAMP_FILE_PROJECT_INSTALL}
COMMAND ${CMAKE_COMMAND} -DSTEP=install -P ${PROJECT_INSTALL_STEPS_FILE}
COMMAND ${CMAKE_COMMAND} -E touch ${STAMP_FILE_PROJECT_INSTALL}
VERBATIM
)
add_custom_target(project_install DEPENDS project_build ${STAMP_FILE_PROJECT_INSTALL})

Resources