From 3fb831e8f1b30a9a1e9dea9edb3bbaa4bce15265 Mon Sep 17 00:00:00 2001 From: Harold Martin Date: Tue, 14 Jul 2026 07:11:37 -0700 Subject: [PATCH 1/4] Improve libgit2 build reliability --- ObjectiveGitFramework.xcodeproj/project.pbxproj | 7 +++++++ script/update_libgit2 | 11 ++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index bdfc1d91..1a3e1ac9 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -1390,9 +1390,12 @@ files = ( ); inputPaths = ( + "$(SRCROOT)/script/update_libgit2", + "$(SRCROOT)/External/libgit2/CMakeLists.txt", ); name = libgit2; outputPaths = ( + "$(SRCROOT)/External/libgit2.a", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -1743,6 +1746,7 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; ENABLE_TESTABILITY = YES; + EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -1783,6 +1787,7 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -1918,6 +1923,7 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -2136,6 +2142,7 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; diff --git a/script/update_libgit2 b/script/update_libgit2 index 3152c446..068d2fd1 100755 --- a/script/update_libgit2 +++ b/script/update_libgit2 @@ -2,6 +2,15 @@ set -e +product="External/libgit2.a" +newer_source=$(find External/libgit2 \ + \( -path 'External/libgit2/.git' -o -path 'External/libgit2/build' \) -prune -o \ + -type f -newer "$product" -print -quit 2>/dev/null || true) +if [ -f "$product" ] && [ -z "$newer_source" ]; then + echo "libgit2 is up to date." + exit 0 +fi + cd "External/libgit2" if [ -d "build" ]; then @@ -12,7 +21,7 @@ mkdir build cd build # OpenSSL is keg-only, so add its pkgconfig location manually -if [[ $(uname -m) == 'arm64' ]]; then +if [ "$(uname -m)" = 'arm64' ]; then echo "Running on a Apple Silicon M1" export ARCH_PREFIX=/opt/homebrew else From 9ec0c9557c3d67b4d92d2d127f68177b03b6629f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 16:15:27 +0000 Subject: [PATCH 2/4] Do not exclude x86_64 from the build configurations EXCLUDED_ARCHS = x86_64 was added to work around archiving on an Apple Silicon host: ARCHS defaults to arm64 + x86_64 there, while script/update_libgit2 builds a single-arch libgit2.a for the host, so the x86_64 slice fails to link. Excluding x86_64 project-wide was the wrong lever. All four settings sat on project-level configurations, so they cascaded to every target; they contradicted ObjectiveGit-Mac's own VALID_ARCHS = "x86_64 arm64"; and they left the macos-15-intel CI job with no buildable architecture. Architecture selection belongs at the invocation instead - ARCHS=, as the workflow already passes, or ONLY_ACTIVE_ARCH, which Debug and Release already set. Building a universal libgit2.a is not an option here because libgit2 links Homebrew OpenSSL, which is native-arch only. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EeDNs6jJrjDDdpo2T1YUDF --- ObjectiveGitFramework.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index 1a3e1ac9..d79311fe 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -1746,7 +1746,6 @@ CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; ENABLE_TESTABILITY = YES; - EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -1787,7 +1786,6 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -1923,7 +1921,6 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; @@ -2142,7 +2139,6 @@ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - EXCLUDED_ARCHS = x86_64; GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = NO; From 28aeb09dd25cceaa88de9314d565193481ff90a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 16:17:18 +0000 Subject: [PATCH 3/4] Key the libgit2 freshness check on the submodule revision The mtime scan only considered files that still existed. Switching the libgit2 submodule to a revision that deletes or renames a source could leave every surviving file older than External/libgit2.a, so the script reported "libgit2 is up to date." and the build linked stale code. Replace it with a stamp file recording a key built from the submodule revision, the submodule working tree state, the host architecture and a hash of this script. That covers revision switches including ones that only delete files, uncommitted edits and deletions, an archive built for the other architecture, and changes to the cmake flags. The submodule's build directory is excluded so its own output does not force a rebuild. Failures are no longer masked: git errors go to stderr instead of 2>/dev/null, and any part of the key that cannot be computed yields an empty key, which rebuilds rather than trusting an archive we cannot account for. The stamp is removed before building and written only after the archive is installed, so an interrupted build cannot look up to date. The run script phase now sets alwaysOutOfDate instead of declaring inputPaths. libgit2's sources cannot be enumerated statically, so listing only CMakeLists.txt let Xcode skip the phase when sources changed - the same staleness, one level up. The script's own check is now the single source of truth, and it is cheap. Also anchor the script to the repository root so the CI invocation and Xcode's $SRCROOT invocation behave identically, stop the product variable shadowing itself, and teach clean_externals and .gitignore about the stamp. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EeDNs6jJrjDDdpo2T1YUDF --- .gitignore | 1 + .../project.pbxproj | 3 +- script/clean_externals | 1 + script/update_libgit2 | 53 +++++++++++++++---- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 8d65cfa6..8b4b6d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ objective-git.bridgesupport ObjectiveGitFramework/build/* ObjectiveGit-iOS.framework/* External/*.a +External/*.stamp External/*.dylib *.pbxuser diff --git a/ObjectiveGitFramework.xcodeproj/project.pbxproj b/ObjectiveGitFramework.xcodeproj/project.pbxproj index d79311fe..ae48de7c 100644 --- a/ObjectiveGitFramework.xcodeproj/project.pbxproj +++ b/ObjectiveGitFramework.xcodeproj/project.pbxproj @@ -1386,12 +1386,11 @@ }; D0A330F116027F2300A616FA /* libgit2 */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "$(SRCROOT)/script/update_libgit2", - "$(SRCROOT)/External/libgit2/CMakeLists.txt", ); name = libgit2; outputPaths = ( diff --git a/script/clean_externals b/script/clean_externals index a25b6c53..c55dd266 100755 --- a/script/clean_externals +++ b/script/clean_externals @@ -12,6 +12,7 @@ # A list of external static libraries included in the SwiftGit2 framework libraries=( External/libgit2.a + External/libgit2.a.stamp External/libgit2-ios/libgit2-ios.a External/libssh2-ios/lib/libssh2-ios.a External/ios-openssl/lib/libssl.a diff --git a/script/update_libgit2 b/script/update_libgit2 index 068d2fd1..8f4ce470 100755 --- a/script/update_libgit2 +++ b/script/update_libgit2 @@ -2,16 +2,45 @@ set -e -product="External/libgit2.a" -newer_source=$(find External/libgit2 \ - \( -path 'External/libgit2/.git' -o -path 'External/libgit2/build' \) -prune -o \ - -type f -newer "$product" -print -quit 2>/dev/null || true) -if [ -f "$product" ] && [ -z "$newer_source" ]; then +# Resolve the repository root so the script behaves identically whether CI runs +# it from the root or Xcode runs it from $SRCROOT. +script_dir=$(cd "$(dirname "$0")" && pwd) +script_path="$script_dir/$(basename "$0")" +root=$(cd "$script_dir/.." && pwd) +cd "$root" + +submodule="$root/External/libgit2" +archive="$root/External/libgit2.a" +stamp="$root/External/libgit2.a.stamp" + +# A key identifying the archive we would produce. It covers the submodule +# revision, so switching revisions invalidates the archive even when the new +# revision only deletes or renames sources; the submodule working tree, so +# local edits and deletions invalidate it; the host architecture, since the +# archive is built for the host only; and this script, so changing the cmake +# flags invalidates it. If any part of the key cannot be computed we rebuild +# rather than trust an archive we cannot account for. +build_key() { + git -C "$submodule" rev-parse HEAD || return 1 + git -C "$submodule" status --porcelain --untracked-files=all \ + -- . ':(exclude)build' || return 1 + uname -m || return 1 + git hash-object "$script_path" || return 1 +} + +key=$(build_key) || key='' + +if [ -n "$key" ] && [ -f "$archive" ] && [ -f "$stamp" ] && + [ "$key" = "$(cat "$stamp")" ]; then echo "libgit2 is up to date." exit 0 fi -cd "External/libgit2" +# Drop the stamp before building: if the build fails or is interrupted, the +# archive left behind must not look up to date on the next run. +rm -f "$stamp" + +cd "$submodule" if [ -d "build" ]; then rm -rf "build" @@ -43,9 +72,13 @@ cmake -DBUILD_SHARED_LIBS:BOOL=OFF \ .. cmake --build . -product="libgit2.a" -install_path="../../${product}" -rm -rf $install_path -cp -v "${product}" "${install_path}" +rm -f "$archive" +cp -v "libgit2.a" "$archive" + +if [ -n "$key" ]; then + printf '%s\n' "$key" > "$stamp" +else + echo "Could not record a libgit2 build stamp; libgit2 will be rebuilt next time." >&2 +fi echo "libgit2 has been updated." From 1c6bda20c82cdf1c9a3439e343413e97d0780a22 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 16:17:30 +0000 Subject: [PATCH 4/4] Use the matrix architecture in the pull request workflow The workflow interpolated matrix.arch, but the matrix defines abi, so both jobs passed an empty ARCHS and neither pinned the architecture it claims to test. Use matrix.abi, in the archive step and in the commented-out test step so it is correct if it is re-enabled. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01EeDNs6jJrjDDdpo2T1YUDF --- .github/workflows/BuildPR.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/BuildPR.yml b/.github/workflows/BuildPR.yml index 71d7743a..beac9599 100644 --- a/.github/workflows/BuildPR.yml +++ b/.github/workflows/BuildPR.yml @@ -48,6 +48,6 @@ jobs: run: | brew unlink openssl@3 # - name: Test project -# run: xcodebuild -workspace ObjectiveGitFramework.xcworkspace -scheme "ObjectiveGit Mac" test ARCHS="${{ matrix.arch }}" +# run: xcodebuild -workspace ObjectiveGitFramework.xcworkspace -scheme "ObjectiveGit Mac" test ARCHS="${{ matrix.abi }}" - name: Archive project - run: xcodebuild -workspace ObjectiveGitFramework.xcworkspace -scheme "ObjectiveGit Mac" archive ARCHS="${{ matrix.arch }}" + run: xcodebuild -workspace ObjectiveGitFramework.xcworkspace -scheme "ObjectiveGit Mac" archive ARCHS="${{ matrix.abi }}"