commit e15e04f32fcdfe1bb055b47b2dc015ea7ba9d12d
parent a741b7cab63983c0e58ebc99500bb3c81976c1c6
Author: triesap <tyson@radroots.org>
Date: Mon, 16 Feb 2026 00:40:04 +0000
build: replace ios ffi wrapper with makefile pipeline
Diffstat:
13 files changed, 184 insertions(+), 107 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,6 +1,6 @@
-WRAPPER_ROOT := RadrootsCore
+FFI_ROOT := RadrootsFFI
-.PHONY: all clean build generate package bindings
+.PHONY: all clean distclean sync-source build generate package install print-config
-all clean build generate package bindings:
- $(MAKE) -C $(WRAPPER_ROOT) $@
+all clean distclean sync-source build generate package install print-config:
+ $(MAKE) -C $(FFI_ROOT) $@
diff --git a/RadrootsCore/.gitignore b/RadrootsCore/.gitignore
@@ -1 +0,0 @@
-target/
diff --git a/RadrootsCore/Cargo.lock b/RadrootsCore/Cargo.lock
@@ -1,7 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 4
-
-[[package]]
-name = "radrootscore-ios"
-version = "0.1.0"
diff --git a/RadrootsCore/Cargo.toml b/RadrootsCore/Cargo.toml
@@ -1,14 +0,0 @@
-[package]
-name = "radrootscore-ios"
-version = "0.1.0"
-edition = "2024"
-authors = ["Radroots Authors"]
-rust-version = "1.92.0"
-license = "AGPL-3.0"
-description = "radroots ios rust wrapper manifest"
-
-[workspace]
-resolver = "2"
-
-[workspace.dependencies]
-radroots-app-ffi-swift = { path = "../../../../../../foundation/oss/rs/crates/app-ffi-swift" }
diff --git a/RadrootsCore/Makefile b/RadrootsCore/Makefile
@@ -1,60 +0,0 @@
-CRATE := radroots-app-ffi-swift
-CRATES_ROOT := ../../../../../../foundation/oss/rs/crates
-FFI_MANIFEST := $(CRATES_ROOT)/app-ffi-swift/Cargo.toml
-RUST_TARGET_DIR := target
-OUTDIR := $(RUST_TARGET_DIR)/xcframework
-CARGO_FFI := CARGO_TARGET_DIR=$(RUST_TARGET_DIR) cargo
-
-FRAMEWORK_DEST := ../RadrootsKit/Artifacts
-GENERATED_SWIFT := ../RadrootsKit/Sources/RadrootsKit/Generated
-CONFIG_PATH := $(CRATES_ROOT)/app-ffi-swift/uniffi.toml
-
-LIB_DEV := $(RUST_TARGET_DIR)/aarch64-apple-ios/release/libradroots_app_ffi_swift.a
-LIB_SIM_ARM64 := $(RUST_TARGET_DIR)/aarch64-apple-ios-sim/release/libradroots_app_ffi_swift.a
-HOST_DYLIB := $(RUST_TARGET_DIR)/release/libradroots_app_ffi_swift.dylib
-
-HEADERS_DIR := $(OUTDIR)/headers
-
-.PHONY: all clean build generate package bindings
-
-all: clean build generate package bindings
- @echo "done."
- @echo " - xcframework: $(FRAMEWORK_DEST)/RadrootsFFI.xcframework"
- @echo " - swift bindings: $(GENERATED_SWIFT)/*.swift"
-
-clean:
- @echo "cleaning output dirs..."
- rm -rf $(OUTDIR) $(FRAMEWORK_DEST) $(GENERATED_SWIFT)
- mkdir -p $(OUTDIR) $(FRAMEWORK_DEST) $(GENERATED_SWIFT)
-
-build:
- @echo "building $(CRATE) for ios device + simulator..."
- $(CARGO_FFI) build --manifest-path $(FFI_MANIFEST) --release --target aarch64-apple-ios
- $(CARGO_FFI) build --manifest-path $(FFI_MANIFEST) --release --target aarch64-apple-ios-sim
-
- @echo "building host cdylib for uniffi metadata..."
- $(CARGO_FFI) build --manifest-path $(FFI_MANIFEST) --release
-
-generate:
- @echo "generating swift bindings with uniffi..."
- $(CARGO_FFI) run --manifest-path $(FFI_MANIFEST) --bin uniffi-bindgen -- \
- generate --library $(HOST_DYLIB) \
- --language swift \
- --out-dir $(OUTDIR)/generated \
- --config $(CONFIG_PATH)
-
- @echo "preparing headers..."
- mkdir -p $(HEADERS_DIR)
- cp $(OUTDIR)/generated/RadrootsFFI.h $(HEADERS_DIR)/
- cp $(OUTDIR)/generated/RadrootsFFI.modulemap $(HEADERS_DIR)/module.modulemap
-
-package:
- @echo "packaging RadrootsFFI.xcframework..."
- xcodebuild -create-xcframework \
- -library $(LIB_DEV) -headers $(HEADERS_DIR) \
- -library $(LIB_SIM_ARM64) -headers $(HEADERS_DIR) \
- -output $(FRAMEWORK_DEST)/RadrootsFFI.xcframework
-
-bindings:
- @echo "copying swift bindings into RadrootsKit..."
- cp $(OUTDIR)/generated/*.swift $(GENERATED_SWIFT)/
diff --git a/RadrootsCore/README.md b/RadrootsCore/README.md
@@ -1,20 +0,0 @@
-# RadrootsCore
-
-`ios/RadrootsCore` is the iOS Rust wrapper for the shared Swift FFI crate.
-
-## Source of truth
-- `../../../../../../foundation/oss/rs/crates/app-ffi-swift` (built via `--manifest-path`)
-- the transitive `radroots-*` crate graph resolves through that vendor workspace, where those crates use `workspace = true`
-
-## Dependency pattern
-- Keep `radroots-app-ffi-swift` in `[workspace.dependencies]` with a monorepo-relative `path`.
-- Switch this to a crates.io version later without changing the make/build flow.
-- Do not link `radroots-app-ffi-swift` as a Rust `[dependencies]` crate in this wrapper.
-
-## Build flow
-Run `make -C ios` (or `make -C ios/RadrootsCore`) to:
-- build from `../../../../../../foundation/oss/rs/crates/app-ffi-swift/Cargo.toml`
-- write Rust build artifacts under `ios/RadrootsCore/target`
-- generate UniFFI Swift bindings
-- package `RadrootsFFI.xcframework` into `ios/RadrootsKit/Artifacts`
-- copy generated Swift files into `ios/RadrootsKit/Sources/RadrootsKit/Generated`
diff --git a/RadrootsCore/src/lib.rs b/RadrootsCore/src/lib.rs
@@ -1 +0,0 @@
-#![forbid(unsafe_code)]
diff --git a/RadrootsFFI/.gitignore b/RadrootsFFI/.gitignore
@@ -0,0 +1,2 @@
+.build/
+target/
diff --git a/RadrootsFFI/Config/ffi-build.env b/RadrootsFFI/Config/ffi-build.env
@@ -0,0 +1,3 @@
+# Optional overrides for local development.
+# SOURCE_MODE=local
+# LOCAL_FFI_MANIFEST=/absolute/path/to/app-ffi-swift/Cargo.toml
diff --git a/RadrootsFFI/Makefile b/RadrootsFFI/Makefile
@@ -0,0 +1,139 @@
+SHELL := /bin/bash
+.SHELLFLAGS := -eu -o pipefail -c
+
+-include source.lock
+-include Config/ffi-build.env
+
+SOURCE_MODE ?= git
+RADROOTS_CRATES_GIT_URL ?= https://github.com/radrootslabs/crates.git
+RADROOTS_CRATES_GIT_REV ?= 766c63c07ac7497d75a71aaab010fbc08ee934bc
+RADROOTS_APP_FFI_CRATE_VERSION ?= 0.1.0
+LOCAL_FFI_MANIFEST ?=
+
+IOS_ROOT := $(abspath $(CURDIR)/..)
+KIT_ROOT := $(IOS_ROOT)/RadrootsKit
+ARTIFACTS_DIR := $(KIT_ROOT)/Artifacts
+GENERATED_SWIFT_DIR := $(KIT_ROOT)/Sources/RadrootsKit/Generated
+
+BUILD_ROOT := $(CURDIR)/.build
+SOURCE_ROOT := $(BUILD_ROOT)/source
+TARGET_DIR := $(BUILD_ROOT)/target
+OUT_DIR := $(BUILD_ROOT)/out
+GENERATED_DIR := $(OUT_DIR)/generated
+HEADERS_DIR := $(OUT_DIR)/headers
+
+CRATE_NAME := radroots-app-ffi-swift
+FFI_OUTPUT_NAME := RadrootsFFI
+LIB_STEM := libradroots_app_ffi_swift
+
+GIT_CRATES_DIR := $(SOURCE_ROOT)/radroots-crates
+CRATE_ARCHIVE := $(SOURCE_ROOT)/$(CRATE_NAME)-$(RADROOTS_APP_FFI_CRATE_VERSION).crate
+CRATE_EXTRACT_DIR := $(SOURCE_ROOT)/$(CRATE_NAME)-$(RADROOTS_APP_FFI_CRATE_VERSION)
+
+ifeq ($(SOURCE_MODE),git)
+FFI_MANIFEST := $(GIT_CRATES_DIR)/app-ffi-swift/Cargo.toml
+UNIFFI_CONFIG := $(GIT_CRATES_DIR)/app-ffi-swift/uniffi.toml
+SOURCE_TARGET := sync-source-git
+else ifeq ($(SOURCE_MODE),crates)
+FFI_MANIFEST := $(CRATE_EXTRACT_DIR)/Cargo.toml
+UNIFFI_CONFIG := $(CRATE_EXTRACT_DIR)/uniffi.toml
+SOURCE_TARGET := sync-source-crates
+else ifeq ($(SOURCE_MODE),local)
+FFI_MANIFEST := $(LOCAL_FFI_MANIFEST)
+UNIFFI_CONFIG := $(dir $(LOCAL_FFI_MANIFEST))uniffi.toml
+SOURCE_TARGET := sync-source-local
+else
+$(error SOURCE_MODE must be one of: git, crates, local)
+endif
+
+FFI_CRATE_DIR := $(dir $(FFI_MANIFEST))
+
+CARGO := CARGO_TARGET_DIR=$(TARGET_DIR) cargo
+
+LIB_DEVICE := $(TARGET_DIR)/aarch64-apple-ios/release/$(LIB_STEM).a
+LIB_SIMULATOR := $(TARGET_DIR)/aarch64-apple-ios-sim/release/$(LIB_STEM).a
+LIB_HOST := $(TARGET_DIR)/release/$(LIB_STEM).dylib
+
+.PHONY: all clean distclean print-config sync-source sync-source-git sync-source-crates sync-source-local ensure-toolchain build generate package install
+
+all: sync-source build generate package install
+ @echo "done"
+ @echo " - source mode: $(SOURCE_MODE)"
+ @echo " - xcframework: $(ARTIFACTS_DIR)/$(FFI_OUTPUT_NAME).xcframework"
+ @echo " - swift bindings: $(GENERATED_SWIFT_DIR)"
+
+print-config:
+ @echo "SOURCE_MODE=$(SOURCE_MODE)"
+ @echo "FFI_MANIFEST=$(FFI_MANIFEST)"
+ @echo "UNIFFI_CONFIG=$(UNIFFI_CONFIG)"
+ @echo "ARTIFACTS_DIR=$(ARTIFACTS_DIR)"
+ @echo "GENERATED_SWIFT_DIR=$(GENERATED_SWIFT_DIR)"
+
+clean:
+ rm -rf $(TARGET_DIR) $(OUT_DIR) $(ARTIFACTS_DIR)/$(FFI_OUTPUT_NAME).xcframework
+ if [ -d $(GENERATED_SWIFT_DIR) ]; then \
+ find $(GENERATED_SWIFT_DIR) -maxdepth 1 -type f -name '*.swift' -delete; \
+ fi
+
+distclean: clean
+ rm -rf $(SOURCE_ROOT)
+
+sync-source: $(SOURCE_TARGET)
+
+sync-source-git:
+ mkdir -p $(SOURCE_ROOT)
+ if [ ! -d $(GIT_CRATES_DIR)/.git ]; then \
+ git clone --filter=blob:none $(RADROOTS_CRATES_GIT_URL) $(GIT_CRATES_DIR); \
+ fi
+ git -C $(GIT_CRATES_DIR) fetch --tags --force origin
+ git -C $(GIT_CRATES_DIR) checkout --detach $(RADROOTS_CRATES_GIT_REV)
+ test -f $(FFI_MANIFEST)
+
+sync-source-crates:
+ mkdir -p $(SOURCE_ROOT)
+ rm -rf $(CRATE_EXTRACT_DIR)
+ curl -fLsS https://crates.io/api/v1/crates/$(CRATE_NAME)/$(RADROOTS_APP_FFI_CRATE_VERSION)/download -o $(CRATE_ARCHIVE)
+ tar -xzf $(CRATE_ARCHIVE) -C $(SOURCE_ROOT)
+ test -f $(FFI_MANIFEST)
+
+sync-source-local:
+ if [ -z "$(LOCAL_FFI_MANIFEST)" ]; then \
+ echo "LOCAL_FFI_MANIFEST is required when SOURCE_MODE=local"; \
+ exit 1; \
+ fi
+ test -f $(LOCAL_FFI_MANIFEST)
+ test -f $(UNIFFI_CONFIG)
+
+ensure-toolchain:
+ rustup target add aarch64-apple-ios aarch64-apple-ios-sim
+
+build: ensure-toolchain
+ mkdir -p $(TARGET_DIR) $(OUT_DIR)
+ $(CARGO) build --manifest-path $(FFI_MANIFEST) --release --target aarch64-apple-ios
+ $(CARGO) build --manifest-path $(FFI_MANIFEST) --release --target aarch64-apple-ios-sim
+ $(CARGO) build --manifest-path $(FFI_MANIFEST) --release
+
+generate:
+ mkdir -p $(GENERATED_DIR) $(HEADERS_DIR)
+ cd $(FFI_CRATE_DIR) && CARGO_TARGET_DIR=$(TARGET_DIR) cargo run --manifest-path Cargo.toml --bin uniffi-bindgen -- \
+ generate --library $(LIB_HOST) \
+ --language swift \
+ --out-dir $(GENERATED_DIR) \
+ --config $(UNIFFI_CONFIG)
+ cp $(GENERATED_DIR)/$(FFI_OUTPUT_NAME).h $(HEADERS_DIR)/
+ cp $(GENERATED_DIR)/$(FFI_OUTPUT_NAME).modulemap $(HEADERS_DIR)/module.modulemap
+
+package:
+ mkdir -p $(ARTIFACTS_DIR)
+ rm -rf $(ARTIFACTS_DIR)/$(FFI_OUTPUT_NAME).xcframework
+ xcodebuild -create-xcframework \
+ -library $(LIB_DEVICE) -headers $(HEADERS_DIR) \
+ -library $(LIB_SIMULATOR) -headers $(HEADERS_DIR) \
+ -output $(ARTIFACTS_DIR)/$(FFI_OUTPUT_NAME).xcframework
+
+install:
+ mkdir -p $(GENERATED_SWIFT_DIR)
+ if [ -d $(GENERATED_SWIFT_DIR) ]; then \
+ find $(GENERATED_SWIFT_DIR) -maxdepth 1 -type f -name '*.swift' -delete; \
+ fi
+ cp $(GENERATED_DIR)/*.swift $(GENERATED_SWIFT_DIR)/
diff --git a/RadrootsFFI/README.md b/RadrootsFFI/README.md
@@ -0,0 +1,32 @@
+# RadrootsFFI
+
+`ios/RadrootsFFI` is the source resolver and build workspace for the Rust FFI
+artifact consumed by `ios/RadrootsKit`.
+
+## Goals
+- keep the iOS project openable and buildable in Xcode for OSS developers
+- keep `radroots-app-ffi-swift` reusable for other Apple clients
+- support three source modes from one Makefile: `git`, `crates`, and `local`
+
+## Quick start
+- build everything: `make -C ios all`
+- print current config: `make -C ios print-config`
+- rebuild from scratch: `make -C ios distclean all`
+
+## Source modes
+- `SOURCE_MODE=git` (default)
+ - clones `RADROOTS_CRATES_GIT_URL` at `RADROOTS_CRATES_GIT_REV`
+ - builds `app-ffi-swift` from the checked out workspace
+- `SOURCE_MODE=crates`
+ - downloads `radroots-app-ffi-swift` from crates.io by version
+- `SOURCE_MODE=local`
+ - requires `LOCAL_FFI_MANIFEST=/absolute/path/to/app-ffi-swift/Cargo.toml`
+
+## Configuration
+Configuration is read from:
+- `RadrootsFFI/source.lock` for pinned defaults
+- `RadrootsFFI/Config/ffi-build.env` for optional local overrides
+
+## Outputs
+- xcframework: `ios/RadrootsKit/Artifacts/RadrootsFFI.xcframework`
+- generated swift bindings: `ios/RadrootsKit/Sources/RadrootsKit/Generated`
diff --git a/RadrootsCore/rust-toolchain.toml b/RadrootsFFI/rust-toolchain.toml
diff --git a/RadrootsFFI/source.lock b/RadrootsFFI/source.lock
@@ -0,0 +1,4 @@
+SOURCE_MODE=git
+RADROOTS_CRATES_GIT_URL=https://github.com/radrootslabs/crates.git
+RADROOTS_CRATES_GIT_REV=766c63c07ac7497d75a71aaab010fbc08ee934bc
+RADROOTS_APP_FFI_CRATE_VERSION=0.1.0