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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With