5.9 KiB
5.9 KiB
# Conversation Summary
## Main Topic
Exploration of Swift development across Windows, macOS, and Linux/Armbian — including:
- Managing multiple Swift toolchains & PATH on Windows
- Swift timers (Foundation.Timer, GCD timers, asyncAfter loops)
- Swift Package Manager configuration
- Cross-compiling Swift applications and tests to Linux (incl. Static Linux SDK)
- Swift Concurrency examples (tasks, MainActor hops, async URL fetches)
---
## Key Decisions & Findings
### Swift on Windows
- Changing default toolchain requires editing **PATH** manually.
- To view PATH:
- PowerShell: `$env:Path -split ';'`
- CMD: `echo %PATH%`
- System PATH: `[Environment]::GetEnvironmentVariable("Path","Machine")`
- Uninstall Swift:
- Remove toolchain folders.
- Clean PATH entries.
### Timer & GCD Behavior on Windows
- `DispatchSource.makeTimerSource()` **crashes with illegal instruction** on Windows 10 → avoid.
- Use **`DispatchQueue.main.asyncAfter` loop** or **Foundation.Timer** instead.
### Swift Package Manager
- `platforms:` in `Package.swift` **only supports Apple platforms**.
- Linux/Windows builds **must omit** the `platforms` constraint.
- Can still use `.when(platforms: [.linux, .windows])` for conditional build flags.
### Swift Cross-Compilation
- Swift supports **cross-compiling from macOS to Linux** via the Static Linux SDK.
- Static builds produce **large binaries** (40–150MB), can be reduced with:
- `-Osize`
- `--gc-sections`
- `strip` / `llvm-strip`
- avoiding Foundation where possible
- Dynamic builds (glibc) are much smaller but require Swift runtime on target.
### Swift Testing Framework
- Yes — **Swift Testing** tests can be cross-compiled and shipped to run on Linux **without Swift installed** using the Static SDK:
swift build --build-tests --swift-sdk aarch64-swift-linux-musl -c release
- Run the produced test binary directly on the target machine.
---
## Important Code Created
### 1. GCD asyncAfter repeating loop (Windows-safe)
```swift
import Foundation
import Dispatch
func startRepeating(on queue: DispatchQueue, interval: TimeInterval) {
queue.asyncAfter(deadline: .now() + interval) {
print("[tick] \(Date())")
startRepeating(on: queue, interval: interval)
}
}
print("Start…")
startRepeating(on: .main, interval: 1.0)
RunLoop.main.run()
2. Full Swift Concurrency test app (tasks, hops, sleep, throwing)
enum DemoError: Error, CustomStringConvertible {
case boom(id: String)
var description: String { "DemoError.boom(\(id))" }
}
func sleepSeconds(_ seconds: Double) async throws {
#if compiler(>=6.0)
try await Task.sleep(for: .seconds(seconds))
#else
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
#endif
}
func runScenario(id: String, shouldThrow: Bool) async -> Result<Int,Error> {
await MainActor.run { print("[\(id)] start on MainActor") }
let worker = Task.detached(priority: .background) { () throws -> Int in
print("[\(id)] background: begin")
try await sleepSeconds(1)
print("[\(id)] background: after 1s")
if shouldThrow { throw DemoError.boom(id: id) }
try await sleepSeconds(1)
print("[\(id)] background: after 2s")
return id.unicodeScalars.reduce(100) { $0 + Int($1.value) }
}
let result = await worker.result
await MainActor.run { print("[\(id)] result: \(result)") }
return result
}
@main
struct App {
static func main() async {
let t1 = Task { await runScenario(id:"A", shouldThrow:false) }
let t2 = Task { await runScenario(id:"B", shouldThrow:true) }
print(await t1.value, await t2.value)
}
}
3. Enhanced concurrency test with real URLSession async fetches
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
enum DemoError: Error, CustomStringConvertible {
case boom(id: String)
var description: String { "DemoError.boom(\(id))" }
}
func runScenario(id: String, shouldThrow: Bool) async -> Result<Int,Error> {
await MainActor.run { print("[\(id)] start") }
let worker = Task.detached(priority: .background) { () throws -> Int in
try await Task.sleep(nanoseconds: 1_000_000_000)
let session = URLSession(configuration: .ephemeral)
let (data1, _) = try await session.data(from: URL(string:"https://example.com")!)
if shouldThrow {
_ = try await session.data(from: URL(string:"https://invalid.invalid")!)
}
try await Task.sleep(nanoseconds: 1_000_000_000)
let (data2, _) = try await session.data(from: URL(string:"https://worldtimeapi.org/api/timezone/Etc/UTC")!)
return data1.count + data2.count
}
let result = await worker.result
await MainActor.run { print("[\(id)] -> \(result)") }
return result
}
@main
struct App {
static func main() async {
let r1 = await Task { await runScenario(id:"A", shouldThrow:false) }.value
let r2 = await Task { await runScenario(id:"B", shouldThrow:true) }.value
print(r1, r2)
}
}
4. Minimal cross-platform Package.swift
// swift-tools-version: 5.10
import PackageDescription
let package = Package(
name: "MyApp",
products: [ .executable(name: "MyApp", targets: ["MyApp"]) ],
targets: [
.executableTarget(
name: "MyApp",
swiftSettings: [ .unsafeFlags(["-Osize"], .when(configuration: .release)) ],
linkerSettings: [ .unsafeFlags(["--gc-sections"], .when(platforms: [.linux])) ]
)
]
)
Unresolved / Open Items
-
Detailed example of dynamic-linking cross-compile workflow (glibc) if reducing binary size further is needed.
-
Automatic helper for selecting Swift toolchains and PATH switching on Windows.
-
Setting up CI to run cross-compiled Swift Testing binaries on remote Linux/ARM targets.