diff --git a/.changeset/ios-ci-disabled.md b/.changeset/ios-ci-disabled.md new file mode 100644 index 0000000..3459b27 --- /dev/null +++ b/.changeset/ios-ci-disabled.md @@ -0,0 +1,14 @@ +--- +--- + +Comment out the iOS native CI job. Four `pod lib lint` invocations ran +sequentially on a macOS runner and took 81 minutes a run — against 2m17s for the +Android equivalent — because each lint builds the Lynx engine from scratch in +its own sandbox to check roughly 2,600 lines of Objective-C. + +Nothing compiles the Objective-C now. `native-contract.test.ts` still pins the +native method surfaces against the TypeScript, so a renamed or dropped method is +still caught, but a syntax error or a missing header will reach a tarball. Run +`pod lib lint` by hand on a Mac before releasing a change under +`packages/lynx-*/ios/`. The job is left commented out in the workflow with the +two fixes worth making before turning it back on. diff --git a/.claude/architecture.md b/.claude/architecture.md index 3deff87..4d79bdd 100644 --- a/.claude/architecture.md +++ b/.claude/architecture.md @@ -240,7 +240,11 @@ came from: with an explanation locally, `--require-sdk` in CI. The Gradle harness lives in `android-check/` rather than `android/` so the shipped directory stays clean. - **`pod lib lint`** compiles the Objective-C against the real Lynx pod and iOS - SDK. macOS only, so CI is the only place it can ever run. + SDK. macOS only. It ran in CI until the job's cost — 81 minutes a run, almost + all of it recompiling the Lynx engine once per pod — bought it out; the job is + commented out in `.github/workflows/ci.yml` with the two fixes worth making + before re-enabling it. Nothing compiles the Objective-C now unless someone + runs this by hand on a Mac. **None of that is a device.** Permission flows, `AlarmManager` under Doze, APNs registration and FCM delivery are unverified, as is whether Lynx's annotation diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8169e1..63c2bfa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,9 +53,9 @@ jobs: - name: Smoke and e2e test built artifacts run: bun run test:dist - # The two jobs below are the only things anywhere that compile the native - # halves of the `@amritk/lynx-*` packages — notifications, location, dialogs - # and deep linking. + # `android` below — and `ios`, while it was enabled — are the only things + # anywhere that compile the native halves of the `@amritk/lynx-*` packages — + # notifications, location, dialogs and deep linking. # Everything in `Check` above runs in JavaScript and would pass just as # happily against Kotlin and Objective-C that do not build — # `src/native-contract.test.ts` narrows that gap by comparing the two native @@ -97,34 +97,64 @@ jobs: - name: Compile the Android libraries run: bun run check:android --require-sdk - ios: - name: iOS native - runs-on: macos-15 - steps: - - uses: actions/checkout@v4 - - # `pod lib lint` builds the podspec's sources against the real iOS SDK, - # resolving `Lynx` from CocoaPods so the Lynx headers the module imports - # are the actual ones. It validates from the files on disk and never - # fetches the podspec's `source`. - # - # `--allow-warnings` because CocoaPods warns about things a library - # published from a monorepo subdirectory cannot avoid; it does NOT - # suppress compiler errors, which is the point of running this. - - name: Lint and compile the notifications pod - working-directory: packages/lynx-notifications/ios - run: pod lib lint MiniLynxNotifications.podspec --allow-warnings --platforms=ios - - # A separate step rather than a second podspec in the same command, so a - # failure names which pod broke without reading the log. - - name: Lint and compile the location pod - working-directory: packages/lynx-location/ios - run: pod lib lint MiniLynxLocation.podspec --allow-warnings --platforms=ios - - - name: Lint and compile the dialogs pod - working-directory: packages/lynx-dialogs/ios - run: pod lib lint MiniLynxDialogs.podspec --allow-warnings --platforms=ios - - - name: Lint and compile the deep-linking pod - working-directory: packages/lynx-deep-linking/ios - run: pod lib lint MiniLynxDeepLinking.podspec --allow-warnings --platforms=ios + # DISABLED — the iOS job below was the entire wall clock of this workflow: + # 81 minutes, against 2m17s for `Android native` and 50s for `Check`. + # + # Not because the code is large. The four pods are ~2,600 lines of + # Objective-C between them. Each `pod lib lint` builds an eight-target graph + # — `Lynx`, `LynxBase`, `LynxServiceAPI`, `Lynx-LynxResources`, `PrimJS` and + # a generated host `App` — and compiling the Lynx engine is essentially the + # whole twenty minutes per pod. Because every lint gets its own throwaway + # sandbox, that compile happened four times per run, sequentially, to check + # four small files. On a macOS runner's 10x multiplier that is ~810 billed + # minutes for a check whose Android counterpart costs two. + # + # What is lost while this is off: nothing anywhere compiles the Objective-C. + # `src/native-contract.test.ts` still compares the native method surfaces + # against the TypeScript one, so a renamed or dropped method is still caught + # — but a syntax error, a bad selector, or a header that no longer exists + # will reach a published tarball. Treat a release touching + # `packages/lynx-*/ios/**` as unverified and lint those pods by hand: + # + # cd packages/lynx-notifications/ios + # pod lib lint MiniLynxNotifications.podspec --allow-warnings --platforms=ios + # + # Re-enabling as-is brings the 81 minutes back with it. The two fixes worth + # making first, in order of payoff: gate the job on `packages/lynx-*/ios/**` + # and the podspecs changing (use a job-level `if:`, not a workflow-level + # `paths:` — a workflow that never runs never reports, and a required status + # check that never reports blocks the PR forever), and collapse the four + # lints into a single Podfile carrying all four as development pods so Lynx + # compiles once instead of four times. + # + # ios: + # name: iOS native + # runs-on: macos-15 + # steps: + # - uses: actions/checkout@v4 + # + # # `pod lib lint` builds the podspec's sources against the real iOS SDK, + # # resolving `Lynx` from CocoaPods so the Lynx headers the module imports + # # are the actual ones. It validates from the files on disk and never + # # fetches the podspec's `source`. + # # + # # `--allow-warnings` because CocoaPods warns about things a library + # # published from a monorepo subdirectory cannot avoid; it does NOT + # # suppress compiler errors, which is the point of running this. + # - name: Lint and compile the notifications pod + # working-directory: packages/lynx-notifications/ios + # run: pod lib lint MiniLynxNotifications.podspec --allow-warnings --platforms=ios + # + # # A separate step rather than a second podspec in the same command, so a + # # failure names which pod broke without reading the log. + # - name: Lint and compile the location pod + # working-directory: packages/lynx-location/ios + # run: pod lib lint MiniLynxLocation.podspec --allow-warnings --platforms=ios + # + # - name: Lint and compile the dialogs pod + # working-directory: packages/lynx-dialogs/ios + # run: pod lib lint MiniLynxDialogs.podspec --allow-warnings --platforms=ios + # + # - name: Lint and compile the deep-linking pod + # working-directory: packages/lynx-deep-linking/ios + # run: pod lib lint MiniLynxDeepLinking.podspec --allow-warnings --platforms=ios diff --git a/AGENTS.md b/AGENTS.md index ae5e5d7..c1e8ee4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,11 +39,13 @@ sources of their own: [`packages/lynx-dialogs`](./packages/lynx-dialogs) — `@amritk/lynx-dialogs`, the platform's own date picker, action sheet and alert — and [`packages/lynx-deep-linking`](./packages/lynx-deep-linking) — -`@amritk/lynx-deep-linking`, deep links in and out. Those compile in CI — -`bun run check:android` for the Kotlin, `pod lib lint` on a macOS runner for the -Objective-C — and a parity suite pins their method surfaces against the -TypeScript. **None of it has run on a device.** See each package's `AGENTS.md` -for what that does and does not cover. +`@amritk/lynx-deep-linking`, deep links in and out. The Kotlin compiles in CI — +`bun run check:android` — and a parity suite pins their method surfaces against +the TypeScript. The Objective-C compiles nowhere automatic: `pod lib lint` on a +macOS runner cost 81 minutes a run and is commented out in +`.github/workflows/ci.yml`, so run it by hand on a Mac when you touch `ios/`. +**None of it has run on a device.** See each package's `AGENTS.md` for what that +does and does not cover. The four are deliberately alike: each was built from the last one's shape, so a structural change to one is usually owed to the others. Where one diverges it diff --git a/packages/lynx-deep-linking/AGENTS.md b/packages/lynx-deep-linking/AGENTS.md index c449919..30496ca 100644 --- a/packages/lynx-deep-linking/AGENTS.md +++ b/packages/lynx-deep-linking/AGENTS.md @@ -27,8 +27,10 @@ bun run --filter='@amritk/lynx-deep-linking' build bun run check:android ``` -The iOS half is compiled by `pod lib lint` on a macOS CI runner — there is no -way to build it on Linux at all. See "What is verified, and what is not" below, +The iOS half is not compiled anywhere automatic: the macOS CI job that ran +`pod lib lint` is commented out in `.github/workflows/ci.yml` because it cost 81 +minutes a run, and there is no way to build it on Linux at all. Run it by hand +on a Mac when you touch `ios/`. See "What is verified, and what is not" below, which is the most important thing on this page. ## Layout @@ -140,7 +142,7 @@ one you are relying on before trusting a green run. | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real `org.lynxsdk.lynx:lynx` AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | `native-contract.test.ts` is the cheapest of these and catches the failure a compiler cannot on either side: a method renamed in one language, an argument diff --git a/packages/lynx-deep-linking/README.md b/packages/lynx-deep-linking/README.md index 4274c0a..d5f8a55 100644 --- a/packages/lynx-deep-linking/README.md +++ b/packages/lynx-deep-linking/README.md @@ -296,7 +296,7 @@ reach stand behind it, and it is worth knowing which one a green run came from: | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real Lynx AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | None of that is a device. Whether the `ContentProvider` initialiser wins the race against the first activity, whether `+load` fires before the launch diff --git a/packages/lynx-dialogs/AGENTS.md b/packages/lynx-dialogs/AGENTS.md index 29964d1..0496213 100644 --- a/packages/lynx-dialogs/AGENTS.md +++ b/packages/lynx-dialogs/AGENTS.md @@ -26,8 +26,10 @@ bun run --filter='@amritk/lynx-dialogs' build bun run check:android ``` -The iOS half is compiled by `pod lib lint` on a macOS CI runner — there is no -way to build it on Linux at all. See "What is verified, and what is not" below, +The iOS half is not compiled anywhere automatic: the macOS CI job that ran +`pod lib lint` is commented out in `.github/workflows/ci.yml` because it cost 81 +minutes a run, and there is no way to build it on Linux at all. Run it by hand +on a Mac when you touch `ios/`. See "What is verified, and what is not" below, which is the most important thing on this page. ## Layout @@ -207,7 +209,7 @@ one you are relying on before trusting a green run. | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real `org.lynxsdk.lynx:lynx` AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | `native-contract.test.ts` is the cheapest of these and catches the failure a compiler cannot on either side: a method renamed in one language, an argument diff --git a/packages/lynx-dialogs/README.md b/packages/lynx-dialogs/README.md index c8f0322..ec8054b 100644 --- a/packages/lynx-dialogs/README.md +++ b/packages/lynx-dialogs/README.md @@ -211,9 +211,9 @@ background-thread code. ## What is verified, and what is not The facade and the fake run here. `bun run check:android` compiles the Kotlin -against the real Lynx AAR, `pod lib lint` compiles the Objective-C against the -real iOS SDK, and `src/native-contract.test.ts` pins the method surfaces against -each other. +against the real Lynx AAR and `src/native-contract.test.ts` pins the method +surfaces against each other. `pod lib lint` compiles the Objective-C against the +real iOS SDK, but it is no longer part of CI — run it by hand on a Mac. **None of that is a device.** See [`AGENTS.md`](./AGENTS.md) for exactly what each check does and does not cover. diff --git a/packages/lynx-location/AGENTS.md b/packages/lynx-location/AGENTS.md index 160ebc7..064059e 100644 --- a/packages/lynx-location/AGENTS.md +++ b/packages/lynx-location/AGENTS.md @@ -25,8 +25,10 @@ bun run --filter='@amritk/lynx-location' build bun run check:android ``` -The iOS half is compiled by `pod lib lint` on a macOS CI runner — there is no -way to build it on Linux at all. See "What is verified, and what is not" below, +The iOS half is not compiled anywhere automatic: the macOS CI job that ran +`pod lib lint` is commented out in `.github/workflows/ci.yml` because it cost 81 +minutes a run, and there is no way to build it on Linux at all. Run it by hand +on a Mac when you touch `ios/`. See "What is verified, and what is not" below, which is the most important thing on this page. ## Layout @@ -166,7 +168,7 @@ one you are relying on before trusting a green run. | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real `org.lynxsdk.lynx:lynx` AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | `native-contract.test.ts` is the cheapest of these and catches the failure a compiler cannot on either side: a method renamed in one language, an argument diff --git a/packages/lynx-location/README.md b/packages/lynx-location/README.md index c83aca2..61ea2d9 100644 --- a/packages/lynx-location/README.md +++ b/packages/lynx-location/README.md @@ -252,7 +252,7 @@ reach stand behind it, and it is worth knowing which one a green run came from: | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real Lynx AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | None of that is a device. Permission flows, provider selection, what a fix actually contains outdoors, whether a watch survives a backgrounding, and diff --git a/packages/lynx-notifications/AGENTS.md b/packages/lynx-notifications/AGENTS.md index 409b551..ee8b404 100644 --- a/packages/lynx-notifications/AGENTS.md +++ b/packages/lynx-notifications/AGENTS.md @@ -19,8 +19,10 @@ bun run --filter='@amritk/lynx-notifications' build bun run check:android ``` -The iOS half is compiled by `pod lib lint` on a macOS CI runner — there is no -way to build it on Linux at all. See "What is verified, and what is not" below, +The iOS half is not compiled anywhere automatic: the macOS CI job that ran +`pod lib lint` is commented out in `.github/workflows/ci.yml` because it cost 81 +minutes a run, and there is no way to build it on Linux at all. Run it by hand +on a Mac when you touch `ios/`. See "What is verified, and what is not" below, which is the most important thing on this page. ## Layout @@ -126,7 +128,7 @@ one you are relying on before trusting a green run. | Facade behaviour | `bun run test` | real code, fake platform | | Cross-language signatures | `src/native-contract.test.ts` | parses Kotlin + Objective-C, compares to TypeScript | | Kotlin compiles + packages | `bun run check:android` | real `com.lynx:lynx` AAR, real Android SDK | -| Objective-C compiles | `pod lib lint` (CI, macOS) | real Lynx pod, real iOS SDK | +| Objective-C compiles | `pod lib lint` (manual, macOS — not in CI) | real Lynx pod, real iOS SDK | `native-contract.test.ts` is the cheapest of these and catches the failure a compiler cannot on either side: a method renamed in one language, an argument diff --git a/packages/lynx-notifications/README.md b/packages/lynx-notifications/README.md index 995614d..003592b 100644 --- a/packages/lynx-notifications/README.md +++ b/packages/lynx-notifications/README.md @@ -210,7 +210,7 @@ notifications.deliver({ title: 'Order shipped' }) | TypeScript facade | `bun run test` | everywhere | | JS ⇄ native contract (names, arities, event names) | `src/native-contract.test.ts` | everywhere | | Kotlin compiles and packages to an AAR | `bun run check:android` | needs an Android SDK; CI | -| Objective-C compiles against the iOS SDK | `pod lib lint` | macOS only; CI | +| Objective-C compiles against the iOS SDK | `pod lib lint` | macOS only; not in CI, run by hand | | Any of it working on a device | — | **nothing** | The last row is the one to keep in mind. Both native halves compile against the