Skip to content

FILE(WRITE) of git2.h causes full rebuild on every cmake configure #7222

@kevinushey

Description

@kevinushey

(Full disclosure; this report was AI generated. I reviewed it for accuracy before submission.)

Summary

src/libgit2/CMakeLists.txt uses FILE(WRITE) to produce ${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h (typically git2.h):

FILE(READ "${PROJECT_SOURCE_DIR}/include/git2.h" LIBGIT2_INCLUDE)
STRING(REGEX REPLACE "#include \"git2\/" "#include \"${LIBGIT2_FILENAME}/" LIBGIT2_INCLUDE "${LIBGIT2_INCLUDE}")
FILE(WRITE "${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h" ${LIBGIT2_INCLUDE})

FILE(READ "${PROJECT_SOURCE_DIR}/include/git2.h" LIBGIT2_INCLUDE)
STRING(REGEX REPLACE "#include \"git2/" "#include \"${LIBGIT2_FILENAME}/" LIBGIT2_INCLUDE "${LIBGIT2_INCLUDE}")
FILE(WRITE "${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h" ${LIBGIT2_INCLUDE})

Unlike configure_file(), FILE(WRITE) unconditionally updates the output file's timestamp even when the content is unchanged. Since this header is included (directly or transitively) by virtually every libgit2 source file, the timestamp change triggers a full rebuild of the library on every cmake configure.

This is particularly painful when consuming libgit2 via FetchContent / add_subdirectory, where re-running cmake is common during development.

Other generated headers in the project (git2_features.h, experimental.h) already use configure_file() and don't have this problem.

Suggested fix

Replace the FILE(WRITE) with a write-to-temp + configure_file(COPYONLY) pattern, or the equivalent file(COPY_FILE ... ONLY_IF_DIFFERENT):

FILE(READ "${PROJECT_SOURCE_DIR}/include/git2.h" LIBGIT2_INCLUDE)
STRING(REGEX REPLACE "#include \"git2/" "#include \"${LIBGIT2_FILENAME}/" LIBGIT2_INCLUDE "${LIBGIT2_INCLUDE}")
FILE(WRITE "${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h.tmp" ${LIBGIT2_INCLUDE})
configure_file("${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h.tmp"
               "${PROJECT_BINARY_DIR}/include/${LIBGIT2_FILENAME}.h"
               COPYONLY)

configure_file(COPYONLY) only updates the output when the content differs, preserving the timestamp across re-configures and avoiding unnecessary rebuilds.

Reproduction

  1. Add libgit2 to a project via FetchContent or add_subdirectory
  2. Run cmake .. followed by cmake --build . (first build — everything compiles)
  3. Run cmake .. again (no source changes)
  4. Run cmake --build . — all of libgit2 rebuilds

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions