Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
15fe377
Move FFI callback to a global
za-creature Jan 6, 2025
93e60ad
Better mac build system
za-creature Jan 7, 2025
50044ab
Merge branch 'service' into darwin
za-creature Jan 14, 2025
a0be495
Expose LogLevel via FFI
za-creature Jan 14, 2025
70c7380
Move logInit to before Server.start
za-creature Jan 14, 2025
900150d
Remove old swift bindings
za-creature Jan 14, 2025
58d68cc
Add ouisync-service ffi bindings
za-creature Jan 14, 2025
21c95e4
Set explicit enum values
za-creature Jan 15, 2025
147958a
More explicit enum values
za-creature Jan 15, 2025
a3de4e7
Manually add missing enums
za-creature Jan 15, 2025
9ba2434
Remove no longer used channel errors
za-creature Jan 16, 2025
a181f85
Type-safe bindings
za-creature Jan 17, 2025
2e17b34
`swift build` compatibility
za-creature Jan 21, 2025
eee0b1d
Add trivial tests
za-creature Jan 21, 2025
7987aa4
Merge branch 'service' into darwin
za-creature Jan 21, 2025
1ec34c3
Add tests
za-creature Jan 24, 2025
e3e02cd
Merge branch 'develop' into darwin
za-creature Jan 24, 2025
3becae2
Finish tests
za-creature Jan 24, 2025
ee38525
Remove unsafe swift code
za-creature Jan 28, 2025
ba0d3fa
Include version hash in exported EntryType (now called StatEntry)
za-creature Jan 28, 2025
e0dca71
Add `root/target` to shared rust artifact pool on darwin
za-creature Jan 30, 2025
b4bddb6
Remove superflous logInit call
za-creature Jan 30, 2025
93ae842
Merge branch 'develop' into darwin
za-creature Feb 2, 2025
c1580d5
fix `cargo test` on darwin
za-creature Jan 30, 2025
69d332e
Readability
za-creature Jan 31, 2025
7fe9fc0
Update gradle
za-creature Feb 1, 2025
32448a5
Rename `listenerAddrs` property to mirror upstream
za-creature Feb 2, 2025
ea65398
Disable cli tests on macos without breaking other platforms
za-creature Feb 2, 2025
6aaa2ee
Export DirectoryEntry struct
za-creature Feb 3, 2025
77e9620
WIP: last commit
za-creature Apr 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bindings/dart/lib/ouisync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Session {
// that one instead. If we do spawn, we are responsible for logging
if (startServer) {
try {
logInit(callback: logger, tag: 'Server');
server = await Server.start(
configPath: configPath,
debugLabel: debugLabel,
Expand Down
9 changes: 9 additions & 0 deletions bindings/swift/Ouisync/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/OuisyncService.xcframework
/config.sh
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm
.netrc
14 changes: 14 additions & 0 deletions bindings/swift/Ouisync/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,41 +1,40 @@
// swift-tools-version: 5.9
import PackageDescription


let package = Package(
name: "OuisyncLib",
name: "Ouisync",
platforms: [.macOS(.v13), .iOS(.v16)],
products: [
.library(name: "OuisyncLib",
.library(name: "Ouisync",
type: .static,
targets: ["OuisyncLib"]),
targets: ["Ouisync"]),
],
dependencies: [
.package(url: "https://github.com/a2/MessagePack.swift.git", from: "4.0.0"),
],
targets: [
.target(name: "OuisyncLib",
.target(name: "Ouisync",
dependencies: [.product(name: "MessagePack",
package: "MessagePack.swift"),
"FFIBuilder",
"OuisyncLibFFI"],
path: "Sources"),
.testTarget(name: "OuisyncLibTests",
dependencies: ["OuisyncLib"],
path: "Tests"),
// FIXME: move this to a separate package / framework
.binaryTarget(name: "OuisyncLibFFI",
path: "output/OuisyncLibFFI.xcframework"),
.plugin(name: "FFIBuilder",
"CargoBuild",
"OuisyncService"],
path: "Sources",
linkerSettings: [.linkedFramework("SystemConfiguration")]),
.testTarget(name: "OuisyncTests",
dependencies: ["Ouisync"],
path: "Tests",
linkerSettings: [.linkedFramework("SystemConfiguration")]),
.binaryTarget(name: "OuisyncService",
path: "OuisyncService.xcframework"),
.plugin(name: "CargoBuild",
capability: .buildTool(),
path: "Plugins/Builder"),
.plugin(name: "Update rust dependencies",
capability: .command(intent: .custom(verb: "cargo-fetch",
description: "Update rust dependencies"),
permissions: [
.allowNetworkConnections(scope: .all(),
reason: "Downloads dependencies defined by Cargo.toml"),
.writeToPackageDirectory(reason: "These are not the droids you are looking for")]),
reason: "Downloads dependencies defined by Cargo.toml")]),
path: "Plugins/Updater"),
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,9 @@ import PackagePlugin
@main struct Builder: BuildToolPlugin {
func createBuildCommands(context: PackagePlugin.PluginContext,
target: PackagePlugin.Target) async throws -> [PackagePlugin.Command] {
let build = context.pluginWorkDirectory

// FIXME: this path is very unstable; we might need to search the tree instead
let update = build
.removingLastComponent() // FFIBuilder
.removingLastComponent() // OuisyncLibFFI
.removingLastComponent() // ouisync.output
.appending("Update rust dependencies.output")

guard FileManager.default.fileExists(atPath: update.string) else {
Diagnostics.error("Please run `Update rust dependencies` on the OuisyncLib package")
fatalError("Unable to build LibOuisyncFFI.xcframework")
}

return [.prebuildCommand(displayName: "Build OuisyncLibFFI.xcframework",
executable: context.package.directory.appending(["Plugins", "build.sh"]),
arguments: [update.string, build.string],
outputFilesDirectory: build.appending("dummy"))]
[.prebuildCommand(displayName: "Build OuisyncService.xcframework",
executable: context.package.directory.appending(["Plugins", "build.sh"]),
arguments: [context.pluginWorkDirectory.string],
outputFilesDirectory: context.pluginWorkDirectory.appending("dummy"))]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,11 @@ import PackagePlugin

func performCommand(context: PackagePlugin.PluginContext,
arguments: [String] = []) async throws {
let update = context.pluginWorkDirectory

// FIXME: this path is very unstable; we might need to search the tree instead
let build = update
.removingLastComponent()
.appending(["\(context.package.id).output", "OuisyncLib", "FFIBuilder"])

let task = Process()
let exe = context.package.directory.appending(["Plugins", "update.sh"]).string
task.standardInput = nil
task.executableURL = URL(fileURLWithPath: exe)
task.arguments = [update.string, build.string]
task.arguments = [context.pluginWorkDirectory.string]
do { try task.run() } catch { panic("Unable to start \(exe): \(error)") }
task.waitUntilExit()

Expand Down
116 changes: 116 additions & 0 deletions bindings/swift/Ouisync/Plugins/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env zsh
# Command line tool which produces a OuisyncService xcframework for all llvm
# triples from config.sh (defaults to macos x86_64, macos arm64 and ios arm64)
#
# This tool runs in a sandboxed process that cannot access the network, so it
# relies on the updater companion plugin to download the required dependencies
# ahead of time. Hic sunt dracones! These may be of interest:
# [1] https://forums.developer.apple.com/forums/thread/666335
# [2] https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md#build-tool-target-dependencies
# [3] https://www.amyspark.me/blog/posts/2024/01/10/stripping-rust-libraries.html
fatal() { echo "Error $@" && exit $1 }
PROJECT_HOME=$(realpath "$(dirname "$0")/../../../../")
export MACOSX_DEPLOYMENT_TARGET=13.0
BUILD_OUTPUT=$(realpath "$1")

# find the rust toolchain installed by `update.sh` by searching for swift's
# package plugin root directory (called "plugins") in the build output path
cd "$BUILD_OUTPUT";
while : ; do
PWD=$(basename "$(pwd)")
test "$PWD" != / || fatal 1 "Unable to find swift package plugin root"
test "$PWD" != "plugins" || break
cd ..
done
CROSS=$(find . -path "**/bin/cross" -print -quit)
test -f $CROSS || fatal 2 "Please run `Update rust dependencies` on the Ouisync package"
export CARGO_HOME=$(realpath "$(dirname "$(dirname "$CROSS")")")
export PATH="$CARGO_HOME/bin:$PATH"
export RUSTUP_HOME="$CARGO_HOME/.rustup"

# cargo builds some things that confuse xcode such as fingerprints and depfiles
# which cannot be (easily) disabled; additionally, xcode does pick up the
# xcframework and reports it as a duplicate target if present in the output
# folder, so we tell xcode that all our output is in this empty `dummy` folder
mkdir -p "$BUILD_OUTPUT/dummy"

# read config and prepare to build
source "$PROJECT_HOME/bindings/swift/Ouisync/config.sh" || fatal 1 "Unable to find config file"
if [ $SKIP ] && [ $SKIP -gt 0 ]; then
exit 0
fi
if [ $DEBUG ] && [ $DEBUG -gt 0 ]; then
CONFIGURATION="debug"
FLAGS=""
else
CONFIGURATION="release"
FLAGS="--release"
fi

# convert targets to dictionary
LIST=($TARGETS[@])
declare -A TARGETS
for TARGET in $LIST[@]; do TARGETS[$TARGET]="" done

# build configured targets
cd "$PROJECT_HOME"
for TARGET in ${(k)TARGETS}; do
cross build \
--frozen \
--package ouisync-service \
--target $TARGET \
--target-dir "$BUILD_OUTPUT" \
$FLAGS || fatal 3 "Unable to compile for $TARGET"
done

# generate include files
INCLUDE="$BUILD_OUTPUT/include"
mkdir -p "$INCLUDE"
echo "module OuisyncService {
header \"bindings.h\"
export *
}" > "$INCLUDE/module.modulemap"
cbindgen --lang C --crate ouisync-service > "$INCLUDE/bindings.h" || fatal 4 "Unable to generate bindings.h"
# hack for autoimporting enums https://stackoverflow.com/questions/60559599/swift-c-api-enum-in-swift
perl -i -p0e 's/enum\s+(\w+)([^}]+});\ntypedef (\w+) \1/typedef enum __attribute__\(\(enum_extensibility\(open\)\)\) : \3\2 \1/sg' "$INCLUDE/bindings.h"

# xcodebuild refuses multiple architectures per platform, instead expecting fat libraries when the
# destination operating system supports multiple architectures; apple also explicitly rejects any
# submissions that link to mixed-platform libraries so `lipo` usage is reduced to an if and only if
# scenario; since our input is a list of llvm triples which do not follow rigid naming conventions,
# we first have to statically define the platform->arch tree and then run some annoying diffs on it
PARAMS=()
declare -A TREE
TREE=(
macos "aarch64-apple-darwin x86_64-apple-darwin"
ios "aarch64-apple-ios"
simulator "aarch64-apple-ios-sim x86_64-apple-ios"
)
for PLATFORM OUTPUTS in ${(kv)TREE}; do
MATCHED=() # list of libraries compiled for this platform
for TARGET in ${=OUTPUTS}; do
if [[ -v TARGETS[$TARGET] ]]; then
MATCHED+="$BUILD_OUTPUT/$TARGET/$CONFIGURATION/libouisync_service.a"
fi
done
if [ $#MATCHED -eq 0 ]; then # platform not enabled
continue
elif [ $#MATCHED -eq 1 ]; then # single architecture: skip lipo and link directly
LIBRARY=$MATCHED
else # at least two architectures; run lipo on all matches and link the output instead
LIBRARY="$BUILD_OUTPUT/$PLATFORM/libouisync_service.a"
mkdir -p "$(dirname "$LIBRARY")"
lipo -create $MATCHED[@] -output $LIBRARY || fatal 5 "Unable to run lipo for ${MATCHED[@]}"
fi
PARAMS+=("-library" "$LIBRARY" "-headers" "$INCLUDE")
done

# TODO: skip xcodebuild and manually create symlinks instead (faster but Info.plist would be tricky)
rm -Rf "$BUILD_OUTPUT/temp.xcframework"
find "$BUILD_OUTPUT/OuisyncService.xcframework" -mindepth 1 -delete
xcodebuild \
-create-xcframework ${PARAMS[@]} \
-output "$BUILD_OUTPUT/temp.xcframework" || fatal 6 "Unable to build xcframework"
for FILE in $(ls "$BUILD_OUTPUT/temp.xcframework"); do
mv "$BUILD_OUTPUT/temp.xcframework/$FILE" "$BUILD_OUTPUT/OuisyncService.xcframework/$FILE"
done
28 changes: 28 additions & 0 deletions bindings/swift/Ouisync/Plugins/update.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env zsh
# Command line tool which pulls all dependencies needed to build the rust core library.
fatal() { echo "Error $@" >&2 && exit $1 }
PROJECT_HOME=$(realpath "$(dirname "$0")/../../../../")
export CARGO_HOME=$(realpath "$1")
export PATH="$CARGO_HOME/bin:$PATH"
export RUSTUP_HOME="$CARGO_HOME/.rustup"

# install rust or update to latest version
export RUSTUP_USE_CURL=1 # https://github.com/rust-lang/rustup/issues/1856
if [ -f "$CARGO_HOME/bin/rustup" ]; then
rustup update || fatal 1 "Unable to update rust"
else
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path \
|| fatal 1 "Unable to install rust"
fi

# also install all possible toolchains since they only take up about 100MiB in total
export CARGO_HTTP_CHECK_REVOKE="false" # unclear it fails without this, but it does
rustup target install aarch64-apple-darwin aarch64-apple-ios \
aarch64-apple-ios-sim x86_64-apple-darwin x86_64-apple-ios || fatal 2 "Unable to install rust via rustup"

# build.sh needs `cbindgen` and `cross` to build as a multiplatform framework
cargo install cbindgen cross || fatal 3 "Unable to install header generator or cross compiler"

# fetch all up to date package dependencies for the next build (which must run offline)
cd "$PROJECT_HOME"
cargo fetch --locked || fatal 4 "Unable to fetch library dependencies"
Loading