cmake: include DefaultCFlags before Check* / Find* modules - #7362
Open
michael-g-matthews wants to merge 1 commit into
Open
michael-g-matthews wants to merge 1 commit into
michael-g-matthews wants to merge 1 commit into
Conversation
When libgit2 is a CMake dependency for another project, the behavior of check_source_compiles is not guaranteed. Functions defined in any Check*SourceCompiles-related module follow the value of CMP0067 at that module's first include() in a CMake configuration. If the consuming project, either directly or indirectly, includes these modules before reaching us, there's no guarantee CMP0067 is set to OLD. When CMP0067 is NEW, check_struct_has_member (used in FindStatNsec.cmake) compiles its test program honoring CMAKE_C_STANDARD and CMAKE_C_EXTENSIONS. By default they are 90 and OFF, respectively, which adds the -std=c90 compiler option. For glibc, -std=c90 defines __STRICT_ANSI__, which suppresses the feature macros that provide st_mtim in struct stat, causing HAVE_STRUCT_STAT_ST_MTIM to report failure. libgit2 targets, however, compile with a definition of struct stat that does have st_mtim. DefaultCFlags.cmake unconditionally adds -D_GNU_SOURCE to CMAKE_C_FLAGS, which provides st_mtim even compiled with -std=c90. But DefaultCFlags.cmake was included after FindStatNsec, so -D_GNU_SOURCE was not yet present when the checks ran. With the USE_NSEC option left at its default, this disabled nanosecond timestamp support with a warning, even though the platform supports it. With -DUSE_NSEC=ON, CMake instead failed to configure outright. Move DefaultCFlags and its dependencies (AddCFlagIfSupported and EnableWarnings) ahead of the Check*/Find* modules, so the tests result in the expected behavior, and therefore the desired features are enabled, regardless of CMP0067. Fixes libgit2#7328
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7328.
Problem
When libgit2 is consumed as a CMake subproject (
add_subdirectoryorFetchContent), nanosecondstattimestamp detection can silently produce the wrong answer, and in some configurations breaks the build outright.On a platform with glibc that fully supports the member
st_mtiminstruct stat, a consuming project may experience any of the following with the main branch (0551dfd):-DUSE_NSEC=ON-DUSE_NSEC=OFF.And additionally on release tag v1.9.7 (49e408b):
src/util/unix/posix.h:35:3: error: #error GIT_USE_NSEC defined but unknown struct stat nanosecond typeThe cause is whether some unrelated part of the dependent project includes one of CMake's
Check*SourceCompiles/CheckStructHasMembermodules before reaching libgit2's scope.Why it happens
FindStatNsec.cmakedetects the nanosecond fields withcheck_struct_has_member("struct stat" st_mtim ... LANGUAGE C), which internally runstry_compile().try_compile()honorsCMAKE_C_STANDARD/CMAKE_C_EXTENSIONSis governed by policyCMP0067(introduced in CMake 3.8).CheckSourceCompiles.cmake,CheckStructHasMember.cmake, and similar are all guarded withinclude_guard(GLOBAL), so the function that performs the check is defined exactly once per CMake run by whicheverinclude()happens firstanywhere in the project tree.
function()andmacro()commands record policy settings when they are created and use the pre-record policies when they are invoked."CMakeLists.txtdeclarescmake_minimum_required(VERSION 3.5.1), so when libgit2 is the first toinclude these modules, the check function is recorded with
CMP0067unset, and therefore consideredOLDby definition, andtry_compile()uses the compiler's default standard (gnu17), where glibc exposesst_mtim.cmake_minimum_required(VERSION 3.8)or newer and includes aCheck*module first, the function is recorded withCMP0067set toNEW.check_struct_has_memberthen compiles its probe with-std=c90, the libgit2 default withCMAKE_C_STANDARD=90andCMAKE_C_EXTENSIONS=OFF.-std=c90defines__STRICT_ANSI__, which suppresses glibc's implicit feature-test macros_GNU_SOURCEor_POSIX_C_SOURCE, which exposest_mtim. SoHAVE_STRUCT_STAT_ST_MTIMis given the valuefalse, and sincethe other probes also fail, libgit2 concludes the platform has no nanosecond
statsupport.DefaultCFlags.cmakeunconditionally adds-D_GNU_SOURCEtoCMAKE_C_FLAGS, sost_mtimis always available when libgit2's sources are compiled. However,DefaultCFlagswas included after
FindStatNsec, so-D_GNU_SOURCEwas not yet inCMAKE_C_FLAGSwhen the probes ran.maint/v1.9,GIT_USE_NSECis defined by default whileGIT_USE_STAT_MTIM,GIT_USE_STAT_MTIMESPEC, andGIT_USE_STAT_MTIME_NSECare all undefined, sosrc/util/unix/posix.hhits its#errorduring compilation.Fix
Move
DefaultCFlags(and the two modules it depends on,AddCFlagIfSupportedandEnableWarnings) ahead of theCheck*/Find*modules, so-D_GNU_SOURCEis already part ofCMAKE_C_FLAGSwhen thestatchecks run. The checks then see the samestruct statdefinition that libgit2's targets compile against, and detection is correct regardless of the value ofCMP0067.DefaultCFlags.cmakeonly readsCMAKE_SYSTEM_NAME,BUILD_SHARED_LIBS,MSVC,MINGW, reproducible-build options, andlibgit2_VERSION_*, all of which are already set by this point, so it is safe to run earlier. It does not depend on any result produced by theCheck*/Find*modules.This does not attempt to change libgit2's default C standard or to stop the
Check*modules from being policy-sensitive. Rather, it ensures the necessary C flag for thestruct statchecks (-D_GNU_SOURCE) is present before they run.Verification
Reproduction project
A minimal CMake parent project that links libgit2 as a subproject (
add_subdirectory). It contains a single toggle that decides whether aCheck*module is included before libgit2 scope:-DCHECK_FIRSTmaps directly onto the effectiveCMP0067value seen by the guarded check functions:CHECK_FIRSTCheck*firstCMP0067recorded on the check functionOFFcmake_minimum_required(VERSION 3.5.1)ONcmake_minimum_required(VERSION 3.8)Environment
Commands
CMP0067USE_NSECcmakecommand-line options-DCHECK_FIRST=OFF-DCHECK_FIRST=ON-DCHECK_FIRST=OFF -DUSE_NSEC:STRING="true"-DCHECK_FIRST=ON -DUSE_NSEC:STRING="true"-DCHECK_FIRST=OFF -DUSE_NSEC:STRING="false"-DCHECK_FIRST=ON -DUSE_NSEC:STRING="false"main(0551dfd4ad989b6a3d5683c0d4cf326c6efef929)CMP0067USE_NSECHAVE_STRUCT_STAT_ST_MTIMcheckNanosecond support, using mtimCMake Warning ... nanosecond timestamp precision was not detected, feature listed as disabledNanosecond support, using mtimCMake Error at cmake/SelectNsec.cmake:18... nanosecond support was requested but no platform support is availableRows 2 and 4 show the bug: the platform supports
st_mtim, but a change inCheck*function behavior, outside libgit2's control, disables the feature witha warning (row 2) or fails to configure (row 4).
mainafter this PR (d59f04354d846a7305520d22fd2eb60d75d84612)CMP0067USE_NSECHAVE_STRUCT_STAT_ST_MTIMcheckNanosecond support, using mtimNanosecond support, using mtimNanosecond support, using mtimNanosecond support, using mtimgit2_features.hnow contains#define GIT_NSEC 1/#define GIT_NSEC_MTIM 1in rows 1–4 regardless of
CMP0067.USE_NSEC:STRING="false"still cleanlydisables the feature.
Backport to
maint/v1.9The commit cherry-picks onto
maint/v1.9(49e408b, v1.9.7) cleanly.maint/v1.9(v1.9.7,49e408b3208bc3093757a1c2db938d3590f3f412)CMP0067USE_NSECHAVE_STRUCT_STAT_ST_MTIMchecknanoseconds, support nanosecond precision file mtimes and ctimesnanoseconds, support nanosecond precision file mtimes and ctimes)posix.h:35:3: error: #error GIT_USE_NSEC defined but unknown struct stat nanosecond typenanoseconds, support nanosecond precision file mtimes and ctimesnanoseconds, support nanosecond precision file mtimes and ctimes)posix.h:35:3: error: #error GIT_USE_NSEC defined but unknown struct stat nanosecond typemaint/v1.9after backport (8713217c6fc007c22d33cdeab63bc2089a857a2b)CMP0067USE_NSECHAVE_STRUCT_STAT_ST_MTIMchecknanoseconds, support nanosecond precision file mtimes and ctimesGIT_USE_NSEC+GIT_USE_STAT_MTIM)nanoseconds, support nanosecond precision file mtimes and ctimesnanoseconds, support nanosecond precision file mtimes and ctimesnanoseconds, support nanosecond precision file mtimes and ctimesAfter the backport, all six configurations compile, the
NEWrows match theOLDrows, and the two previously broken build failures (rows 2 and 4) are resolved.USE_NSEC:STRING="false"still disables the feature as requested.If a backport is desired, should I open another PR targeting that branch, which also includes the fix to the
HAVE_STRUCT_STAT_MTIME_NSEC,HAVE_STRUCT_STAT_ST_MTIME_NSECmismatch?