204 lines
5.9 KiB
Markdown
204 lines
5.9 KiB
Markdown
```markdown
|
||
# 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)
|
||
|
||
```swift
|
||
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
|
||
|
||
```swift
|
||
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
|
||
// 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. |