Skip to content

Add SwiftPM and initial linux support for swift - #55

Open
hiimtmac wants to merge 10 commits into
tailscale:mainfrom
hiimtmac:spm
Open

Add SwiftPM and initial linux support for swift#55
hiimtmac wants to merge 10 commits into
tailscale:mainfrom
hiimtmac:spm

Conversation

@hiimtmac

@hiimtmac hiimtmac commented Jul 11, 2026

Copy link
Copy Markdown

TailscaleKit currently only builds via Xcode/xcframework, which requires macOS and doesn't work for server-side or Linux-hosted Swift consumers. This adds a SwiftPM path alongside the existing Xcode build. I tried to do the most limited code change to achieve this goal without breaking the existing xcframework path (make test still passes).

SwiftPM doesn't build the underlying Go/C library the way the Xcode project does. Instead, libtailscale.a is prebuilt per platform/arch and shipped as a TailscaleKit.artifactbundle, which the package's CTailscale binaryTarget consumes. Downstream consumers of the SwiftPM package never need Go or a C toolchain, only the artifactbundle.

  • Connection/listener state was tracked with Combine's CurrentValueSubject, and Combine isn't available on Linux
  • URLSessionConfiguration.tailscaleSession/proxyVia which uses Network.framework is Apple-only, now #if canImport(Network) so it is not included on Linux
  • PlatformShims.swift adds a System namespace for close/read/write, resolved to Darwin/Glibc/Musl depending on platform. Some types declare their own close()/read() methods which shadow the global libc functions of the same name
  • .devcontainer/swift/ is a ready-to-use Linux dev environment (Swift + Go toolchains) for exercising the Linux SwiftPM path without setting up cross-compilation locally

Testing

In the Xcode build for testing, libtstestcontrol.a lives in a separate test-support framework, so it never touches libtailscale.a. SwiftPM's test target links everything into one flat binary, and since both archives share the same Go runtime, they carry duplicate runtime symbols that collide when linked together. fix-tstestcontrol-archive.sh rewrites those symbols in libtstestcontrol.a so they no longer clash (thanks Claude! 🤖), and CTstestControl (a systemLibrary target) exposes the fixed archive to the test target as a C dependency. This is only needed for swift test, not swift build/swift run. Tests also migrated to swift testing.

TailscaleKitCLI

swift/Examples/TailscaleKitCLI is a minimal swift-argument-parser CLI that brings up a node and lists tailnet devices via LocalAPIClient to test that TailscaleKit works through plain SwiftPM, including on Linux, with no Xcode involved. Verified end-to-end against a real Tailscale account (output sanitized).

URLSession on Linux

Foundation's URLSession on Linux is the libcurl-backed swift-corelibs-foundation implementation rather than Apple's CFNetwork, and has historically been less robust for server-side use. If useful, I'd be happy to switch the Linux-side HTTP usage (LocalAPI client, IPN-bus event stream) to AsyncHTTPClient instead. Flagging this as a possible follow-up rather than doing it in this PR, likely with a trait/platform gate so that it doesn't pull in the dependency outside of Linux.

@hiimtmac
hiimtmac force-pushed the spm branch 4 times, most recently from 78b9c67 to 2415990 Compare July 12, 2026 00:23
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