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 (26055f5), but is still present on main (2e307f0), 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.cmake calls check_struct_has_member("struct stat" st_mtim ... LANGUAGE C) to detect whether nanosecond-precision fields are present in the struct stat's definition. Internally this calls try_compile(... SOURCE_FROM_VAR ...).
- Whether
try_compile honors the definitions of CMAKE_C_STANDARD and CMAKE_C_EXTENSIONS is set by policy CMP0067, which is introduced in CMake 3.8.
- The modules
Check*SourceCompiles and CheckStructHasMember (and other similar modules) are guarded with include_guard(GLOBAL), which means the CMake function that performs the check is defined exactly once per build configuration. The definition is set by whichever include() 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_member compiles 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=c90 defines __STRICT_ANSI__, which suppresses glibc's default/implicit enabling of feature-test macros like _GNU_SOURCE or _POSIX_C_SOURCE (the ones that expose st_mtim). Unless something explicitly defines them, they will not be available when using -std=c90.
- The libgit2 targets do get
-D_GNU_SOURCE added to CMAKE_C_FLAGS in DefaultCFlags.cmake. So, st_mtim is defined even with -std=c90. However, this is included after FindStatNsec.cmake. So the targets will always have st_mtim, but the check may incorrectly report that it doesn't, based on CMP0067's value when check_struct_has_member is first defined.
- On v1.9.6, when
HAVE_STRUCT_STAT_ST_MTIM is incorrectly OFF and the other two tests always fail, GIT_USE_STAT_MTIM, GIT_USE_STAT_MTIMESPEC, and GIT_USE_STAT_MTIME_NSEC are all not defined and GIT_USE_NSEC is defined by default, which leads to the #error in src/util/unix/posix.h.
- On main, when
HAVE_STRUCT_STAT_ST_MTIM is incorrectly OFF and the other two tests always fail, USE_NSEC is set to OFF, displays a warning message, and sets GIT_NSEC to 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=ON is 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!
When attempting to use libgit2 as a dependency without installing it to the environment, I ran into an issue where libgit2 failed to compile:
This is ultimately caused by some of CMake's nuances around policy CMP0067 and whether some unrelated CMake includes any of the
Check*SourceCompiles.cmakemodules beforeFindStatNsec.cmakeduring build configuration.My environment is:
This was happening on tag v1.9.6 (26055f5), but is still present on main (2e307f0), 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:
The only difference between the two configurations is whether an unrelated
include(CheckCXXSourceCompiles)runs beforeFetchContent_MakeAvailable(libgit2). This also will happen if libgit2 is instead added via git submodules andadd_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 ...).try_compilehonors the definitions ofCMAKE_C_STANDARDandCMAKE_C_EXTENSIONSis set by policyCMP0067, which is introduced in CMake 3.8.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.CheckSourceCompiles.cmake. So whichever scope is active when whichever module is first included dictates the policy behavior for all of them.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.-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.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.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 forHAVE_STRUCT_STAT_ST_MTIME_NSECinstead, soGIT_USE_STAT_MTIME_NSECis never defined on any platform.I'd be happy to put together a PR to fix this in
main. Could a backport fix tomaint/v1.9also be considered?And thank you for the fantastic library!