Skip to content

nix-native ios cross compile#3759

Draft
insipx wants to merge 11 commits into
mainfrom
insipx/ios-build
Draft

nix-native ios cross compile#3759
insipx wants to merge 11 commits into
mainfrom
insipx/ios-build

Conversation

@insipx

@insipx insipx commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

this resolves some issues compiling on macOS that crop up occasionally b/c of conflicting XCodes.

this applies my patch which fixes iOS cross-compilation upstream. it adds a script which imports XCode into the nix store, if the right xcode version does not exist it downloads the correct version with xcodes. this pins the xcode version for the iOS library builds.

Note

Add Nix-native iOS cross-compilation pipeline for xcframework and Swift bindings

  • Introduces a new nix/ios-packages.nix flake-parts module that defines iOS cross targets and exposes packages: ios-libs, ios-xcframework-static, ios-xcframework-dynamic, ios-release, and ios-devFast.
  • Adds nix/lib/default.nix support for mkCrossPkgs by patching nixpkgs at build time (via fetchpatch2 of the upstream iOS PR) to enable iOS cross toolchains.
  • Adds nix/package/ios-bindings.nix to build per-ABI libxmtpv3 static/dynamic libraries and generate Swift FFI headers via uniffi.
  • Adds nix/package/ios-xcframework/ with mkStatic, mkDynamic, mkRelease, and mkDev constructors that lipo slices, assemble signed .framework bundles, and invoke xcodebuild -create-xcframework.
  • Adds dev/ios-verify to validate Mach-O artifacts for iOS shippability (platform, arch, load commands, nix store refs, symbol blocklist).
  • Adds sdks/ios/dev/.ensure-xcode to auto-import the required Xcode version into the Nix store on developer machines.
  • Changes commonArgs in nix/lib/base.nix: switches from sqlite to sqlcipher, enforces strictDeps, and uses system OpenSSL instead of vendored builds.
  • Risk: nix/lib/base.nix changes affect all Rust derivations — switching to sqlcipher and system OpenSSL may break existing builds that relied on vendored dependencies.
📊 Macroscope summarized 570924d. 7 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

> >

@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown

Claude finished @insipx's task —— View job


PR Review - Nix-native iOS Cross-Compilation

Review Date: July 2, 2026 17:39 UTC

This PR introduces a comprehensive Nix-based iOS build pipeline to resolve Xcode version conflicts. The implementation is well-structured, but there are three critical issues that need attention before merging:


🔴 Critical Issues

1. Node.js Darwin Build Will Fail with New OpenSSL Linking (nix/lib/base.nix:79)

File: nix/lib/base.nix lines 79-88
Issue: The new commonArgs forces all builds to link against Nix store OpenSSL/sqlcipher via OPENSSL_NO_VENDOR=1. However, nix/package/node.nix:120 has a hard assertion that fails if any /nix/store references remain in the Darwin .node addon after postFixup:

remaining=$(otool -L "$NODE_LIB" | awk 'NR > 1 && $1 ~ /^\/nix\/store\// { print $1 }')
if [ -n "$remaining" ]; then
  echo "error: $NODE_LIB still references /nix/store after postFixup:" >&2
  exit 1
fi

The current postFixup only rewrites libiconv references (lines 97-109), but with OPENSSL_NO_VENDOR, the addon will now also link against /nix/store/.../libssl.dylib, /nix/store/.../libcrypto.dylib, and /nix/store/.../libsqlcipher.dylib. These are not being rewritten, so the remaining-path check will fail.

Impact: Darwin Node.js builds will break in CI and local development.

Fix Required: Either:

  • Add install_name_tool rewrites for openssl/sqlcipher libraries in node.nix postFixup, OR
  • Make the OPENSSL_NO_VENDOR override conditional (exclude it for Node builds), OR
  • Update the remaining-path check to allow these specific libraries

2. CocoaPods Publish Job Missing Xcode Setup (.github/workflows/release-ios.yml:222)

File: .github/workflows/release-ios.yml line 222
Issue: The publish job runs pod trunk push XMTP.podspec --allow-warnings --skip-tests (line 234), but never calls .github/actions/setup-ios-xcode. The pod trunk push command invokes xcodebuild for validation, which will use the runner's default Xcode instead of the pinned version.

This defeats the core goal of this PR (resolving Xcode version conflicts). If the runner's default Xcode differs from what the xcframework was built with, the validation can fail with version mismatches.

Impact: Release job instability when runner Xcode doesn't match pinned version.

Fix Required: Add the setup-ios-xcode step to the publish job before the CocoaPods push:

- uses: ./.github/actions/setup-ios-xcode
- name: Publish to CocoaPods

3. Xcode Version Mismatch Between Action and Source Code

Files: .github/actions/setup-ios-xcode/action.yml:24 and nix/ios-packages.nix:16
Issue: The GitHub action accepts any 26.3.* patch version (line 24: case "$v" in 26.3|26.3.*)), but:

  • The nix-path is hard-coded to a specific store path (line 33) precomputed for one exact Xcode bundle hash
  • The actual iOS build is pinned to xcodeVer = "26.3" in nix/ios-packages.nix:16

On a cold cache with a runner that has Xcode 26.3.1 installed, the action will resolve that bundle, but xmtp-cache-apple cannot import it into the expected /nix/store/x9hdz5mfp44i9b05sswp271jdv68r8vx-Xcode.app path (different bundle = different hash = different store path). Later steps that reference DEVELOPER_DIR with the hard-coded path will fail.

Impact: Cold-cache CI builds fail when runner has a different 26.3.x patch build.

Fix Required: Either tighten the version check to match exactly what the pinned store path expects, OR compute the store path dynamically instead of hard-coding it.


✅ Positive Observations

  1. Static OpenSSL validation (ios-bindings.nix:47-57) properly checks for Nix store refs and undefined symbols - good defensive programming
  2. Hermetic xcframework assembly avoiding xcodebuild is architecturally sound and well-documented
  3. Version pinning strategy with requireFile + NAR cache is appropriate for Apple licensing constraints
  4. The manual framework bundle construction (mk-dynamic.nix, mk-static.nix) correctly handles iOS vs macOS layout differences

📝 Minor Observations (Non-Blocking)

  • The uniffiGenerate call at ios-bindings.nix:65 passes .a to dylibPath - while this appears to work based on author's testing, it's counter-intuitive since uniffi docs suggest .dylib for library mode
  • Several review comments from macroscopeapp are marked as resolved but the resolution relies on "tested empirically" - consider adding integration tests to prevent regressions

Recommendation: Address the three critical issues above, particularly #1 and #2, before merging to prevent immediate CI/release failures.


@insipx insipx force-pushed the insipx/ios-build branch 2 times, most recently from 90ab637 to d23960c Compare June 10, 2026 21:28
Comment thread nix/lib/base.nix
Comment thread nix/package/mls_validation_service.nix
Comment thread nix/lib/packages/xcode-tools.nix Outdated
Comment thread flake.nix

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High

packages = {

The new import ./nix/ios-packages.nix at line 58 replaces the manually-defined iOS packages, but the old flake outputs .#ios-xcframeworks, .#ios-xcframeworks-fast, .#ios-libs, and .#ios-libs-fast are removed without compatibility aliases. The iOS release workflow in .github/workflows/release-ios.yml still invokes nix build .#ios-xcframeworks, so it will fail with "does not provide attribute 'ios-xcframeworks'" after this change. Consider re-exporting these attributes from ./nix/ios-packages.nix or adding passthrough aliases in packages to maintain backward compatibility.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @flake.nix around line 83:

The new import `./nix/ios-packages.nix` at line 58 replaces the manually-defined iOS packages, but the old flake outputs `.#ios-xcframeworks`, `.#ios-xcframeworks-fast`, `.#ios-libs`, and `.#ios-libs-fast` are removed without compatibility aliases. The iOS release workflow in `.github/workflows/release-ios.yml` still invokes `nix build .#ios-xcframeworks`, so it will fail with "does not provide attribute 'ios-xcframeworks'" after this change. Consider re-exporting these attributes from `./nix/ios-packages.nix` or adding passthrough aliases in `packages` to maintain backward compatibility.

Comment thread .envrc Outdated
@@ -1,2 +1,2 @@
watch_file nix/shells/local.nix
use flake .
# watch_file nix/shells/local.nix

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium .envrc:1

Changing use flake . to use flake .#rust removes nix/shells/local.nix from the dev shell, so tools like just are no longer available. The workflow .github/workflows/test-devcontainer.yml runs direnv exec . just --version, which will fail because just is missing from the rust shell. Consider re-adding watch_file nix/shells/local.nix and including just in the rust shell, or document why these tools are intentionally excluded.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.envrc around line 1:

Changing `use flake .` to `use flake .#rust` removes `nix/shells/local.nix` from the dev shell, so tools like `just` are no longer available. The workflow `.github/workflows/test-devcontainer.yml` runs `direnv exec . just --version`, which will fail because `just` is missing from the `rust` shell. Consider re-adding `watch_file nix/shells/local.nix` and including `just` in the `rust` shell, or document why these tools are intentionally excluded.

Comment thread sdks/ios/dev/.ensure-xcode
Comment thread nix/ios-packages.nix Outdated
@insipx insipx force-pushed the insipx/ios-build branch from d23960c to d468d2c Compare June 10, 2026 21:34
Comment thread nix/lib/patches.nix
Comment thread nix/lib/default.nix Outdated
Comment thread nix/lib/base.nix Outdated
Comment on lines +75 to +80
# Use nixpkgs' pre-built openssl instead of letting openssl-sys vendor and build from source
# (which calls xcrun for the SDK and fails in cross sandboxes).
OPENSSL_NO_VENDOR = "1";
OPENSSL_DIR = "${openssl.dev}";
OPENSSL_LIB_DIR = "${openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${openssl.dev}/include";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High lib/base.nix:75

The new OPENSSL_* environment variables in commonArgs are inherited by Android cross-compilation derivations. Android builds intentionally use buildInputs = [ ] to link against the NDK/sysroot instead of Nix libraries, but these OPENSSL_* variables force openssl-sys to use nixpkgs' host OpenSSL (${openssl}). This causes Android builds to either fail compilation or link against x86_64 libraries instead of ARM/Android libraries. Consider making these OpenSSL overrides conditional on !stdenv.hostPlatform.isAndroid (or similar platform check), or moving them to the iOS-specific derivation where they are actually needed.

-    # Use nixpkgs' pre-built openssl instead of letting openssl-sys vendor and build from source
-    # (which calls xcrun for the SDK and fails in cross sandboxes).
-    OPENSSL_NO_VENDOR = "1";
-    OPENSSL_DIR = "${openssl.dev}";
-    OPENSSL_LIB_DIR = "${openssl.out}/lib";
-    OPENSSL_INCLUDE_DIR = "${openssl.dev}/include";
+    # Use nixpkgs' pre-built openssl instead of letting openssl-sys vendor and build from source
+    # (which calls xcrun for the SDK and fails in cross sandboxes).
+    # Only apply for iOS; Android uses NDK OpenSSL via vendored build.
+    OPENSSL_NO_VENDOR = if stdenv.hostPlatform.isDarwin then "1" else null;
+    OPENSSL_DIR = if stdenv.hostPlatform.isDarwin then "${openssl.dev}" else null;
+    OPENSSL_LIB_DIR = if stdenv.hostPlatform.isDarwin then "${openssl.out}/lib" else null;
+    OPENSSL_INCLUDE_DIR = if stdenv.hostPlatform.isDarwin then "${openssl.dev}/include" else null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/lib/base.nix around lines 75-80:

The new `OPENSSL_*` environment variables in `commonArgs` are inherited by Android cross-compilation derivations. Android builds intentionally use `buildInputs = [ ]` to link against the NDK/sysroot instead of Nix libraries, but these `OPENSSL_*` variables force `openssl-sys` to use nixpkgs' host OpenSSL (`${openssl}`). This causes Android builds to either fail compilation or link against x86_64 libraries instead of ARM/Android libraries. Consider making these OpenSSL overrides conditional on `!stdenv.hostPlatform.isAndroid` (or similar platform check), or moving them to the iOS-specific derivation where they are actually needed.

Comment thread sdks/ios/dev/.ensure-xcode
Comment thread nix/package/ios-bindings.nix
Comment thread nix/ios-packages.nix
Comment thread nix/package/ios-bindings.nix Outdated
version = xmtp.mkVersion rust;
inherit (base) commonArgs bindingsFileset;

cargoArtifacts = xmtp.base.mkCargoArtifacts rust false null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High package/ios-bindings.nix:13

The cargoArtifacts derivation at line 13 uses xmtp.base.mkCargoArtifacts rust false null, which builds dependencies without iosEnv.envSetup. Crates with Apple/Xcode-dependent build scripts fail during the dependency phase because the Apple SDK environment is never configured. This breaks cross-compilation for all iOS targets (device and simulator) since ios-packages.nix reuses this derivation for every iOS ABI. Consider passing iosEnv to mkCargoArtifacts so the dependency build runs with proper SDK setup, similar to the previous iOS builder.

-  cargoArtifacts = xmtp.base.mkCargoArtifacts rust false null;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/package/ios-bindings.nix around line 13:

The `cargoArtifacts` derivation at line 13 uses `xmtp.base.mkCargoArtifacts rust false null`, which builds dependencies without `iosEnv.envSetup`. Crates with Apple/Xcode-dependent build scripts fail during the dependency phase because the Apple SDK environment is never configured. This breaks cross-compilation for all iOS targets (device and simulator) since `ios-packages.nix` reuses this derivation for every iOS ABI. Consider passing `iosEnv` to `mkCargoArtifacts` so the dependency build runs with proper SDK setup, similar to the previous iOS builder.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contradicted empirically: the deps-only phase builds clean on the prebuilt toolchain — the cross cc wrapper carries the SDK itself (-isysroot via iosSdkPkgs), so iosEnv is not needed during dependency builds. arm64-apple-ios and simulator artifacts were produced from exactly this configuration.

Comment thread nix/ios-packages.nix
Comment on lines +74 to +77
fastAbis = [
fastAbi
"iphone64-simulator"
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low nix/ios-packages.nix:74

When fastAbi resolves to "x86_64-darwin" on Intel Macs, fastAbis still only contains ["x86_64-darwin", "iphone64-simulator"], where iphone64-simulator is hard-coded to aarch64-apple-ios-simulator. This produces xcframeworks with an arm64 simulator slice and no x86_64-apple-ios-simulator binary, making them unusable for local simulator builds on Intel hosts despite explicitly selecting x86_64-darwin as a supported host. Consider adding an iphone64-simulator-intel target for x86_64-apple-ios-simulator and including it in fastAbis when the host is Intel.

-      fastAbis = [
-        fastAbi
-        "iphone64-simulator"
-      ];
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/ios-packages.nix around lines 74-77:

When `fastAbi` resolves to `"x86_64-darwin"` on Intel Macs, `fastAbis` still only contains `["x86_64-darwin", "iphone64-simulator"]`, where `iphone64-simulator` is hard-coded to `aarch64-apple-ios-simulator`. This produces xcframeworks with an arm64 simulator slice and no `x86_64-apple-ios-simulator` binary, making them unusable for local simulator builds on Intel hosts despite explicitly selecting `x86_64-darwin` as a supported host. Consider adding an `iphone64-simulator-intel` target for `x86_64-apple-ios-simulator` and including it in `fastAbis` when the host is Intel.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged limitation: x86_64 simulator slices are intentionally unsupported (team is arm64-only). Can revisit if an Intel use-case appears.

Comment thread nix/package/ios-xcframework/helpers.nix
Comment thread sdks/ios/dev/.ensure-xcode
Comment on lines +36 to +65
inherit version;
pname = "xmtpv3-swift";
language = "swift";
dylibPath = "${dylib}/libxmtpv3.a";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High package/ios-bindings.nix:36

swift-bindings passes ${dylib}/libxmtpv3.a as dylibPath to uniffiGenerate, which forwards it to ffi-uniffi-bindgen generate --library. UniFFI library-mode generation requires a loadable shared library, not a static archive, and fails with Failed to extract data from archive member when given .a files. This causes the iOS bindings derivation to fail during Swift binding generation instead of producing the expected xcframework inputs.

-    dylibPath = "${dylib}/libxmtpv3.a";
+    dylibPath = "${dylib}/libxmtpv3.dylib";
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/package/ios-bindings.nix around line 36:

`swift-bindings` passes `${dylib}/libxmtpv3.a` as `dylibPath` to `uniffiGenerate`, which forwards it to `ffi-uniffi-bindgen generate --library`. UniFFI library-mode generation requires a loadable shared library, not a static archive, and fails with `Failed to extract data from archive member` when given `.a` files. This causes the iOS bindings derivation to fail during Swift binding generation instead of producing the expected xcframework inputs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works in practice: uniffi-bindgen library mode reads static archives and the swift bindings in this PR were generated from libxmtpv3.a in green builds (device + simulator).

Comment thread sdks/ios/dev/bindings Outdated
Comment thread nix/lib/patches.nix
Comment thread sdks/ios/dev/.ensure-xcode
Comment thread sdks/ios/dev/bindings
Comment thread nix/package/ios-xcframework/default.nix
Comment thread nix/lib/packages/xcode-tools.nix Outdated
Comment thread nix/ios-packages.nix
Comment thread .github/workflows/test-ios.yml Outdated
@insipx insipx force-pushed the insipx/ios-build branch from b74529b to 9ec5479 Compare June 10, 2026 22:05
Comment thread .github/workflows/lint-ios.yml Outdated
Comment on lines +16 to +18
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "26.3"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium workflows/lint-ios.yml:16

xcode-version: "26.3" is a semver range for maxim-lobanov/setup-xcode, not an exact version. The action resolves any installed version matching the range (e.g., 26.3.1), but xmtplabs/xmtp-cache-apple expects the exact content-addressed bundle at the pinned /nix/store path. When the resolved Xcode differs, the cache action only warns about the hash mismatch, then later tries to export the pinned store path which is absent — causing the lint job to fail on cold caches or after eviction. Consider pinning to an exact Xcode version string or adding validation that the selected Xcode matches the expected store path hash.

       - uses: maxim-lobanov/setup-xcode@v1
         with:
-          xcode-version: "26.3"
+          xcode-version: "26.3.0"
Also found in 1 other location(s)

sdks/ios/dev/.ensure-xcode:61

xcodes install "${XCODE_VERSION}" does not request the same Xcode variant that Nix expects. The pinned nixpkgs package darwin.xcode_26_3 is the 26.3_Universal bundle (pkgs/os-specific/darwin/xcode/default.nix), but xcodes now has architecture-specific installs and can choose a non-universal download for install 26.3. When that happens, line 81 rejects the imported app because $got != $expected, so the new "auto-install Xcode" path fails on machines that download the Apple-silicon-only variant instead of the universal one.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/lint-ios.yml around lines 16-18:

`xcode-version: "26.3"` is a semver range for `maxim-lobanov/setup-xcode`, not an exact version. The action resolves any installed version matching the range (e.g., `26.3.1`), but `xmtplabs/xmtp-cache-apple` expects the exact content-addressed bundle at the pinned `/nix/store` path. When the resolved Xcode differs, the cache action only warns about the hash mismatch, then later tries to export the pinned store path which is absent — causing the lint job to fail on cold caches or after eviction. Consider pinning to an exact Xcode version string or adding validation that the selected Xcode matches the expected store path hash.

Also found in 1 other location(s):
- sdks/ios/dev/.ensure-xcode:61 -- `xcodes install "${XCODE_VERSION}"` does not request the same Xcode variant that Nix expects. The pinned nixpkgs package `darwin.xcode_26_3` is the `26.3_Universal` bundle (`pkgs/os-specific/darwin/xcode/default.nix`), but `xcodes` now has architecture-specific installs and can choose a non-universal download for `install 26.3`. When that happens, line 81 rejects the imported app because `$got != $expected`, so the new "auto-install Xcode" path fails on machines that download the Apple-silicon-only variant instead of the universal one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged, but the failure mode is loud-not-wrong: builds consume only the pinned /nix/store path. If setup-xcode resolves a different 26.3.x, the bootstrap import lands at a different store path, the pinned path stays absent, and the nix build fails with requireFile's instructions — a mismatched bundle can never silently poison a build. Warm runs restore from cache and never touch the local bundle. Tightening the bootstrap to verify before import is an xmtp-cache-apple change, tracked separately.

Comment thread nix/package/ios-xcframework/helpers.nix
@insipx insipx force-pushed the insipx/ios-build branch 3 times, most recently from 2ffdf70 to f777113 Compare June 11, 2026 02:10
Comment thread nix/lib/uniffiGenerate.nix
Comment thread sdks/ios/dev/.ensure-xcode Outdated
Comment thread nix/package/ios-xcframework/mk-dynamic.nix Outdated
Comment thread nix/lib/base.nix Outdated
Comment thread sdks/ios/dev/.ensure-xcode Outdated
Comment thread nix/package/ios-xcframework/mk-dynamic.nix
Comment thread nix/lib/packages/xcode-tools.nix Outdated
@insipx insipx force-pushed the insipx/ios-build branch from f777113 to bf8ac4d Compare June 11, 2026 02:21
Comment thread .github/actions/setup-ios-xcode/action.yml
Comment thread .github/workflows/release-ios.yml
@insipx insipx force-pushed the insipx/ios-build branch from bf8ac4d to 83b1163 Compare June 11, 2026 02:57
Comment thread .github/actions/setup-ios-xcode/action.yml Outdated
Comment thread nix/ios-packages.nix
@insipx insipx force-pushed the insipx/ios-build branch from 83b1163 to adae38e Compare June 11, 2026 03:09
Comment thread .github/actions/setup-ios-xcode/action.yml Outdated
@insipx insipx force-pushed the insipx/ios-build branch from adae38e to 7be2c67 Compare June 11, 2026 03:30
Comment thread .github/actions/setup-ios-xcode/action.yml Outdated
@insipx insipx force-pushed the insipx/ios-build branch 2 times, most recently from edcd3c4 to e43c26e Compare June 11, 2026 06:38
Comment thread nix/ios-packages.nix Outdated
@insipx insipx force-pushed the insipx/ios-build branch 3 times, most recently from 538dbd0 to 44a9c6d Compare June 11, 2026 13:51
Comment thread nix/lib/default.nix Outdated
Comment on lines +113 to +114
(hostPkgs.fetchpatch2 {
url = "https://github.com/NixOS/nixpkgs/compare/2f51ad37d9416828be4be7f48e7617b01cdf0641...insipx:nixpkgs:4f8f394b62476598617a085acda285902d5998ce.patch";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High lib/default.nix:113

The fetchpatch2 call at line 113 uses a GitHub .patch URL without ?full_index=1. GitHub's patch files abbreviate index hashes by default, which fetchpatch2 cannot normalize, causing hash verification failures and breaking mkCrossPkgs on clean builds.

-            (hostPkgs.fetchpatch2 {
-              url = "https://github.com/NixOS/nixpkgs/compare/2f51ad37d9416828be4be7f48e7617b01cdf0641...insipx:nixpkgs:4f8f394b62476598617a085acda285902d5998ce.patch";
+            (hostPkgs.fetchpatch2 {
+              url = "https://github.com/NixOS/nixpkgs/compare/2f51ad37d9416828be4be7f48e7617b01cdf0641...insipx:nixpkgs:4f8f394b62476598617a085acda285902d5998ce.patch?full_index=1";
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/lib/default.nix around lines 113-114:

The `fetchpatch2` call at line 113 uses a GitHub `.patch` URL without `?full_index=1`. GitHub's patch files abbreviate `index` hashes by default, which `fetchpatch2` cannot normalize, causing hash verification failures and breaking `mkCrossPkgs` on clean builds.

@insipx insipx force-pushed the insipx/ios-build branch 2 times, most recently from 43307d7 to a3861fd Compare June 11, 2026 16:10
Comment on lines +16 to 17
- uses: ./.github/actions/setup-ios-xcode
- uses: taiki-e/install-action@just

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium workflows/lint-ios.yml:16

The new setup-ios-xcode step pins the workflow to a specific Xcode NAR, but just ios lint only runs swiftlint and swiftformat which don't require Xcode. When the Xcode cache is cold, the runner fails at bootstrap despite the lint tools being available, turning a previously reliable job into an intermittently failing gate. Consider removing this step since it's unnecessary for the tools being invoked.

-      - uses: ./.github/actions/setup-ios-xcode
       - uses: taiki-e/install-action@just
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/lint-ios.yml around lines 16-17:

The new `setup-ios-xcode` step pins the workflow to a specific Xcode NAR, but `just ios lint` only runs `swiftlint` and `swiftformat` which don't require Xcode. When the Xcode cache is cold, the runner fails at bootstrap despite the lint tools being available, turning a previously reliable job into an intermittently failing gate. Consider removing this step since it's unnecessary for the tools being invoked.

@insipx insipx force-pushed the insipx/ios-build branch 4 times, most recently from 8c1ff22 to a083d4d Compare June 11, 2026 18:44
Comment thread nix/package/nextest.nix Outdated
Comment on lines +31 to +36
nativeBuildInputs = xmtp.base.commonArgs.nativeBuildInputs ++ [
cargo-llvm-cov
];
# Test executables link the nix openssl/sqlcipher dynamically
# (OPENSSL_NO_VENDOR) and cargo doesn't rpath — resolve at run time.
LD_LIBRARY_PATH = lib.makeLibraryPath xmtp.base.commonArgs.buildInputs;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium package/nextest.nix:31

LD_LIBRARY_PATH only works on ELF/Linux; macOS dyld uses DYLD_LIBRARY_PATH for runtime library resolution. Since this package is also instantiated for aarch64-darwin in nix/ci-checks.nix, test binaries on macOS will fail to start with missing library errors for openssl/sqlcipher.

Consider setting DYLD_LIBRARY_PATH for Darwin targets, either alongside or conditional to LD_LIBRARY_PATH.

     # Test executables link the nix openssl/sqlcipher dynamically
     # (OPENSSL_NO_VENDOR) and cargo doesn't rpath — resolve at run time.
     LD_LIBRARY_PATH = lib.makeLibraryPath xmtp.base.commonArgs.buildInputs;
+    DYLD_LIBRARY_PATH = lib.optionalString stdenv.isDarwin (lib.makeLibraryPath xmtp.base.commonArgs.buildInputs);
   };
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/package/nextest.nix around lines 31-36:

`LD_LIBRARY_PATH` only works on ELF/Linux; macOS `dyld` uses `DYLD_LIBRARY_PATH` for runtime library resolution. Since this package is also instantiated for `aarch64-darwin` in `nix/ci-checks.nix`, test binaries on macOS will fail to start with missing library errors for `openssl`/`sqlcipher`.

Consider setting `DYLD_LIBRARY_PATH` for Darwin targets, either alongside or conditional to `LD_LIBRARY_PATH`.

@insipx insipx force-pushed the insipx/ios-build branch from a083d4d to 5eabd41 Compare June 11, 2026 19:39
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.64%. Comparing base (004b48c) to head (bd52923).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3759      +/-   ##
==========================================
+ Coverage   84.55%   84.64%   +0.08%     
==========================================
  Files         410      410              
  Lines       60905    60905              
==========================================
+ Hits        51497    51550      +53     
+ Misses       9408     9355      -53     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread nix/lib/base.nix

# Use nixpkgs' pre-built openssl instead of letting openssl-sys vendor and build from source
# (which calls xcrun for the SDK and fails in cross sandboxes).
OPENSSL_NO_VENDOR = "1";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High lib/base.nix:79

Setting OPENSSL_NO_VENDOR=1 forces openssl-sys to link against Nix store libraries, but the iOS packaging code only copies libxmtpv3 into the xcframework without bundling the dependent openssl/sqlcipher/zstd dylibs. The resulting xcframework references absolute Nix store paths that don't exist on consumer machines, causing linking/loading failures for iOS developers outside Nix. Consider vendoring OpenSSL for iOS targets, or ensuring the xcframework packaging rewrites/bundles the runtime dependencies.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/lib/base.nix around line 79:

Setting `OPENSSL_NO_VENDOR=1` forces `openssl-sys` to link against Nix store libraries, but the iOS packaging code only copies `libxmtpv3` into the xcframework without bundling the dependent `openssl`/`sqlcipher`/`zstd` dylibs. The resulting xcframework references absolute Nix store paths that don't exist on consumer machines, causing linking/loading failures for iOS developers outside Nix. Consider vendoring OpenSSL for iOS targets, or ensuring the xcframework packaging rewrites/bundles the runtime dependencies.

xcode-path: ${{ steps.xcode-path.outputs.path }}
# requireFile fixed-output path: moves only if the pinned sha256 for
# xcode_26_3 changes, never with nixpkgs/flake.lock bumps.
xcode-nix-path: /nix/store/x9hdz5mfp44i9b05sswp271jdv68r8vx-Xcode.app

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium setup-ios-xcode/action.yml:36

The Resolve Xcode bundle path step accepts any 26.3.* patch build (line 24: case "$v" in 26.3|26.3.*)), but xcode-nix-path and the final DEVELOPER_DIR export hard-code a single store path (/nix/store/x9hdz5mfp44i9b05sswp271jdv68r8vx-Xcode.app) precomputed for one exact bundle. On a cold cache, a runner with a different patch build (e.g. 26.3.1) resolves a local Xcode whose hash produces a different store path, so xmtplabs/xmtp-cache-apple cannot import it into the expected /nix/store path and later steps that reference the hard-coded DEVELOPER_DIR fail. Consider tightening the version glob to match only the exact patch build the pinned xcode-nix-path was computed from.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/actions/setup-ios-xcode/action.yml around line 36:

The `Resolve Xcode bundle path` step accepts any `26.3.*` patch build (line 24: `case "$v" in 26.3|26.3.*)`), but `xcode-nix-path` and the final `DEVELOPER_DIR` export hard-code a single store path (`/nix/store/x9hdz5mfp44i9b05sswp271jdv68r8vx-Xcode.app`) precomputed for one exact bundle. On a cold cache, a runner with a different patch build (e.g. `26.3.1`) resolves a local Xcode whose hash produces a different store path, so `xmtplabs/xmtp-cache-apple` cannot import it into the expected `/nix/store` path and later steps that reference the hard-coded `DEVELOPER_DIR` fail. Consider tightening the version glob to match only the exact patch build the pinned `xcode-nix-path` was computed from.

@insipx insipx force-pushed the insipx/ios-build branch from 72c4377 to bf63350 Compare July 2, 2026 15:56

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium

- uses: ./.github/actions/setup-release-tools

The publish job runs pod trunk push XMTP.podspec, which invokes xcodebuild for validation, but this job never calls .github/actions/setup-ios-xcode. It uses the runner's default Xcode, so if that differs from the pinned toolchain the pod validation/build can fail on the exact Xcode-version mismatch this PR is trying to eliminate. Add the same setup-ios-xcode step to the publish job (before the CocoaPods push) so both jobs use the same Xcode.

Also found in 1 other location(s)

sdks/ios/dev/.ensure-xcode:23

XCODE_VERSION is taken from the ambient environment (XCODE_VERSION="${XCODE_VERSION:-26.3}"), but the actual iOS build graph is still pinned to xcodeVer = "26.3" in nix/ios-packages.nix. If a developer or CI job already exports XCODE_VERSION (a common variable name in Apple build tooling), this script will materialize and/or cache the wrong darwin.xcode_* store path and return success, after which the subsequent nix build still fails because the real pinned Xcode was never imported.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @.github/workflows/release-ios.yml around line 130:

The `publish` job runs `pod trunk push XMTP.podspec`, which invokes `xcodebuild` for validation, but this job never calls `.github/actions/setup-ios-xcode`. It uses the runner's default Xcode, so if that differs from the pinned toolchain the pod validation/build can fail on the exact Xcode-version mismatch this PR is trying to eliminate. Add the same `setup-ios-xcode` step to the `publish` job (before the CocoaPods push) so both jobs use the same Xcode.

Also found in 1 other location(s):
- sdks/ios/dev/.ensure-xcode:23 -- `XCODE_VERSION` is taken from the ambient environment (`XCODE_VERSION="${XCODE_VERSION:-26.3}"`), but the actual iOS build graph is still pinned to `xcodeVer = "26.3"` in `nix/ios-packages.nix`. If a developer or CI job already exports `XCODE_VERSION` (a common variable name in Apple build tooling), this script will materialize and/or cache the wrong `darwin.xcode_*` store path and return success, after which the subsequent `nix build` still fails because the real pinned Xcode was never imported.

Comment thread nix/lib/base.nix

# Use nixpkgs' pre-built openssl instead of letting openssl-sys vendor and build from source
# (which calls xcrun for the SDK and fails in cross sandboxes).
OPENSSL_NO_VENDOR = "1";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High lib/base.nix:79

On Darwin, the Node addon build in nix/package/node.nix rewrites libiconv load commands in postFixup and then aborts if any other /nix/store path remains in otool -L output. Now that commonArgs forces dynamic linking against Nix openssl and sqlcipher via OPENSSL_NO_VENDOR/OPENSSL_*, the .node addon also records /nix/store/.../libssl, libcrypto, and libsqlcipher references. Those are never rewritten, so the existing remaining=$(otool -L ... | awk 'NR > 1 && $1 ~ /^\/nix\/store\//') check will fail the Darwin node build. Add install name rewrites for those libraries (or adjust the remaining-path check) so the Node addon does not carry unrewritten /nix/store references.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/lib/base.nix around line 79:

On Darwin, the Node addon build in `nix/package/node.nix` rewrites `libiconv` load commands in `postFixup` and then aborts if any other `/nix/store` path remains in `otool -L` output. Now that `commonArgs` forces dynamic linking against Nix `openssl` and `sqlcipher` via `OPENSSL_NO_VENDOR`/`OPENSSL_*`, the `.node` addon also records `/nix/store/.../libssl`, `libcrypto`, and `libsqlcipher` references. Those are never rewritten, so the existing `remaining=$(otool -L ... | awk 'NR > 1 && $1 ~ /^\/nix\/store\//')` check will fail the Darwin node build. Add install name rewrites for those libraries (or adjust the remaining-path check) so the Node addon does not carry unrewritten `/nix/store` references.

insipx added a commit that referenced this pull request Jul 2, 2026
<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible
markers, and the markers themselves will not be visible in the GitHub
rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's
description, Macroscope will append its summary at the bottom of the
description. -->
> [!NOTE]
> ### Update Rust toolchain to 1.96.1 and fix nix wasm and cargo
zigbuild builds
> - Bumps Rust channel in
[flake.nix](https://github.com/xmtp/libxmtp/pull/3810/files#diff-206b9ce276ab5971a2489d75eb1b12999d4bf3843b7988cbe8d687cfde61dea0)
from 1.95.0 to 1.96.1 and updates
[flake.lock](https://github.com/xmtp/libxmtp/pull/3810/files#diff-216b2b7bfde9416c79d133bacb031e95702a20bdedb548c0b055c837aa4f6a9c)
accordingly.
> - Fixes
[nix/package/node.nix](https://github.com/xmtp/libxmtp/pull/3810/files#diff-fa9b6ba547ea5ae35048580f634e5edb6375afb0138fd6fb6e77fef6ee8c357b)
to pass an explicit `--target` flag to `cargo zigbuild` for
glibc-suffixed targets.
> - Reworks
[nix/package/wasm.nix](https://github.com/xmtp/libxmtp/pull/3810/files#diff-2d98b57de1a5d3f600ce6146d8d6cf009fcdb1f194727820ef2639148b5ccd7e)
to let `wasm-pack` install artifacts into `$out/dist` directly, removing
cargo binary post-install and JSON log capture.
> - Updates `@napi-rs/cli` from 3.5.1 to 3.7.2 and Playwright from 1.58
to 1.60 across JS packages.
> - Updates `.envrc` to load the `#rust` flake output and adds `just` to
the Rust dev shell.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized
551ccfe.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- macroscope-ui-refresh -->
<!-- Macroscope's pull request summary ends here -->



updating the .envrc to use the #rust shell since the android bindings
build w/o android sdk in nix shell & nix ios shell is broken until #3759
. further should make loading dev shell faster

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
insipx added 11 commits July 2, 2026 13:31
Cross pkgsets for arm64-apple-ios + simulator via mkCrossPkgs, iOS
bindings derivations, and Darwin sandbox fixes for transitive deps.
Wrap the builder-host Xcode (~94 drvs vs ~2500 source-built); dedupe
target config into iosCommon; ios-verify smoke-checks xcframeworks.
xcode-tools bundle + ios-xcframework module (static, dynamic,
release, dev outputs); no __noChroot.
Cross pkgsets apply the upstream iOS branch onto nixos-unstable via
fetchpatch2 (re-pin hash after branch pushes; URL in nix/lib/default.nix).
SDK output content-addressed on the from-scratch path.
just ios build imports /Applications/Xcode_<ver>.app when missing
(one-time, Darwin only); CI verifies xmtp-cache-apple already did it.
sqlcipher kept for musl dep-builds; DEVELOPER_DIR hook for xcodebuild;
Android drops OPENSSL_* pins; reproducible version file; Linux hides
unbuildable iOS outputs; plist min-version fixes; workflows derive
xcode-path and materialize Xcode via xmtp-cache-apple.
Same stack applied to the same nixos-unstable input; existing cross
drvs are unchanged.
An xcframework is one directory per slice plus an Info.plist manifest;
write it directly with cctools + rcodesign. xcodebuild -create-xcframework
dlopens host Xcode first-launch frameworks (CoreDevice -> MobileDevice),
which cannot be bound into the build sandbox. Drops the xcode-tools
bundle and the __impureHostDeps escape hatch.
Runner Xcode bundles are pruned images and never hash to the pinned
requireFile path; bindings substitute from xmtp.cachix and swift steps
fall back to the version-verified runner bundle.
Static OpenSSL for shipped Apple binaries (nix-store dylib refs crash on
device); floors stay 14/11, asserted against Package.swift/podspec at eval;
validation checks LC_BUILD_VERSION and store-path leaks; symlink-safe
release packaging; shared slice/openssl helpers.
@insipx insipx force-pushed the insipx/ios-build branch from bf63350 to bd52923 Compare July 2, 2026 17:38
Comment thread nix/lib/ios-env.nix
@@ -58,14 +58,16 @@ let
# We detect this by checking if the result starts with /nix/ and skip it.
resolveXcode = ''

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium lib/ios-env.nix:59

When DEVELOPER_DIR is a non-/nix path like /Library/Developer/CommandLineTools (the Command Line Tools package, not a full Xcode), resolveXcode accepts it without verifying it contains a real Xcode toolchain. The exported CC/CXX paths (.../Toolchains/XcodeDefault.xctoolchain/usr/bin/clang) and iOS SDK paths (Platforms/iPhoneOS.platform/...) do not exist in that tree, so envSetup/envSetupAll produce broken compiler and SDK variables and iOS compilation fails — even when xcode-select -p points at a usable full Xcode. Consider rejecting DEVELOPER_DIR values that lack Toolchains/XcodeDefault.xctoolchain and falling through to the xcode-select lookup instead.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @nix/lib/ios-env.nix around line 59:

When `DEVELOPER_DIR` is a non-`/nix` path like `/Library/Developer/CommandLineTools` (the Command Line Tools package, not a full Xcode), `resolveXcode` accepts it without verifying it contains a real Xcode toolchain. The exported `CC`/`CXX` paths (`.../Toolchains/XcodeDefault.xctoolchain/usr/bin/clang`) and iOS SDK paths (`Platforms/iPhoneOS.platform/...`) do not exist in that tree, so `envSetup`/`envSetupAll` produce broken compiler and SDK variables and iOS compilation fails — even when `xcode-select -p` points at a usable full Xcode. Consider rejecting `DEVELOPER_DIR` values that lack `Toolchains/XcodeDefault.xctoolchain` and falling through to the `xcode-select` lookup instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant