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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions