Skip to content

ci: stop piping the macOS xcodebuild through xcbeautify - #391

Merged
kraenhansen merged 2 commits into
mainfrom
kh/ci-capture-xcodebuild-log
Aug 10, 2026
Merged

ci: stop piping the macOS xcodebuild through xcbeautify#391
kraenhansen merged 2 commits into
mainfrom
kh/ci-capture-xcodebuild-log

Conversation

@kraenhansen

@kraenhansen kraenhansen commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

When the macOS test app's xcodebuild archive fails in a script phase, the job log is currently undiagnosable: xcbeautify prints the failure summary (** ARCHIVE FAILED ** plus the list of failed phases) but not the phase's own output, and its stdout is block-buffered through the pipe, so only the first handful of build lines ever reach the job log — out of order, after the summary — before the process exits.

Hit while investigating the macOS failure on #372 (tracked in #392), where both [RN] [1] Build Hermesc and [CP] Copy XCFrameworks were reported as failed with zero output explaining either; the actual cause had to be reproduced locally.

This drops the pipe entirely and takes the raw xcodebuild output: verbose, but complete and correctly ordered, and the step's exit code is now xcodebuild's own (so the set -o pipefail guard is no longer needed). It was the only use of xcbeautify in the repo.

Also points the test-macos job's label gate at #392, which is the current reason the job doesn't run by default.

Independent of #372 — this applies to main as-is.

Test plan

  • Test app (macOS) on this PR (label-gated on MacOS 💻, added) — build succeeds and the log shows raw xcodebuild output

🤖 Generated with Claude Code

@kraenhansen kraenhansen added MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support CI Continuous integration labels Aug 10, 2026
@kraenhansen
kraenhansen force-pushed the kh/ci-capture-xcodebuild-log branch from e362967 to 1061c57 Compare August 10, 2026 19:29
@kraenhansen kraenhansen changed the title ci: keep the raw xcodebuild log from the macOS test app build ci: stop piping the macOS xcodebuild through xcbeautify Aug 10, 2026
xcbeautify prints the failure summary of a failed archive but not the
output of the script phase that actually failed, and its stdout is
block-buffered through the pipe, so the tail of the build never reaches
the job log either. A failing archive left nothing to diagnose. Take the
raw xcodebuild output instead: verbose, but complete and correctly
ordered. It was the only use of xcbeautify in the repo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kraenhansen
kraenhansen force-pushed the kh/ci-capture-xcodebuild-log branch from 1061c57 to 1d5868e Compare August 10, 2026 19:30
Keeps the existing rationale for the gate and adds the issue tracking the
react-native-macos version block that keeps it in place for now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kraenhansen
kraenhansen force-pushed the kh/ci-capture-xcodebuild-log branch from cd18ce7 to 6495d95 Compare August 10, 2026 19:35
@kraenhansen kraenhansen self-assigned this Aug 10, 2026
@kraenhansen
kraenhansen marked this pull request as ready for review August 10, 2026 20:00
@kraenhansen
kraenhansen merged commit 0f467e7 into main Aug 10, 2026
15 checks passed
@kraenhansen
kraenhansen deleted the kh/ci-capture-xcodebuild-log branch August 10, 2026 20:35
kraenhansen pushed a commit that referenced this pull request Aug 13, 2026
Follows #391, which dropped xcbeautify from the macOS job and noted it was
the only use of it in the repo. This branch forked before that landed, so
rebasing brought it back for iOS — it should go the same way.

Its stated failure mode showed up here too. Diagnosing the Release build on
this branch, "** ARCHIVE FAILED **" and the failed-command list printed
~150 lines *before* the compiler errors that explained them, because
xcbeautify's stdout is block-buffered through the pipe. The summary read as
the whole story and pointed at the wrong cause.

Raw xcodebuild output is verbose but complete and correctly ordered. It
also makes the pipefail guard added earlier on this branch unnecessary: the
step's exit code is now xcodebuild's own, so `shell: bash` goes too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww
kraenhansen added a commit that referenced this pull request Aug 13, 2026
…ccache enabled (#427)

* Bundle, build and run iOS test app in separate steps in production

* Boot the simulator and use an older xcode

* Print module path on framework slicing failure

* Add x86_64-apple-ios as a default target on an Apple host

* Use ad-hoc signing

* Build app for "iOS Simulator"

* Bundle the test app before running pod install

react-native-test-app resolves the resources declared in app.json when it
generates the workspace (validate_resources in ios/test_app.rb), warning
about and skipping any that don't exist yet:

  CocoaPods will not include resources it cannot find: [...] The app will
  still build and run if they are served by the dev server. To include
  missing resources, make sure they exist, then run `pod install` again.

Bundling after `pod install` therefore left dist/main.ios.jsbundle out of
the app, so it expected a Metro dev server that CI never starts.

Also pass `--assets-dest dist` rather than `dist/res`: dist/res is the
Android convention, whereas React Native's iOS bundler writes into
<assets-dest>/assets and app.json declares dist/assets for iOS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Pick an available iPhone simulator instead of hard-coding one

"iPhone 17" only exists on the Xcode versions that shipped it, and
macos-latest rolls forward, so hard-coding a device name means the boot
step breaks on a runner image update. Query simctl for the first available
iPhone instead, wait for it with `bootstatus -b`, and address it by UDID in
the later steps rather than relying on the "booted" alias.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Don't let xcbeautify mask a failing xcodebuild

GitHub Actions runs a bare `run:` step as `bash -e {0}`, without pipefail,
so `xcodebuild ... | xcbeautify` reports xcbeautify's exit code and a
failing build is swallowed. Naming the shell explicitly opts into
`bash --noprofile --norc -eo pipefail`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Disable explicit modules so the ccache build works

The Release build failed with every system module unbuildable:

  module '_c_standard_library_obsolete' requires feature
  'found_incompatible_headers__check_search_paths'
  could not build module '_Builtin_float' / 'CoreFoundation' / 'Foundation'

preceded by 116 copies of:

  note: Explicit modules is enabled but the compiler was not recognized;
  disable explicit modules with CLANG_ENABLE_EXPLICIT_MODULES=NO, or use
  C_COMPILER_LAUNCHER with CLANG_ENABLE_EXPLICIT_MODULES_WITH_COMPILER_LAUNCHER=YES
  if using a compatible launcher

USE_CCACHE=1 makes React Native's Podfile integration point CC/CXX at its
scripts/xcode/ccache-clang{,++}.sh wrappers. Xcode does not recognise a
wrapper script as a compiler, so it cannot build explicit modules with it.

Of the two fixes Xcode suggests, this takes the first: it keeps ccache,
which is the point of the change. The second would need React Native to
configure ccache through C_COMPILER_LAUNCHER rather than by overriding
CC/CXX, which it does not do as of 0.81.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Wire ccache through C_COMPILER_LAUNCHER instead of USE_CCACHE

The previous commit silenced the "Explicit modules is enabled but the
compiler was not recognized" notes but did not fix the build: the module
errors remained, so explicit modules was a symptom rather than the cause.

The cause is which compiler runs. USE_CCACHE makes React Native set
CC/CXX/LD/LDPLUSPLUS to scripts/xcode/ccache-clang{,++}.sh, whose last line
is:

  exec $CCACHE_BINARY clang "$@"

That `clang` is unqualified, so it resolves off PATH rather than to Xcode's
toolchain. This job runs aminya/setup-cpp, which puts an LLVM toolchain on
PATH, so the build compiles against the iOS SDK with a compiler that does
not match it. Apple traps precisely this:

  module '_c_standard_library_obsolete' requires feature
  'found_incompatible_headers__check_search_paths'

and every system module (_Builtin_float, CoreFoundation, Foundation,
CoreGraphics, UIKit) then fails to build. main is unaffected because it
never sets USE_CCACHE for the app build.

So drop USE_CCACHE and use Xcode's compiler-launcher support instead, which
keeps Xcode's own clang and only prefixes it with ccache. Explicit modules
stays enabled, opted in for a launcher as Xcode's diagnostic describes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Retry pod install on the CocoaPods null-byte flake

CocoaPods intermittently aborts while generating the Pods project:

  ArgumentError - pathname contains null byte
  cocoapods/project.rb:452 Pod::Project#group_for_path_in_group

It hit 2 of the 4 pod install runs on this branch, twice ending the job
before the build step ran at all. It is an unresolved upstream bug that
CocoaPods' own issue matcher points at, specific to pnpm monorepos:
CocoaPods/CocoaPods#12866 and #12798. 1.17.0 is the newest release on
RubyGems (2026-07-06) and is the version that fails, so there is no fixed
version to move to and a workaround is the only option here.

The retry is deliberately narrow: only a failure whose output carries that
exact signature is retried, so any genuine pod install error still fails
the step immediately, and each retry emits a ::warning:: annotation so the
flake stays visible in the run summary instead of being silently absorbed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* ci: stop piping the iOS xcodebuild through xcbeautify

Follows #391, which dropped xcbeautify from the macOS job and noted it was
the only use of it in the repo. This branch forked before that landed, so
rebasing brought it back for iOS — it should go the same way.

Its stated failure mode showed up here too. Diagnosing the Release build on
this branch, "** ARCHIVE FAILED **" and the failed-command list printed
~150 lines *before* the compiler errors that explained them, because
xcbeautify's stdout is block-buffered through the pipe. The summary read as
the whole story and pointed at the wrong cause.

Raw xcodebuild output is verbose but complete and correctly ordered. It
also makes the pipefail guard added earlier on this branch unnecessary: the
step's exit code is now xcodebuild's own, so `shell: bash` goes too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Trim the inline comments in the iOS job

Comment-only; the non-comment diff is empty. Keeps the load-bearing facts —
why bundling precedes pod install, why USE_CCACHE is not set, the upstream
issue behind the pod install retry and the condition for dropping it — and
cuts the surrounding explanation.

* Apply React Native's ccache tuning to the Xcode build

The first green run cached almost nothing: 2160 of 2190 calls uncacheable,
and the 30 hits came from the CMake bootstrap rather than Xcode.

React Native ships scripts/xcode/ccache.conf for exactly this, but it only
takes effect through their ccache-clang.sh wrapper, which sets
CCACHE_CONFIGPATH to it. Moving ccache to a compiler launcher dropped the
wrapper and with it that config, leaving ccache on its defaults — under
which Xcode's -fmodules, precompiled headers, -index-store-path and
-ivfsoverlay all make a compile "too hard" to cache.

So set the same options directly. They land in the config file the ccache
action already writes, which is where the launcher's ccache reads them from,
so the previous CCACHE_DIR/MAXSIZE/COMPILERCHECK exports are now redundant
and go.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

* Give the iOS ccache room to hold a whole build

With React Native's tuning applied, cacheable calls went from 30/2190
(1.37%) to 1082/2185 (49.52%). The cache could not hold the result:

  Cache size (GB):  0.5 /  0.5 (99.76%)
  Cleanups:          88

500M is the action's default. One build filled it and evicted 88 times, so
most of the 1052 newly-cached objects were discarded before the build
finished, and none survive for the next run to hit.

Raised on the iOS job only, which is the one building React Native from
source; the other jobs stay on the default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UxzZtpa5Nz3F8ACXzX7Hww

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI Continuous integration MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant