├── .github └── workflows │ └── ci.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── PatchFile.cmake ├── include ├── other.h └── value.h ├── main.cpp └── value.h.patch /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | env: 4 | CMAKE_BUILD_PARALLEL_LEVEL: 4 5 | CTEST_PARALLEL_LEVEL: 0 6 | CTEST_NO_TESTS_ACTION: error 7 | 8 | on: 9 | push: 10 | paths: 11 | - "**.c" 12 | - "**.patch" 13 | - "**.cmake" 14 | - "**/CMakeLists.txt" 15 | - ".github/workflows/ci.yml" 16 | 17 | jobs: 18 | 19 | base: 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | os: [ubuntu-latest, macos-latest] 25 | 26 | runs-on: ${{ matrix.os }} 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - run: cmake -Bbuild 32 | - run: cmake --build build 33 | - run: ctest --test-dir build -V 34 | 35 | - name: verify touch does not fail 36 | run: | 37 | cmake -E touch main.cpp 38 | cmake --build build 39 | ctest --test-dir build -V 40 | 41 | - name: verify clean does not fail 42 | run: | 43 | cmake --build build --clean-first 44 | ctest --test-dir build -V 45 | 46 | msvc: 47 | runs-on: windows-latest 48 | 49 | steps: 50 | - uses: actions/checkout@v4 51 | 52 | - run: cmake -Bbuild 53 | - run: cmake --build build 54 | - run: ctest --test-dir build -C Debug -V 55 | 56 | 57 | - name: verify touch does not fail 58 | run: | 59 | cmake -E touch main.cpp 60 | cmake --build build 61 | ctest --test-dir build -C Debug -V 62 | 63 | - name: verify clean does not fail 64 | run: | 65 | cmake --build build --clean-first 66 | ctest --test-dir build -C Debug -V 67 | 68 | mingw: 69 | runs-on: windows-latest 70 | 71 | steps: 72 | - uses: actions/checkout@v4 73 | 74 | - run: cmake -Bbuild -G "MinGW Makefiles" 75 | - run: cmake --build build 76 | - run: ctest --test-dir build -V 77 | 78 | - name: verify touch does not fail 79 | run: | 80 | cmake -E touch main.cpp 81 | cmake --build build 82 | ctest --test-dir build -V 83 | 84 | - name: verify clean does not fail 85 | run: | 86 | cmake --build build --clean-first 87 | ctest --test-dir build -V 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # patches "value.h" under build directory with "value.h.patch" using Git apply 2 | 3 | cmake_minimum_required(VERSION 3.20) 4 | 5 | if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) 6 | message(FATAL_ERROR "In-source builds are not allowed. 7 | cmake -B build") 8 | endif() 9 | 10 | project(demo_patch LANGUAGES CXX) 11 | 12 | enable_testing() 13 | 14 | add_custom_command( 15 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/value.h 16 | COMMAND ${CMAKE_COMMAND} 17 | -Din:FILEPATH=${CMAKE_CURRENT_SOURCE_DIR}/include/value.h 18 | -Dout:FILEPATH=${CMAKE_CURRENT_BINARY_DIR}/include/value.h 19 | -Dpatch:FILEPATH=${CMAKE_CURRENT_SOURCE_DIR}/value.h.patch 20 | -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/PatchFile.cmake 21 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 22 | ) 23 | 24 | add_executable(hello main.cpp ${CMAKE_CURRENT_BINARY_DIR}/include/value.h) 25 | # specify header file so that add_custom_command() is triggered 26 | target_include_directories(hello PRIVATE include) 27 | target_include_directories(hello BEFORE PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include) 28 | # BEFORE to prioritize patched value.h 29 | target_compile_features(hello PRIVATE cxx_std_11) 30 | 31 | add_test(NAME include_patch COMMAND hello) 32 | 33 | file(GENERATE OUTPUT .gitignore CONTENT "*") 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CMake patch file 2 | 3 | Use `git apply` from CMake to patch source/headers/etc. under the build directory, even with untracked files. 4 | 5 | This works on Linux, MacOS, Windows, etc. assuming Git is installed. 6 | We have tested this approach with GCC, Clang, oneAPI, MSVC, MinGW, MSYS2, etc. 7 | 8 | This is a canonical CMake approach using 9 | [add_custom_command()](https://cmake.org/cmake/help/latest/command/add_custom_command.html) 10 | that is only triggered if the input file changes. 11 | It can also be used for FetchContent patching etc. 12 | 13 | ## Caveats 14 | 15 | As per add_custom_command() docs, the add_custom_command() must be in the same directory as the consuming target. 16 | Otherwise, add_custom_command() is not triggered and the file isn't patch. 17 | If targets from multiple directories consume the patched file, 18 | ensure one of the targets is in the same directory as add_custom_command(), 19 | then use add_dependencies() from the other targets to the target in the patch directory. 20 | 21 | We show a non-basic case where we have a header file to patch, showing how to prepend include directories. 22 | Compilers prioritize include directories left-to-right. 23 | 24 | `git apply` avoids numerous pitfalls experienced with GNU Patch. 25 | 26 | ## Advanced example 27 | 28 | More advanced projects such as those using FetchContent may need additional techniques. 29 | This [example CMakeLists.txt](https://github.com/scivision/mumps/blob/3948131a6b28ad589effd4674a0067b9b3fd871e/src/CMakeLists.txt#L173) 30 | shows that we had to rename some files to avoid collisions. 31 | -------------------------------------------------------------------------------- /cmake/PatchFile.cmake: -------------------------------------------------------------------------------- 1 | find_package(Git REQUIRED) 2 | 3 | set(CMAKE_EXECUTE_PROCESS_COMMAND_ECHO STDOUT) 4 | 5 | # this is done here so that cmake --build build --clean-first also works 6 | 7 | cmake_path(GET patch FILENAME patch_name) 8 | 9 | configure_file("${in}" "${out}" COPYONLY) 10 | configure_file(${patch} ${patch_name} COPYONLY) 11 | 12 | execute_process(COMMAND ${GIT_EXECUTABLE} apply --ignore-whitespace "${patch_name}" 13 | RESULT_VARIABLE ret 14 | ERROR_VARIABLE err 15 | TIMEOUT 5) 16 | # if patch already applied - will fail 17 | 18 | if(NOT ret EQUAL 0) 19 | execute_process(COMMAND ${GIT_EXECUTABLE} apply --ignore-whitespace --check -R "${patch_name}" 20 | RESULT_VARIABLE ret1 21 | ERROR_VARIABLE err1 22 | TIMEOUT 5) 23 | # if succeeds - patch sucessfully applied - conf OK 24 | 25 | if(NOT ret1 EQUAL 0) 26 | message(FATAL_ERROR "Patch ${patch} failed to apply: 27 | ${ret} ${err} 28 | ${ret1} ${err1}" 29 | ) 30 | endif() 31 | 32 | endif() 33 | -------------------------------------------------------------------------------- /include/other.h: -------------------------------------------------------------------------------- 1 | constexpr int other=3; 2 | -------------------------------------------------------------------------------- /include/value.h: -------------------------------------------------------------------------------- 1 | constexpr int RET=1; 2 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "value.h" 4 | #include "other.h" 5 | 6 | int main(){ 7 | 8 | std::cout << "Returning " << RET << "\n"; 9 | 10 | return RET; 11 | } 12 | -------------------------------------------------------------------------------- /value.h.patch: -------------------------------------------------------------------------------- 1 | --- a/include/value.h 2 | +++ b/include/value.h 3 | @@ -1 +1 @@ 4 | -constexpr int RET=1; 5 | +constexpr int RET=0; 6 | --------------------------------------------------------------------------------