Skip to content

Commit 5461539

Browse files
Merge pull request #79 from torlando-tech/pr1-rns-core
Dual-backend 1/3 — RNS core + protocol seam
2 parents e65d410 + 8ced444 commit 5461539

44 files changed

Lines changed: 12473 additions & 215 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/tests.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ jobs:
2222
git clone https://github.com/torlando-tech/LXMF-swift.git ../LXMF-swift
2323
git clone https://github.com/torlando-tech/LXST-swift.git ../LXST-swift
2424
25+
# The Python-backend (`Columba`) scheme links Python.xcframework + the
26+
# bundled wheels; without these the build phase that copies them fails.
27+
# Fetch them before any xcodebuild step (pinned deps → reproducible).
28+
- name: Fetch Python framework + wheels
29+
run: |
30+
support/fetch-python.sh
31+
support/fetch-wheels.sh
32+
2533
- name: Select Xcode
2634
run: |
2735
XCODE=$(ls -d /Applications/Xcode_16*.app 2>/dev/null | sort -V | tail -1)
@@ -37,7 +45,7 @@ jobs:
3745
echo "device_id=$DEVICE_ID" >> "$GITHUB_OUTPUT"
3846
echo "Using simulator: $DEVICE_ID"
3947
40-
- name: Build
48+
- name: Build (Python backend — default)
4149
run: |
4250
xcodebuild build \
4351
-project Columba.xcodeproj \
@@ -50,6 +58,22 @@ jobs:
5058
DEVELOPMENT_TEAM="" \
5159
PROVISIONING_PROFILE_SPECIFIER=""
5260
61+
# Verify the native reticulum-swift/LXMF-swift backend variant also builds
62+
# (COLUMBA_BACKEND_SWIFT). The UI/AppServices are backend-agnostic, so this
63+
# guards against the Swift backend or its seam drifting out of compile.
64+
- name: Build (Swift-native backend)
65+
run: |
66+
xcodebuild build \
67+
-project Columba.xcodeproj \
68+
-scheme Columba-Swift \
69+
-sdk iphonesimulator \
70+
-destination "id=${{ steps.sim.outputs.device_id }}" \
71+
CODE_SIGN_IDENTITY=- \
72+
CODE_SIGNING_REQUIRED=NO \
73+
CODE_SIGNING_ALLOWED=YES \
74+
DEVELOPMENT_TEAM="" \
75+
PROVISIONING_PROFILE_SPECIFIER=""
76+
5377
- name: Build and run tests
5478
run: |
5579
xcodebuild test \

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ xcuserdata/
66
*.dSYM
77
timeline.xctimeline
88
playground.xcworkspace
9+
build/
910

1011
# Swift Package Manager
1112
.build/
@@ -27,3 +28,16 @@ package-lock.json
2728

2829
# Local Xcode signing overrides
2930
Config/LocalSigning.xcconfig
31+
32+
# Embedded Python (fetched by support/fetch-python.sh — too large for git)
33+
Frameworks/Python.xcframework/
34+
Frameworks/Python-3.13-iOS-support.*.tar.gz
35+
Frameworks/VERSIONS
36+
37+
# iOS Python wheels (fetched by support/fetch-wheels.sh)
38+
wheels-iphoneos/
39+
wheels-iphonesimulator/
40+
41+
# Python build artifacts
42+
__pycache__/
43+
*.pyc

ARCHITECTURE.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Columba-iOS Architecture
2+
3+
Target / module dependency graph for the iOS app. Mirrors the role of `docs/architecture.md` in the sibling Android repo (`columba/`).
4+
5+
Regenerate this file from the current `Package.swift` + `Columba.xcodeproj/project.pbxproj`:
6+
7+
```sh
8+
ruby support/generate-module-graph.rb
9+
```
10+
11+
The script reads:
12+
- pbxproj targets (e.g. `ColumbaApp`, `ColumbaNetworkExtension`, `PythonBridge`, `RNSBackendPy`) via the `xcodeproj` Ruby gem (already a dependency for `support/configure-xcodeproj.rb`).
13+
- SPM targets (e.g. `RNSAPI`, `LXSTSwift`, `COpus`, `CCodec2`, `SwiftBLEBridge`) via `swift package dump-package`.
14+
15+
It overwrites the Mermaid block between the start/end marker comments below the `## Target Graph` heading. Do not edit between the markers by hand — changes will be lost on next regen.
16+
17+
## Target Graph
18+
19+
<!-- module-graph-start -->
20+
```mermaid
21+
flowchart TD
22+
CCodec2["CCodec2"]
23+
COpus["COpus"]
24+
ColumbaApp["ColumbaApp"]
25+
ColumbaNetworkExtension["ColumbaNetworkExtension"]
26+
LXSTSwift["LXSTSwift"]
27+
MapLibre["MapLibre"]
28+
RNSAPI["RNSAPI"]
29+
SwiftBLEBridge["SwiftBLEBridge"]
30+
ColumbaApp --> LXSTSwift
31+
ColumbaApp --> MapLibre
32+
ColumbaApp --> RNSAPI
33+
ColumbaApp --> SwiftBLEBridge
34+
LXSTSwift --> CCodec2
35+
LXSTSwift --> COpus
36+
LXSTSwift --> RNSAPI
37+
SwiftBLEBridge --> RNSAPI
38+
classDef app fill:#1f6feb,stroke:#0d419d,color:#fff
39+
classDef extension fill:#8957e5,stroke:#553098,color:#fff
40+
classDef bridge fill:#f0883e,stroke:#9e4c0f,color:#fff
41+
classDef spm_lib fill:#3fb950,stroke:#0f7a2e,color:#fff
42+
classDef c_lib fill:#6e7681,stroke:#30363d,color:#fff
43+
class ColumbaApp app
44+
class LXSTSwift,MapLibre,RNSAPI,SwiftBLEBridge spm_lib
45+
class ColumbaNetworkExtension extension
46+
class CCodec2,COpus c_lib
47+
```
48+
<!-- module-graph-end -->

Columba.xcodeproj/project.pbxproj

Lines changed: 697 additions & 178 deletions
Large diffs are not rendered by default.

Columba.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1500"
4+
version = "1.7">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "TARG"
18+
BuildableName = "ColumbaApp.app"
19+
BlueprintName = "ColumbaApp"
20+
ReferencedContainer = "container:Columba.xcodeproj">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug-Swift"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES"
30+
shouldAutocreateTestPlan = "YES">
31+
<Testables>
32+
<TestableReference
33+
skipped = "NO">
34+
<BuildableReference
35+
BuildableIdentifier = "primary"
36+
BlueprintIdentifier = "TTARG"
37+
BuildableName = "ColumbaAppTests.xctest"
38+
BlueprintName = "ColumbaAppTests"
39+
ReferencedContainer = "container:Columba.xcodeproj">
40+
</BuildableReference>
41+
</TestableReference>
42+
</Testables>
43+
</TestAction>
44+
<LaunchAction
45+
buildConfiguration = "Debug-Swift"
46+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
48+
launchStyle = "0"
49+
useCustomWorkingDirectory = "NO"
50+
ignoresPersistentStateOnLaunch = "NO"
51+
debugDocumentVersioning = "YES"
52+
debugServiceExtension = "internal"
53+
allowLocationSimulation = "YES">
54+
<BuildableProductRunnable
55+
runnableDebuggingMode = "0">
56+
<BuildableReference
57+
BuildableIdentifier = "primary"
58+
BlueprintIdentifier = "TARG"
59+
BuildableName = "ColumbaApp.app"
60+
BlueprintName = "ColumbaApp"
61+
ReferencedContainer = "container:Columba.xcodeproj">
62+
</BuildableReference>
63+
</BuildableProductRunnable>
64+
</LaunchAction>
65+
<ProfileAction
66+
buildConfiguration = "Release-Swift"
67+
shouldUseLaunchSchemeArgsEnv = "YES"
68+
savedToolIdentifier = ""
69+
useCustomWorkingDirectory = "NO"
70+
debugDocumentVersioning = "YES">
71+
<BuildableProductRunnable
72+
runnableDebuggingMode = "0">
73+
<BuildableReference
74+
BuildableIdentifier = "primary"
75+
BlueprintIdentifier = "TARG"
76+
BuildableName = "ColumbaApp.app"
77+
BlueprintName = "ColumbaApp"
78+
ReferencedContainer = "container:Columba.xcodeproj">
79+
</BuildableReference>
80+
</BuildableProductRunnable>
81+
</ProfileAction>
82+
<AnalyzeAction
83+
buildConfiguration = "Debug-Swift">
84+
</AnalyzeAction>
85+
<ArchiveAction
86+
buildConfiguration = "Release-Swift"
87+
revealArchiveInOrganizer = "YES">
88+
</ArchiveAction>
89+
</Scheme>

Frameworks/.gitkeep

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Python.xcframework lives in this directory. Fetch it with `support/fetch-python.sh`.
2+
The xcframework, the VERSIONS marker, and the upstream tarball are all gitignored
3+
because the xcframework is ~110 MB. The fetch script pins a specific release tag
4+
for reproducibility — see support/fetch-python.sh.

Package.swift

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,68 @@
11
// swift-tools-version: 5.9
22
import PackageDescription
33

4+
// Module layout — the iOS app is Xcode-built (Columba.xcodeproj), with
5+
// configure-xcodeproj.rb pulling files in by path. This SwiftPM manifest
6+
// exists for two reasons:
7+
//
8+
// 1. The Xcode project references this manifest as a LOCAL package
9+
// (XCLocalSwiftPackageReference) so RNSAPI / SwiftBLEBridge get built by
10+
// SwiftPM rather than hand-written pbxproj entries.
11+
// 2. `swift build` (used by tooling + CI) can still typecheck the pure-Swift
12+
// libraries without the Python.xcframework bridging header.
13+
//
14+
// The LXST voice stack (LXSTSwift + the Opus/Codec2 codec C trees) is no longer
15+
// vendored here — it lives in the standalone, transport-agnostic LXST-swift
16+
// package (consumed via SwiftPM, wired to RNS through Columba's
17+
// PythonNetworkTransport). See `dependencies` below.
18+
//
19+
// Targets that DO require the bridging header (PythonBridge, RNSBackendPy,
20+
// ColumbaApp) live ONLY in the pbxproj — they're not declared here.
421
let package = Package(
522
name: "ColumbaApp",
623
platforms: [
724
.iOS(.v17),
825
.macOS(.v14)
926
],
1027
products: [
11-
.executable(
12-
name: "ColumbaApp",
13-
targets: ["ColumbaApp"]
14-
)
28+
.library(name: "RNSAPI", targets: ["RNSAPI"]),
29+
.library(name: "SwiftBLEBridge", targets: ["SwiftBLEBridge"]),
1530
],
1631
dependencies: [
17-
// SPM resolves these from GitHub on every fresh checkout. To work
18-
// against an in-progress local clone of any of these libraries
19-
// without committing a path-override, drop a per-machine
20-
// `.swiftpm/configuration/mirrors.json` mapping the URL to a local
21-
// directory — see README "Local development against unreleased
22-
// library changes" for the exact recipe.
23-
.package(url: "https://github.com/torlando-tech/LXMF-swift.git", from: "0.4.0"),
24-
.package(url: "https://github.com/torlando-tech/LXST-swift.git", from: "0.2.0"),
25-
.package(url: "https://github.com/torlando-tech/reticulum-swift.git", from: "0.3.0"),
26-
.package(url: "https://github.com/maplibre/maplibre-gl-native-distribution", from: "6.9.0"),
32+
// Transport-agnostic LXST voice library (owns the Opus/Codec2 codecs
33+
// and the NetworkTransport seam; no Reticulum dependency). Columba
34+
// provides the implementation via PythonNetworkTransport. Tracking the
35+
// branch until a release is tagged — same model as the RNS fork.
36+
.package(url: "https://github.com/torlando-tech/LXST-swift.git", branch: "feat/transport-agnostic"),
2737
],
2838
targets: [
29-
.executableTarget(
30-
name: "ColumbaApp",
31-
dependencies: [
32-
.product(name: "LXMFSwift", package: "LXMF-swift"),
33-
.product(name: "LXSTSwift", package: "LXST-swift"),
34-
// ReticulumSwift is imported directly by several view
35-
// models (e.g. NomadNetBrowserViewModel,
36-
// MessagingViewModel) — listed here as a direct product
37-
// dep so the version constraint on the package above is
38-
// actually exercised by SPM at resolution time.
39-
.product(name: "ReticulumSwift", package: "reticulum-swift"),
40-
.product(name: "MapLibre", package: "maplibre-gl-native-distribution"),
41-
],
42-
path: "Sources/ColumbaApp"
43-
)
39+
// ──────── RNSAPI: pure-interface protocol surface ────────
40+
.target(
41+
name: "RNSAPI",
42+
path: "Sources/RNSAPI",
43+
// libsqlite3 (system) backs LXMFDatabase's on-disk persistence.
44+
linkerSettings: [.linkedLibrary("sqlite3")]
45+
),
46+
47+
// ──────── SwiftBLEBridge: CoreBluetooth wrapper for ble-reticulum ──
48+
// Mirror of Columba Android's reticulum/ble module. Holds CBCentralManager
49+
// + CBPeripheralManager state and exposes a Swift API that the iOS BLE
50+
// driver (app/ble/ios_ble_driver.py) calls into. The Python ↔ Swift
51+
// callback invocation path lives separately in the pbxproj-only
52+
// `PythonBLECallbackBridge.swift` (which needs Python.h); SwiftBLEBridge
53+
// itself is pure CoreBluetooth so `swift build` compiles it cleanly.
54+
.target(
55+
name: "SwiftBLEBridge",
56+
dependencies: ["RNSAPI"],
57+
path: "Sources/SwiftBLEBridge"
58+
),
59+
// Pure-Swift unit tests for RNSAPI (msgpack, AppDataParser,
60+
// PropagationNodeInfo). Runs natively via `swift test` on macOS — no
61+
// simulator / Xcode test target needed (RNSAPI has no UIKit/Python deps).
62+
.testTarget(
63+
name: "RNSAPITests",
64+
dependencies: ["RNSAPI"],
65+
path: "Tests/RNSAPITests"
66+
),
4467
]
4568
)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#ifndef ColumbaPython_Bridging_Header_h
2+
#define ColumbaPython_Bridging_Header_h
3+
4+
#import <Python/Python.h>
5+
6+
// Swift cannot express `PyConfig_SetString(&config, &config.home, ...)` because of
7+
// overlapping-access exclusivity rules — both arguments alias into `config`.
8+
// These tiny inline shims pull the dual-mutation pattern down into C, which
9+
// has no such restriction. One shim per `PyConfig` field we touch.
10+
11+
static inline PyStatus ColumbaPy_PyConfig_SetHome(PyConfig *config, const wchar_t *home) {
12+
return PyConfig_SetString(config, &config->home, home);
13+
}
14+
15+
// Py_None is a macro in CPython's headers, which Swift cannot import. This
16+
// shim returns a fresh reference so the caller can pass it where a "new"
17+
// reference is expected (e.g. PyTuple_SetItem, which steals refs).
18+
static inline PyObject *ColumbaPy_None(void) {
19+
Py_INCREF(Py_None);
20+
return Py_None;
21+
}
22+
23+
// Py_True / Py_False are likewise macros. New refs so they can be packed into
24+
// tuples + lists where slot setters steal the ref.
25+
static inline PyObject *ColumbaPy_True(void) {
26+
Py_INCREF(Py_True);
27+
return Py_True;
28+
}
29+
30+
static inline PyObject *ColumbaPy_False(void) {
31+
Py_INCREF(Py_False);
32+
return Py_False;
33+
}
34+
35+
#endif

0 commit comments

Comments
 (0)