Skip to content

ci: build and test the apple app on every push - #333

Merged
Ryanmello07 merged 1 commit into
mainfrom
ci/build-and-test
Aug 22, 2026
Merged

ci: build and test the apple app on every push#333
Ryanmello07 merged 1 commit into
mainfrom
ci/build-and-test

Conversation

@Ryanmello07

Copy link
Copy Markdown
Contributor

This repo has no .github directory today, so nothing verifies that the iOS and macOS
apps still compile, and nothing runs networkTests. The release pipeline
(build/all/run.sh) archives and signs — it never runs the unit suite. This adds a first
CI that closes both gaps.

What it runs

One job, runs-on: macos-15, on push + PR against main plus workflow_dispatch.

  1. Check out apple into apple/, urnetwork/sdk into sdk/, and clone connect,
    glog, goidenticons at the workspace root.
    Both layouts are forced, not stylistic:
    app/URnetworkSdk/Package.swift is a binaryTarget at
    ../../../sdk/build/apple/URnetworkSdk.xcframework, and sdk/go.mod +
    sdk/build/go.mod resolve the three Go siblings through replace ../ directives. Same
    sibling convention — and the same conditional goidenticons RenderPngV2 shim — as the
    existing sdk, connect and windows workflows. (The shim is a no-op today;
    goidenticons publishes RenderPngV2 at goidenticons.go:373.)
  2. make init_tools build_apple in sdk/build — the xcframeworks are committed
    nowhere and no release publishes them, so CI builds them or it cannot build the app.
  3. Assert the xcframeworks carry the ios-arm64, ios-arm64-simulator and macos-*
    slices
    , each with a plausibly-sized binary.
  4. xcodebuild build, Release, generic/platform=iOS, unsigned.
  5. sdk/build/check_apple_size.sh --extension <URnetworkVPN.appex> — the same budget
    gate build.sh runs after its iOS build.
  6. xcodebuild build, Release, generic/platform=macOS, unsigned.
  7. xcodebuild test -only-testing:networkTests on the iOS Simulator (iPhone 16).

Steps 4, 6 and 7 are build.sh and test-main.sh's own unsigned invocations, unchanged.

What it deliberately does not do

  • No release, signing, provisioning, notarization or upload. No secret is used, and
    none is needed. The two builds pass CODE_SIGNING_ALLOWED=NO and nothing else —
    exactly what build.sh passes. Every target is CODE_SIGN_STYLE = Automatic with a
    DEVELOPMENT_TEAM baked into the pbxproj, and also blanking DEVELOPMENT_TEAM /
    CODE_SIGN_IDENTITY is what provokes "Signing for X requires a development team", so
    the workflow does not.
  • macOS is build-only, not test. test-main.sh's macOS leg has to pass
    -allowProvisioningUpdates DEVELOPMENT_TEAM=6BGU69Q742 because the app's sandbox and
    NetworkExtension entitlements need a real profile. The simulator leg needs no signing at
    all, which is why the tests run there.
  • networkUITests is excluded via -only-testing:networkTests. The URnetwork
    scheme's test action lists both bundles, and networkUITests/testMainAcceptance signs
    into a live account from vault/main/test-acceptance.yml and asserts a changed public
    egress IP. That is the product acceptance suite, out of scope under a no-secrets rule —
    not a silent skip. networkTests is the repo's real unit suite: 10 files, XCTest plus
    swift-testing, covering GlobeGeometry, WorldTopology, TransportStats,
    VPNReconciliationSupport, TunnelMemoryBounds, PresentationLifecycle,
    PhysicalPageBenchmarkResult, ProviderLocationLabel and DeviceModelNames.
  • No localizations checkout. build.sh regenerates Localizable.xcstrings via
    npm run gen:apple in the sibling store, but that file is committed (the pipeline
    commits the refresh — see 995cd52), so CI needs four siblings, not five.

Two pins that differ from the sibling workflows, on purpose

  • runs-on: macos-15 must be Apple silicon. sdk/build/Makefile binds
    iossimulator/arm64 with no x86_64 simulator slice, so the simulator test host cannot
    link on an intel runner. Do not move this to an intel image.
  • Go is pinned via go-version-file: sdk/build/go.mod (1.26.5), not
    go-version: stable
    the way sdk/test.yml and connect/test.yml do. stable is now
    1.27, and build_apple exports GODEBUG=gotypesalias=0, which the 1.27 runtime fatals
    on — the Makefile's own comment says gomobile must be built with go <= 1.26.
    GOTOOLCHAIN: local keeps a future sibling go.mod bump from silently undoing that by
    turning it into a loud "requires go >= 1.27" instead.

make init_tools, not build.sh's make init: init ends with
go clean -cache && go clean -modcache, which would throw away the cache setup-go just
restored. The Makefile documents init_tools as the target for exactly this case
("so acceptance builds do not install tools or clean shared Go caches"), and the
go get golang.org/x/mobile/bind that init adds is a no-op — sdk/build/go.mod already
pins x/mobile at the same GOMOBILE_VERSION.

For a maintainer to decide

  1. Cost. macOS runners bill at 10x, and this is a long job: two gomobile binds
    across four slices each (URnetworkSdk, then URnetworkExtensionSdk with
    -tags ios_extension) before any xcodebuild runs, then two Release builds and a
    simulator test run. timeout-minutes: 180. If that is too expensive per push,
    the cheapest lever is dropping the macOS build; the next is caching the xcframeworks
    keyed on the four sibling SHAs (they cannot go through upload-artifact as-is —
    it dereferences the macOS framework symlinks).
  2. The size gate can fail a PR by design. check_apple_size.sh enforces hard
    ceilings (56 MiB SDK, 53 MiB extension SDK, 39 MiB extension). A legitimate size
    increase will turn CI red until the ceiling is raised deliberately. That is the
    intended behaviour, but it is worth knowing before the first red build.
  3. Sibling drift. The four siblings are checked out at their default branches, so an
    unrelated push to connect can turn every open PR here red at once — the same
    tradeoff server/test.yml documents. Pin ref: per sibling if that proves noisy.
  4. A pre-existing manifest oddity, worth a look regardless of this PR.
    app/URnetworkSdk/Package.swift declares
    .testTarget(name: "URnetworkSdkTests", dependencies: ["URnetworkSdkBinary"]), but
    app/URnetworkSdk/ contains only Package.swift — there is no
    Tests/URnetworkSdkTests directory, and .gitignore does not exclude one. SwiftPM's
    usual response is "Source files for target URnetworkSdkTests should be located under
    'Tests/URnetworkSdkTests'"
    . This predates the PR and would hit a local build.sh
    identically, so Xcode may well tolerate it for a binary-only local package — but if
    the first run dies during package resolution rather than during the Go bind, this is
    the cause.

Verification

python3 -c "import yaml; yaml.safe_load(...)" parses; actionlint (with shellcheck) is
clean; the xcframework-slice assertion was dry-run against a synthetic layout and both
passes and fails correctly. Every referenced name was checked against the repo: the
URnetwork shared scheme, the networkTests blueprint name, the
app.xcodeproj/project.xcworkspace path, sdk/build's init_tools and build_apple
targets, check_apple_size.sh --extension, and sdk/build/go.mod + go.sum.

Not verified without dispatching: that Xcode_16.4.app and an iPhone 16 simulator
are present on the current macos-15 image; the real wall-clock time; and whether the
unsigned generic/platform=iOS Release build clears signing on a runner with no keychain
identity — build.sh does exactly this locally, so it should, but if it demands a team
anyway, drop that step and the --extension size gate with it (the gate needs the
Release-iphoneos appex) and keep the macOS build plus the simulator test.

Left as a draft for that first dispatch.

This repo had no .github directory at all, so nothing verified that the
iOS and macOS apps still compile or that the unit suite still passes.
The release pipeline (build/all/run.sh) archives and signs; it never runs
networkTests. This closes that gap.

What the job does, and why each step is there:

  * Checks out apple into `apple/` and urnetwork/sdk into `sdk/`, then
    clones connect, glog and goidenticons at the workspace root. Both
    layouts are forced, not stylistic: app/URnetworkSdk/Package.swift is
    a binaryTarget at ../../../sdk/build/apple/URnetworkSdk.xcframework,
    and sdk/go.mod + sdk/build/go.mod resolve the three Go siblings
    through `replace ../` directives. Same sibling convention, and the
    same conditional goidenticons RenderPngV2 shim, as the existing
    sdk, connect and windows workflows.

  * Builds the SDK xcframeworks with `make init_tools build_apple`.
    Nothing publishes them, so CI builds them or it cannot build the app.
    init_tools rather than build.sh's `init`, because `init` ends with
    `go clean -cache && go clean -modcache` and would discard the Go
    cache it just populated; the Makefile documents init_tools as the
    target for exactly this case.

  * Pins Go via sdk/build/go.mod (1.26.5) instead of the `go-version:
    stable` the sibling Go workflows use. stable is 1.27, and
    sdk/build/Makefile's build_apple exports GODEBUG=gotypesalias=0,
    which the 1.27 runtime fatals on -- its own comment says gomobile
    must be built with go <= 1.26. GOTOOLCHAIN=local keeps a future
    go.mod bump from silently undoing that.

  * Asserts the built xcframeworks actually carry the ios-arm64,
    ios-arm64-simulator and macos slices. build_apple only tests that
    the directories exist and only size-checks ios-arm64, so a
    part-way bind would otherwise surface 200 lines later as a
    missing-module error in xcodebuild.

  * Builds URnetwork for `generic/platform=iOS` and
    `generic/platform=macOS`, Release, with CODE_SIGNING_ALLOWED=NO and
    nothing else -- byte-for-byte what build.sh does. The targets use
    automatic signing with a DEVELOPMENT_TEAM in the pbxproj, so also
    blanking DEVELOPMENT_TEAM/CODE_SIGN_IDENTITY is what actually
    provokes "requires a development team".

  * Runs check_apple_size.sh --extension on the built appex, as build.sh
    does. Beyond the size ceiling it re-reads the appex's Go build
    metadata, so it also proves the extension was built GOOS=ios with
    fips140 off.

  * Runs `xcodebuild test -only-testing:networkTests` on the iOS
    Simulator. networkTests is a host-app bundle, so the app really
    launches; test-main.sh proves the simulator leg needs no signing,
    while its macOS leg needs -allowProvisioningUpdates and a real team,
    which is why macOS here stays build-only.

Deliberately out of scope: archive, signing, provisioning, notarization
and upload; and networkUITests/testMainAcceptance, which signs into a
live account from vault/main/test-acceptance.yml. No secret is used or
needed.

runs-on macos-15 because it must be Apple silicon: the SDK binds
iossimulator/arm64 with no x86_64 simulator slice, so the test host
cannot link on an intel runner.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MAXFxG1EK4jTxQ1iW73BUr
@Ryanmello07
Ryanmello07 marked this pull request as ready for review August 22, 2026 05:34
@Ryanmello07
Ryanmello07 merged commit 293c961 into main Aug 22, 2026
1 check failed
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