Tech stack¶
Language: Go for core + CLI, Swift for host app and File Provider Extension¶
After explicit evaluation of Go vs Rust, we chose Go:
| Criterion | Go | Rust |
|---|---|---|
| Familiar to maintainer | Yes (Sam) | No (would be learning project) |
| Azure SDK maturity | azidentity GA, msal-go mature |
azure_identity preview/beta |
| MSAL maturity | msal-go is the official Microsoft library |
Community crates only |
| Compile time / iteration | Fast | Slow |
| FFI to Swift | cgo, slightly rough | cbindgen + C-ABI, cleaner |
| File-provider-like memory safety wins | Limited (GC, race detector helps) | Strong (compile-time guarantees) |
| Async story | Goroutines, simple | tokio, sharper learning curve |
| Time to first MVP | Faster | Slower (4-6 weeks ramp-up) |
Rust was the riskier "learning opportunity" path; Go is the safer "ship the product" path. Both can do FUSE-T integration (relevant only if we ever revisit) and FFI to Swift. Sam confirmed Go.
The macOS .app host and the File Provider Extension must be in Swift (or Objective-C). The Go core ships as a static library (libofemcore.a) plus a generated C header (via cgo's //export directives) that Swift imports.
Go libraries¶
Authentication¶
github.com/AzureAD/microsoft-authentication-library-for-go— MSAL Go. Public client app, interactive browser flow, device code flow, silent refresh, cache extensibility.github.com/zalando/go-keyring— macOS Keychain access without cgo dependencies. Implements MSAL'scache.ExportReplace.
HTTP & OneLake¶
net/httpfrom stdlib for the OneLake DFS calls. Custom client wrapper for:- Token injection (
Authorization: Bearer …). - Retry-After honoring on 429 / 503.
- Per-account concurrency limiter (default 4, configurable).
github.com/hashicorp/go-retryablehttp— battle-tested retry wrapper that respectsRetry-After.- Native JSON via stdlib
encoding/json(no need for a faster JSON lib at our volume).
CLI¶
github.com/spf13/cobra— standard Go CLI framework. Subcommands, flags, completions, man-page generation.github.com/spf13/viper— config-file loading, env-var binding (OFEM_*).
Config & data¶
- TOML via
github.com/BurntSushi/toml— the Go standard. Viper supports it natively. - SQLite via
modernc.org/sqlite(pure-Go, no cgo) — for the local file metadata cache (paths, etags, mtimes, sync state).
Logging¶
log/slogfrom stdlib (Go 1.21+). Structured logging with JSON or text handler. No external dependency.- Log files in
~/Library/Logs/dev.debruyn.ofem/ofem.log, rotated withgopkg.in/natefinch/lumberjack.v2.
Telemetry¶
github.com/microsoft/ApplicationInsights-Go— official App Insights SDK for Go. Telemetry client with batching and offline buffer.- Custom panic-handler that flushes telemetry before re-panicking.
IPC (CLI ↔ daemon)¶
net.Listen("unix", …)from stdlib for the Unix domain socket.- JSON-RPC 2.0 over the socket using
net/rpc/jsonrpcfrom stdlib, or a lightweight custom protocol if jsonrpc proves limiting.
LaunchAgent¶
- We ship a
dev.debruyn.ofem.plisttemplate. The CLI writes the resolved plist to~/Library/LaunchAgents/and runslaunchctl bootstrap gui/$UID …to register it. No external dependency needed.
Testing¶
testingfrom stdlib.github.com/stretchr/testifyfor assertions and table-driven test ergonomics.github.com/jarcoal/httpmockfor HTTP-level mocking of OneLake responses in unit tests.- Integration tests use a real Fabric workspace dedicated to OFEM testing, gated behind an env var
OFEM_INTEGRATION=1so they only run when explicitly requested.
Code quality¶
gofmt+goimportson save / pre-commit.github.com/golangci/golangci-lintin CI with a curated config (errcheck, govet, staticcheck, revive, gosec, etc.).commitlintin CI for Conventional Commits enforcement.
Swift libraries¶
Host app¶
- SwiftUI (macOS 14+ baseline) for the account-management UI in Phase 2.
Sparkleis not used — updates are Homebrew-only by decision.
File Provider Extension¶
- Apple's
FileProviderframework (built-in). - Apple's
os.logfor unified logging that integrates with Console.app.
Bridge to Go¶
- cgo's
//export FooproducesFoosymbols callable from C. We generate a headerlibofemcore.hduringgo build -buildmode=c-archive. - Swift imports
libofemcore.hvia a bridging header. - All callbacks across the boundary use C primitives (
char*,int64_t, opaque pointers) — no Go strings or SwiftStringdirectly.
Build & release¶
GoReleaserbuilds the Go binaries.- A separate
xcodebuildstep builds the Swift.appand.appex, linking against the Go static library. codesign --force --options runtime --sign "Developer ID Application: …".xcrun notarytool submit … --waitandxcrun stapler staple.- DMG via
create-dmg(Homebrew formulacreate-dmg). - GoReleaser uploads the DMG to GitHub Releases and bumps the cask in the
homebrew-ofemtap repo.
See docs/packaging-homebrew.md for the full pipeline.
Repository layout (planned)¶
onelake-explorer-macos/
├── cmd/
│ └── ofem/ # CLI entrypoint (main package)
├── internal/
│ ├── auth/ # MSAL wrapper, Keychain cache, account registry
│ ├── onelake/ # DFS API client, retries, pagination
│ ├── fabric/ # Fabric REST API client (discovery)
│ ├── cache/ # SQLite metadata cache, LRU eviction
│ ├── sync/ # Sync engine, write queue, conflict resolution
│ ├── ipc/ # Unix socket server + client
│ ├── telemetry/ # App Insights client
│ ├── config/ # TOML config loading
│ └── log/ # slog setup, lumberjack rotation
├── core/ # cgo-exported façade for Swift
│ ├── core.go # //export symbols
│ └── core.h # generated
├── apple/
│ ├── OneLake.xcodeproj
│ ├── OneLake/ # host app (Swift)
│ └── OneLakeFileProvider/ # extension (.appex, Swift)
├── docs/
├── homebrew/
│ └── ofem.rb # cask template, updated by GoReleaser
├── .github/
├── .goreleaser.yaml
├── go.mod
├── go.sum
├── LICENSE
├── README.md
├── CONTRIBUTING.md
├── SECURITY.md
├── CODE_OF_CONDUCT.md
├── CHANGELOG.md
├── CLAUDE.md
└── PLAN.md