Issue
I want to replace a string a = statement;
with // something else
in my source code file before compiling. This corresponds to a preprocessor #define
, but the string to be replaced is not a simple identifier. So I want to have CMake do this before compilation.
My file structure is:
main.cpp
CMakeLists.txt
build/
Immutable content of main.cpp
:
#include <iostream>
int main() {
std::cout << "foo\n";
}
Current content of CMakeLists.txt
:
cmake_minimum_required(VERSION 3.0)
project(main)
file(READ main.cpp TEXT)
string(REPLACE "foo" "bar" TEXT "${TEXT}")
file(WRITE main.cpp "${TEXT}")
add_executable(main main.cpp)
In build/
I call cmake
to configure and build my project:
cmake ..
cmake --build .
Currently during configuration in the main.cpp
file the string foo
is replaced by bar
. Then the changed file is compiled to an executable file.
I want CMake to read main.cpp
during build, replace the string, write the file to CMAKE_BINARY_DIR
and then compile this new file to an executable with add_executable
. Is this possible?
No external tools should be used, as I have no control over the systems on which this project is compiled. configure_file
does not seem to work, as arbitrary strings cannot be substituted here.
Solution
Yes, this is possible by moving the string replace into a separate CMake script which is then called via add_custom_command
during build.
If you want a single file solution, you can have this separate file written during configuration. The name of the file is replace.cmake
in the following example. To simplify the definition of the file content and not have to do a lot of escaping to prevent immediate variable expansion, bracket arguments are very useful.
cmake_minimum_required(VERSION 3.12)
project(main)
file(WRITE "${CMAKE_BINARY_DIR}/replace.cmake"
[=[
file(READ "${SOURCE}" TEXT)
string(REPLACE "foo" "bar" TEXT "${TEXT}")
file(WRITE "${TARGET}" "${TEXT}")
]=])
add_custom_command(
OUTPUT "${CMAKE_BINARY_DIR}/main.cpp"
COMMAND "${CMAKE_COMMAND}"
"-DSOURCE=${CMAKE_SOURCE_DIR}/main.cpp"
"-DTARGET=${CMAKE_BINARY_DIR}/main.cpp"
-P "${CMAKE_BINARY_DIR}/replace.cmake"
DEPENDS "${CMAKE_SOURCE_DIR}/main.cpp" "${CMAKE_BINARY_DIR}/replace.cmake"
)
add_executable(main "${CMAKE_BINARY_DIR}/main.cpp")
The add_custom_command
specifies that this file is processed if main.cpp
has changed since the last build, or more precisely, if the dependent file has a newer timestamp than the output file.
If you opt to have the script file that does the replacement as its own file instead of generating it, you should also list it as a dependency of the custom command so that it re-runs if you change the script.
You could use configure_file
instead of string(REPLACE)
, but then you'd have to follow the template / variable referencing syntax defined for that command, and make sure that it doesn't go expanding anything else that you don't intend to be expanded.
The target definition with add_executable
must refer to the newly generated version of main.cpp
.
Answered By - starball Answer Checked By - Candace Johnson (WPSolving Volunteer)