Consuming libgit2 as a dependency with CMake's add_subdirectory or FetchContent can silently corrupt results from FindStatNsec
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 10.6k
- Forks
- 2.7k
- PR merge metrics
- No merged PRs in 30d
Description
When attempting to use libgit2 as a dependency without installing it to the environment, I ran into an issue where libgit2 failed to compile:
/…/deps/libgit2/src/util/unix/posix.h:35:3: error: #error GIT_USE_NSEC defined but unknown struct stat nanosecond type
35 | # error GIT_USE_NSEC defined but unknown struct stat nanosecond type
This is ultimately caused by some of CMake's nuances around policy CMP0067 and whether some unrelated CMake includes any of the Check*SourceCompiles.cmake modules before FindStatNsec.cmake during build configuration.
My environment is:
- Ubuntu 24.04.4 LTS (Linux 6.8.0-136-generic), glibc
- CMake 4.4.0
- Ninja 1.11.1
- GCC 13.3.0 and Clang 18.1.3 (I tried both as part of my sanity checking)
This was happening on tag v1.9.6 (26055f5af74ab1cf636d272e8a34315496d3f06f), but is still present on main (2e307f0ec8f50753a24c9471d41fd5902813462d), although instead of a compilation error, there is an incorrect CMake warning, and it is not possible to turn on the nanosecond feature.
Here is a minimal example:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.11) # This will occur with any CMake 3.8+, but 3.11 is when FetchContent was added
project(foo LANGUAGES CXX)
option(CHECK_FIRST "Toggle this ON to include CheckCXXSourceCompiles, which breaks libgit2" OFF)
if(CHECK_FIRST)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("int main(){return 0;}" ARBITRARY_CXX_CODE_COMPILES)
endif()
include(FetchContent)
FetchContent_Declare(libgit2
GIT_REPOSITORY https://github.com/libgit2/libgit2.git
GIT_TAG v1.9.6)
FetchContent_MakeAvailable(libgit2)
$ cmake -S . -B build-ok -DCHECK_FIRST=OFF
...
-- Performing Test HAVE_STRUCT_STAT_ST_MTIM
-- Performing Test HAVE_STRUCT_STAT_ST_MTIM - Success
...
$ cmake --build build-ok # succeeds
$ cmake -S . -B build-bad -DCHECK_FIRST=ON
...
-- Performing Test ARBITRARY_CXX_CODE_COMPILES
-- Performing Test ARBITRARY_CXX_CODE_COMPILES - Success
-- Performing Test HAVE_STRUCT_STAT_ST_MTIM
-- Performing Test HAVE_STRUCT_STAT_ST_MTIM - Failed
-- Performing Test HAVE_STRUCT_STAT_ST_MTIMESPEC
-- Performing Test HAVE_STRUCT_STAT_ST_MTIMESPEC - Failed
-- Performing Test HAVE_STRUCT_STAT_MTIME_NSEC
-- Performing Test HAVE_STRUCT_STAT_MTIME_NSEC - Failed
...
$ cmake --build build-bad
...
error: #error GIT_USE_NSEC defined but unknown struct stat nanosecond type
The only difference between the two configurations is whether an unrelated include(CheckCXXSourceCompiles) runs before FetchContent_MakeAvailable(libgit2). This also will happen if libgit2 is instead added via git submodules and add_subdirectory.
Why does this happen?
During the checks in FindStatNsec.cmake, the compiler options are different than those that are used when compiling libgit2.
FindStatNsec.cmakecallscheck_struct_has_member("struct stat" st_mtim ... LANGUAGE C)to detect whether nanosecond-precision fields are present in the structstat's definition. Internally this callstry_compile(... SOURCE_FROM_VAR ...).- Whether
try_compilehonors the definitions ofCMAKE_C_STANDARDandCMAKE_C_EXTENSIONSis set by policyCMP0067, which is introduced in CMake 3.8. - The modules
Check*SourceCompilesandCheckStructHasMember(and other similar modules) are guarded withinclude_guard(GLOBAL), which means the CMake function that performs the check is defined exactly once per build configuration. The definition is set by whicheverinclude()happens first in the project tree. - CMake's cmake_policy documentation says "Commands created by the function() and macro() commands record policy settings when they are created and use the pre-record policies when they are invoked". And the modules depend on and include
CheckSourceCompiles.cmake. So whichever scope is active when whichever module is first included dictates the policy behavior for all of them. - When one of the modules is included with
CMP0067=NEW,check_struct_has_membercompiles the test programs with-std=c90(CMAKE_C_STANDARD=90 and CMAKE_C_EXTENSIONS=OFF), rather than using the compiler's default standard (gnu17) when no standard is explicitly defined. -std=c90defines__STRICT_ANSI__, which suppresses glibc's default/implicit enabling of feature-test macros like_GNU_SOURCEor_POSIX_C_SOURCE(the ones that exposest_mtim). Unless something explicitly defines them, they will not be available when using-std=c90.- The libgit2 targets do get
-D_GNU_SOURCEadded toCMAKE_C_FLAGSinDefaultCFlags.cmake. So,st_mtimis defined even with-std=c90. However, this is included afterFindStatNsec.cmake. So the targets will always havest_mtim, but the check may incorrectly report that it doesn't, based on CMP0067's value whencheck_struct_has_memberis first defined. - On v1.9.6, when
HAVE_STRUCT_STAT_ST_MTIMis incorrectlyOFFand the other two tests always fail,GIT_USE_STAT_MTIM,GIT_USE_STAT_MTIMESPEC, andGIT_USE_STAT_MTIME_NSECare all not defined andGIT_USE_NSECis defined by default, which leads to the#errorinsrc/util/unix/posix.h. - On main, when
HAVE_STRUCT_STAT_ST_MTIMis incorrectlyOFFand the other two tests always fail,USE_NSECis set toOFF, displays a warning message, and setsGIT_NSECto 0 (which keeps GIT_NSEC undefined in git2_features.h). The code compiles, but always without nanosecond functionality, in the default configuration. If-DUSE_NSEC=ONis added, a fatal error is displayed instead.
Separately, I also noticed on v1.9.6, there is a variable name-mismatch for HAVE_STRUCT_STAT_MTIME_NSEC. In src/CMakeLists.txt, there is a check for HAVE_STRUCT_STAT_ST_MTIME_NSEC instead, so GIT_USE_STAT_MTIME_NSEC is never defined on any platform.
I'd be happy to put together a PR to fix this in main. Could a backport fix to maint/v1.9 also be considered?
And thank you for the fantastic library!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with FindStatNsec.cmake and the CMake checks described in the minimal FetchContent example, then read src/CMakeLists.txt and DefaultCFlags.cmake. Reproduce the difference caused by including CheckCXXSourceCompiles before FetchContent_MakeAvailable(libgit2. Done means add_subdirectory and FetchContent configurations detect struct stat nanosecond support consistently, build successfully, and correct the HAVE_STRUCT_STAT_MTIME_NSEC name mismatch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, cmake
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 62/100