Skip to content

Commit 35ee9bb

Browse files
committed
feat: add DocC documentation, comprehensive tests, and cross-platform CI
- Add DocC documentation catalog with 8 articles covering all container types - GettingStarted, WorkingWithText, WorkingWithLists, WorkingWithMaps - WorkingWithTrees, WorkingWithCounters, VersioningAndSync - Add 51 new unit tests for Text, List, MovableList, Map, Tree, Counter, and Versioning - Add Linux and Windows CI support with GitHub Actions matrix - Add FoundationEssentials support for better Linux compatibility - Add build scripts for Linux (build_linux.sh) and Windows (build_windows.ps1) - Update Package.swift for cross-platform support with system library target
1 parent 8366c78 commit 35ee9bb

20 files changed

+3052
-364
lines changed

.github/workflows/ci.yaml

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,61 @@
1-
name: build-test
1+
name: swift
2+
23
on:
4+
push:
5+
branches: [main, master, develop]
36
pull_request:
4-
branches: [main]
7+
branches: [main, master, develop]
58

69
jobs:
7-
build-test:
8-
runs-on: macos-14
9-
env:
10-
LOCAL_BUILD: true
11-
DEVELOPER_DIR: /Applications/Xcode_15.4.app
10+
# Build and test on multiple platforms
11+
build-and-test:
12+
name: Swift ${{ matrix.swift }} on ${{ matrix.os }}
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
os: [ubuntu-latest, macos-latest, windows-latest]
18+
swift: ["6.0.0"]
1219
steps:
13-
- uses: actions/checkout@v3
20+
- uses: actions/checkout@v4
1421
with:
1522
submodules: recursive
16-
- uses: actions-rs/toolchain@v1
23+
24+
- name: Setup Swift
25+
uses: SwiftyLab/setup-swift@v1
1726
with:
18-
profile: minimal
19-
toolchain: 1.82.0
20-
default: true
21-
- name: get xcode information
27+
swift-version: ${{ matrix.swift }}
28+
29+
# Rust is needed to build the FFI library on all platforms
30+
- name: Setup Rust
31+
uses: dtolnay/rust-toolchain@stable
32+
with:
33+
toolchain: "1.82.0"
34+
35+
# Build Rust FFI library (Linux)
36+
- name: Build Rust FFI (Linux)
37+
if: runner.os == 'Linux'
2238
run: |
23-
xcodebuild -version
24-
swift --version
25-
- name: build xcframework
39+
chmod +x ./scripts/build_linux.sh
40+
./scripts/build_linux.sh
41+
42+
# Build Rust FFI library (Windows)
43+
- name: Build Rust FFI (Windows)
44+
if: runner.os == 'Windows'
45+
run: .\scripts\build_windows.ps1
46+
shell: pwsh
47+
48+
# Build Rust FFI library (macOS) - builds local xcframework
49+
- name: Build Rust FFI (macOS)
50+
if: runner.os == 'macOS'
2651
run: ./scripts/build_macos.sh
27-
- name: Swift tests
28-
run: LOCAL_BUILD=true swift test
52+
53+
- name: Build
54+
run: swift build -c release
55+
env:
56+
LOCAL_BUILD: ${{ runner.os == 'macOS' && 'true' || '' }}
57+
58+
- name: Run tests
59+
run: swift test
60+
env:
61+
LOCAL_BUILD: ${{ runner.os == 'macOS' && 'true' || '' }}

Package.swift

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,81 @@
44
import Foundation
55
import PackageDescription
66

7-
let FFIbinaryTarget: PackageDescription.Target
7+
// Detect the current platform
8+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
9+
let isApplePlatform = true
10+
#else
11+
let isApplePlatform = false
12+
#endif
813

9-
if ProcessInfo.processInfo.environment["LOCAL_BUILD"] != nil {
10-
FFIbinaryTarget = .binaryTarget(name: "LoroFFI", path: "./loroFFI.xcframework.zip")
11-
}else {
12-
FFIbinaryTarget = .binaryTarget(
14+
// Determine which target to use for LoroFFI
15+
let FFITarget: PackageDescription.Target
16+
let loroDependencies: [PackageDescription.Target.Dependency]
17+
18+
if isApplePlatform {
19+
// Apple platforms use the xcframework
20+
if ProcessInfo.processInfo.environment["LOCAL_BUILD"] != nil {
21+
FFITarget = .binaryTarget(name: "LoroFFI", path: "./loroFFI.xcframework.zip")
22+
} else {
23+
FFITarget = .binaryTarget(
24+
name: "LoroFFI",
25+
url: "https://github.com/loro-dev/loro-swift/releases/download/1.8.1/loroFFI.xcframework.zip",
26+
checksum: "6c723580b568aeccd05debc3cb40635912f5a882520cf42fe84c72220edd0f12"
27+
)
28+
}
29+
loroDependencies = ["LoroFFI"]
30+
} else {
31+
// Linux/Windows use a system library target
32+
// The library must be built first using scripts/build_linux.sh or scripts/build_windows.ps1
33+
FFITarget = .systemLibrary(
1334
name: "LoroFFI",
14-
url: "https://github.com/loro-dev/loro-swift/releases/download/1.8.1/loroFFI.xcframework.zip",
15-
checksum: "6c723580b568aeccd05debc3cb40635912f5a882520cf42fe84c72220edd0f12"
35+
path: "Sources/LoroFFI",
36+
pkgConfig: nil,
37+
providers: []
1638
)
39+
loroDependencies = ["LoroFFI"]
1740
}
1841

19-
let package = Package(
42+
// Define platforms - only specify for Apple platforms
43+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
44+
let platforms: [SupportedPlatform] = [
45+
.iOS(.v13),
46+
.macOS(.v10_15),
47+
.visionOS(.v1)
48+
]
49+
#else
50+
let platforms: [SupportedPlatform]? = nil
51+
#endif
52+
53+
var package = Package(
2054
name: "Loro",
21-
platforms: [
22-
.iOS(.v13),
23-
.macOS(.v10_15),
24-
.visionOS(.v1)
25-
],
2655
products: [
27-
// Products define the executables and libraries a package produces, making them visible to other packages.
2856
.library(
2957
name: "Loro",
3058
targets: ["Loro"]),
3159
],
3260
targets: [
33-
// Targets are the basic building blocks of a package, defining a module or a test suite.
34-
// Targets can depend on other targets in this package and products from dependencies.
35-
FFIbinaryTarget,
61+
FFITarget,
3662
.target(
3763
name: "Loro",
38-
dependencies: ["LoroFFI"]
64+
dependencies: loroDependencies,
65+
linkerSettings: isApplePlatform ? nil : [
66+
.linkedLibrary("loro_swift", .when(platforms: [.linux])),
67+
.linkedLibrary("loro_swift", .when(platforms: [.windows])),
68+
.unsafeFlags(["-L", "Sources/LoroFFI/lib"], .when(platforms: [.linux, .windows]))
69+
]
3970
),
4071
.testTarget(
4172
name: "LoroTests",
4273
dependencies: ["Loro"]),
4374
]
4475
)
76+
77+
// Set platforms for Apple
78+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)
79+
package.platforms = [
80+
.iOS(.v13),
81+
.macOS(.v10_15),
82+
.visionOS(.v1)
83+
]
84+
#endif

Sources/Loro/Ephemeral.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
//
22
// Ephemeral.swift
3-
//
3+
//
44
//
55
// Created by Leon Zhao on 2025/6/4.
66
//
77

8+
#if !hasFeature(Embedded)
9+
#if canImport(FoundationEssentials)
10+
import FoundationEssentials
11+
#elseif canImport(Foundation)
812
import Foundation
13+
#endif
14+
#endif
915

1016
class ClosureEphemeralSubscriber: EphemeralSubscriber {
1117
private let closure: (EphemeralStoreEvent) -> Void

Sources/Loro/Event.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
#if !hasFeature(Embedded)
2+
#if canImport(FoundationEssentials)
3+
import FoundationEssentials
4+
#elseif canImport(Foundation)
15
import Foundation
6+
#endif
7+
#endif
28

39
class ClosureSubscriber: Subscriber {
410
private let closure: (DiffEvent) -> Void
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Getting Started
2+
3+
Learn how to create and sync Loro documents.
4+
5+
## Overview
6+
7+
Loro provides conflict-free replicated data types (CRDTs) for building collaborative applications. This guide walks you through creating a document, adding data, and syncing between peers.
8+
9+
## Creating a Document
10+
11+
Start by creating a ``LoroDoc`` instance:
12+
13+
```swift
14+
import Loro
15+
16+
let doc = LoroDoc()
17+
```
18+
19+
## Working with Containers
20+
21+
Loro provides several container types for different data structures:
22+
23+
### Text
24+
25+
```swift
26+
let text = doc.getText(id: "myText")
27+
try text.insert(pos: 0, s: "Hello, World!")
28+
print(text.toString()) // "Hello, World!"
29+
```
30+
31+
### List
32+
33+
```swift
34+
let list = doc.getList(id: "myList")
35+
try list.insert(pos: 0, v: "first")
36+
try list.push(v: "second")
37+
```
38+
39+
### Map
40+
41+
```swift
42+
let map = doc.getMap(id: "myMap")
43+
try map.insert(key: "name", v: "Alice")
44+
try map.insert(key: "age", v: 30)
45+
```
46+
47+
### Tree
48+
49+
See <doc:WorkingWithTrees> for detailed information about working with tree structures.
50+
51+
## Syncing Documents
52+
53+
Loro documents can be synced using snapshots or incremental updates:
54+
55+
```swift
56+
let doc1 = LoroDoc()
57+
let doc2 = LoroDoc()
58+
59+
// Make changes to doc1
60+
let text1 = doc1.getText(id: "text")
61+
try text1.insert(pos: 0, s: "Hello")
62+
63+
// Export and import snapshot
64+
let snapshot = try doc1.export(mode: .snapshot)
65+
try doc2.import(bytes: snapshot)
66+
67+
// Now doc2 has the same content
68+
let text2 = doc2.getText(id: "text")
69+
print(text2.toString()) // "Hello"
70+
```
71+
72+
## Subscribing to Changes
73+
74+
You can subscribe to changes in a document:
75+
76+
```swift
77+
let subscription = doc.subscribeRoot { event in
78+
print("Document changed!")
79+
}
80+
81+
// Don't forget to detach when done
82+
subscription.detach()
83+
```

Sources/Loro/Loro.docc/Loro.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# ``Loro``
2+
3+
A Swift binding for Loro, a high-performance CRDT (Conflict-free Replicated Data Type) library.
4+
5+
## Overview
6+
7+
Loro is a powerful library for building collaborative applications. It provides several container types for different data structures that can be synced across multiple peers while automatically resolving conflicts.
8+
9+
## Topics
10+
11+
### Essentials
12+
13+
- <doc:GettingStarted>
14+
- <doc:WorkingWithText>
15+
- <doc:WorkingWithLists>
16+
- <doc:WorkingWithMaps>
17+
- <doc:WorkingWithTrees>
18+
- <doc:WorkingWithCounters>
19+
- <doc:VersioningAndSync>
20+
21+
### Document
22+
23+
- ``LoroDoc``
24+
25+
### Container Types
26+
27+
- ``LoroText``
28+
- ``LoroList``
29+
- ``LoroMap``
30+
- ``LoroTree``
31+
- ``LoroMovableList``
32+
- ``LoroCounter``
33+
34+
### Collaboration
35+
36+
- ``UndoManager``
37+
- ``Awareness``
38+
39+
### Versioning
40+
41+
- ``VersionVector``
42+
- ``Frontiers``

0 commit comments

Comments
 (0)