Skip to content

chore: require macOS 15, move to grpc-swift v2 and bump dependencies - #280

Open
ethanndickson wants to merge 8 commits into
mainfrom
chore/bump-swift-deps
Open

ethanndickson wants to merge 8 commits into
mainfrom
chore/bump-swift-deps

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

This started as bumping the protoc plugins in the flake. They come from a third-party grpc-swift flake that hasn't been touched since December 2024, and since #276 it needs its own ancient nixpkgs pin just to evaluate on Darwin. nixpkgs doesn't package protoc-gen-grpc-swift, and grpc-swift stopped attaching prebuilt plugins to v1 releases, so keeping v1 meant maintaining our own Nix package for it.

Instead, this PR requires macOS 15 so we can move to grpc-swift v2. Its build tool plugin generates the Swift code for the .proto files at build time, building protoc and both plugins from the resolved packages. The generated code can't drift from the runtime anymore, and the committed .pb.swift/.grpc.swift files, make proto, and all of the protoc tooling in the flake are gone. Clean builds do compile protoc from source once.

The protos now live in a local Swift package, Coder-Desktop/Proto, with a VPNProto target for vpn.proto and a MutagenProto target for the Mutagen protos and their gRPC clients. Attached to an Xcode target, the plugin only sees files in a build phase, which meant putting every .proto and .json in the sources phase and adding no-op build rules to stop Xcode warning about them. In a package it just picks up the files in the target directory. VPNLib imports MutagenProto with internal import, so the Mutagen types stay out of its public API, and anything using the Vpn_ types now imports VPNProto. The exact versions are still pinned in project.yml, and Package.swift only sets minimums. Each target also has a Swift file containing only a comment, since SwiftPM won't load a target without one and the generated code doesn't count.

v2's bidirectional streams are scoped to a closure, so the prompting stream is now withPrompter { promptID in ... } rather than a stream we cancel in a defer. v2 also fails RPCs straight away whilst the channel is backing off after a failed connect, which broke the startup probe (the socket doesn't exist yet), so that call now sets waitForReady. FileSyncDaemonTests caught this, as they run against the real Mutagen binary.

The appcast's minimumSystemVersion and the cask's depends_on are bumped to 15 too, so macOS 14 users don't get offered an update that won't launch.

I also bumped the rest of the app dependencies whilst I was here: Sparkle 2.10.1 (its updater APIs are now @MainActor, so UpdaterService is too), Subprocess 4.0.1, URLRouting 0.7.0, SDWebImageSwiftUI 3.1.4, SDWebImageSVGCoder 1.8.0, and the SwiftLintPlugins floor to 0.65.1 to match the flake's swiftlint. URLRouting 0.7.0 deprecates the .case(CoderRoute.open(...)) form we use; the replacement needs @CasePathable, which needs a direct swift-case-paths dependency, so I've left that warning for now.

This is on top of the stack (#276, #277, #275). I've tested it locally with Xcode 27.

@ethanndickson
ethanndickson added this pull request to stack #278 October 29, 2026 06:53
@ethanndickson
ethanndickson force-pushed the chore/bump-swift-deps branch 2 times, most recently from 30cf862 to 7e83f2e Compare October 29, 2026 08:25
@ethanndickson
ethanndickson removed this pull request from stack #278 October 30, 2026 01:33
Base automatically changed from fix/trusted-app-links to fix/xcode-27 October 30, 2026 01:33
@ethanndickson
ethanndickson added this pull request to stack #281 October 30, 2026 01:45
@ethanndickson
ethanndickson removed this pull request from stack #281 October 30, 2026 01:47
@ethanndickson
ethanndickson added this pull request to stack #282 October 30, 2026 01:47
@matifali

matifali commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

I wonder if we can migrate to mise from nix here?

@ethanndickson
ethanndickson force-pushed the chore/bump-swift-deps branch 2 times, most recently from 01485bc to bdd6220 Compare November 9, 2026 05:47
Base automatically changed from fix/xcode-27 to main November 9, 2026 05:58
grpc-swift v2 needs macOS 15. With it, the Swift code for the .proto
files is generated at build time by grpc-swift-protobuf's build tool
plugin, which builds protoc and both protoc plugins from the resolved
packages. The generated files, `make proto`, and the protoc tooling in
the flake (including the stale grpc-swift flake and its separate nixpkgs
pin) are all gone.

Also bumps SwiftProtobuf, Sparkle, Subprocess, URLRouting,
SDWebImageSwiftUI, SDWebImageSVGCoder, and the SwiftLintPlugins and
ViewInspector floors, and raises the appcast and cask minimum macOS to
15.
The GRPCProtobufGenerator plugin only sees files in a build phase when it's
attached to an Xcode target, which needed the .proto and .json files in the
sources phase project-wide and no-op build rules to silence Xcode. In a Swift
package the plugin picks up the files in the target directory as-is.

VPNProto holds the tunnel messages and MutagenProto the Mutagen messages and
gRPC clients. VPNLib imports MutagenProto internally so the Mutagen types stay
out of its public API.
Depot runners enable Xcode compilation caching through an xcconfig that
prefix maps DerivedData to /^dd. swift-protobuf's protoc finds the
well-known type protos relative to __FILE__, which is remapped too, so the
generator plugin fails with "google/protobuf/timestamp.proto: File not
found". Passing COMPILATION_CACHE_ENABLE_CACHING=NO to xcodebuild wasn't
enough, so opt out with DEPOT_XCODE_CACHE_ENABLED=0 for the jobs that build.
See apple/swift-protobuf#2060
swift-parsing deprecated `.case(CoderRoute.open(workspace:agent:route:))`
in favour of a `CasePathable` case key path. Make `CoderRoute`
`@CasePathable` and route through `\CoderRoute.Cases.open`.

The case key path's value is the labelled payload
`(workspace:agent:route:)`, while the parsers output an unlabelled
`(String, String, OpenRoute)`, so add a conversion that labels the
tuple.

URLRouting doesn't re-export CasePaths, so add swift-case-paths as a
direct VPNLib dependency, pinned to the 1.10.0 that swift-parsing
already resolves.
XcodeGen adds a local package as both a package reference and a folder
reference. Xcode 27 opens the folder first, then refuses to load Proto as a
package because it's "already open as a Folder", so the VPNProto and
MutagenProto products go missing. xcodebuild doesn't mind, which is why CI
passes without this.

There's no XcodeGen option to skip the folder reference yet
(yonaskolb/XcodeGen#1631), so remove it in a
postGenCommand. It's a no-op if XcodeGen stops adding it.
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T04:46:15.907993Z d886054 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 611d726bd4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

grpc-swift v2's graceful shutdown waits for in-flight RPCs to finish,
whereas v1's `channel.close()` failed them straight away. `sync.list` has
no deadline, so if the daemon hung, `stop()` and app quit would hang with
it.

Cancel the task running the connections instead, and drop the
`grpcClient` property, which was only kept for the graceful shutdown.
The whole prompter stream ran at `.background`, including the first
message that `createSession` waits on. Before the grpc-swift v2 move, only
the reply loop did.
Nothing in VPNLib's public API uses them, the same as `MutagenProto`.
@ethanndickson
ethanndickson marked this pull request as ready for review November 10, 2026 03:30
@ethanndickson

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: d886054415

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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.

2 participants