Skip to content

Commit

Permalink
Better mac build system
Browse files Browse the repository at this point in the history
  • Loading branch information
za-creature committed Jan 7, 2025
1 parent 15fe377 commit 93e60ad
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 180 deletions.
3 changes: 2 additions & 1 deletion bindings/swift/OuisyncLib/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/output
/OuisyncLibFFI.xcframework
/config.sh
.DS_Store
/.build
/Packages
Expand Down
6 changes: 2 additions & 4 deletions bindings/swift/OuisyncLib/Package.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// swift-tools-version: 5.9
import PackageDescription


let package = Package(
name: "OuisyncLib",
platforms: [.macOS(.v13), .iOS(.v16)],
Expand All @@ -25,7 +24,7 @@ let package = Package(
path: "Tests"),
// FIXME: move this to a separate package / framework
.binaryTarget(name: "OuisyncLibFFI",
path: "output/OuisyncLibFFI.xcframework"),
path: "OuisyncLibFFI.xcframework"),
.plugin(name: "FFIBuilder",
capability: .buildTool(),
path: "Plugins/Builder"),
Expand All @@ -34,8 +33,7 @@ let package = Package(
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"),
]
)
9 changes: 1 addition & 8 deletions bindings/swift/OuisyncLib/Plugins/Updater/updater.swift
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
47 changes: 21 additions & 26 deletions bindings/swift/OuisyncLib/Plugins/build.sh
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
#!/usr/bin/env zsh
# Command line tool which produces a `OuisyncLibFFI` framework for all configured llvm triples from
# OuisyncLib/config.sh
#
# This tool runs in a sandboxed process that can only write to a `output` folder and cannot access
# the network, so it relies on the `Updater` companion plugin to download the required dependencies
# before hand. Unfortunately, this does not work 100% of the time since both rust and especially
# cargo like to touch the lockfiles or the network for various reasons even when told not to.
#
# Called by the builder plugin which passes its own environment as well as the input, dependency
# and output paths. The builder checks that the dependency folder exists, but does not otherwise
# FIXME: validate that it contains all necessary dependencies as defined in Cargo.toml
#
# Hic sunt dracones! These might be of interest to anyone thinking they can do better than this mess:
# OuisyncLib/config.sh (currently generated in the ouisync-app repository)
#
# 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. Called by the builder plugin
# which passes both plugins' output paths as arguments. 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")/../../../../")
PACKAGE_HOME=$(realpath "$PROJECT_HOME/bindings/swift/OuisyncLib")
export CARGO_HOME="$1"
export CARGO_HOME=$(realpath "$1")
export PATH="$CARGO_HOME/bin:$PATH"
export RUSTUP_HOME="$CARGO_HOME/.rustup"
BUILD_OUTPUT="$2"
BUILD_OUTPUT=$(realpath "$2")

# 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
Expand All @@ -29,7 +22,7 @@ BUILD_OUTPUT="$2"
mkdir -p "$BUILD_OUTPUT/dummy"

# read config and prepare to build
source "$PACKAGE_HOME/config.sh"
source "$PROJECT_HOME/bindings/swift/OuisyncLib/config.sh"
if [ $SKIP ] && [ $SKIP -gt 0 ]; then
exit 0
fi
Expand All @@ -49,12 +42,12 @@ for TARGET in $LIST[@]; do TARGETS[$TARGET]="" done
# build configured targets
cd $PROJECT_HOME
for TARGET in ${(k)TARGETS}; do
"$CARGO_HOME/bin/cross" build \
cross build \
--frozen \
--package ouisync-ffi \
--target $TARGET \
--target-dir "$BUILD_OUTPUT" \
$FLAGS || exit 1
$FLAGS || fatal 1 "Unable to compile for $TARGET"
done

# generate include files
Expand All @@ -64,11 +57,7 @@ echo "module OuisyncLibFFI {
header \"bindings.h\"
export *
}" > "$INCLUDE/module.modulemap"
"$CARGO_HOME/bin/cbindgen" --lang C --crate ouisync-ffi > "$INCLUDE/bindings.h" || exit 2

# delete previous framework (possibly a stub) and replace with new one that contains the archive
# TODO: some symlinks would be lovely here instead, cargo already create two copies
rm -Rf $BUILD_OUTPUT/output/OuisyncLibFFI.xcframework
cbindgen --lang C --crate ouisync-ffi > "$INCLUDE/bindings.h" || fatal 2 "Unable to generate 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
Expand Down Expand Up @@ -96,11 +85,17 @@ for PLATFORM OUTPUTS in ${(kv)TREE}; do
else # at least two architectures; run lipo on all matches and link the output instead
LIBRARY="$BUILD_OUTPUT/$PLATFORM/libouisync_ffi.a"
mkdir -p "$(dirname "$LIBRARY")"
lipo -create $MATCHED[@] -output $LIBRARY || exit 3
lipo -create $MATCHED[@] -output $LIBRARY || fatal 3 "Unable to run lipo for ${MATCHED[@]}"
fi
PARAMS+=("-library" "$LIBRARY" "-headers" "$INCLUDE")
done
echo ${PARAMS[@]}

# TODO: skip xcodebuild and manually create symlinks instead (faster but Info.plist would be tricky)
rm -Rf "$BUILD_OUTPUT/temp.xcframework"
find "$BUILD_OUTPUT/OuisyncLibFFI.xcframework" -mindepth 1 -delete
xcodebuild \
-create-xcframework ${PARAMS[@]} \
-output "$BUILD_OUTPUT/output/OuisyncLibFFI.xcframework" || exit 4
-output "$BUILD_OUTPUT/temp.xcframework" || fatal 4 "Unable to build xcframework"
for FILE in $(ls "$BUILD_OUTPUT/temp.xcframework"); do
mv "$BUILD_OUTPUT/temp.xcframework/$FILE" "$BUILD_OUTPUT/OuisyncLibFFI.xcframework/$FILE"
done
51 changes: 20 additions & 31 deletions bindings/swift/OuisyncLib/Plugins/update.sh
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
#!/usr/bin/env zsh
# Command line tool which pulls all dependencies needed to build the rust core library.
#
# Assumes that `cargo` and `rustup` are installed and available in REAL_PATH and it is run with the
# two plugin output paths (update and build)
fatal() { echo "Error $@" >&2 && exit $1 }
PROJECT_HOME=$(realpath "$(dirname "$0")/../../../../")
PACKAGE_HOME=$(realpath "$PROJECT_HOME/bindings/swift/OuisyncLib")
export CARGO_HOME="$1"
export CARGO_HTTP_CHECK_REVOKE="false" # unclear why this fails, but it does
export RUSTUP_USE_CURL=1 # https://github.com/rust-lang/rustup/issues/1856

# download all possible toolchains: they only take up about 100MiB in total
mkdir -p .rustup
export CARGO_HOME=$(realpath "$1")
export PATH="$CARGO_HOME/bin:$PATH"
export RUSTUP_HOME="$CARGO_HOME/.rustup"
rustup default stable
rustup target install aarch64-apple-darwin aarch64-apple-ios aarch64-apple-ios-sim \
x86_64-apple-darwin x86_64-apple-ios

cd "$PROJECT_HOME"
cargo fetch --locked || exit 1 # this is currently only fixable by moving the plugin location
cargo install cbindgen cross || exit 2 # build.sh also needs `cbindgen` and `cross`

# as part of the updater, we also perform the xcode symlink hack: we replace the existing
# $PACKAGE_HOME/output folder (either stub checked out by git or symlink to a previous build) with
# a link to the $BUILD_OUTPUT/output folder which will eventually contain an actual framework
BUILD_OUTPUT="$2"
mkdir -p "$BUILD_OUTPUT"
cd "$BUILD_OUTPUT" > /dev/null
# if this is the first time we build at this location, generate a new stub library to keep xcode
# happy in case the build process fails later down the line
if ! [ -d "output/OuisyncLibFFI.xcframework" ]; then
"$PACKAGE_HOME/reset-output.sh"
# 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

# we can now replace the local stub (or prior link) with a link to the most recent build location
rm -rf "$PACKAGE_HOME/output"
ln -s "$BUILD_OUTPUT/output" "$PACKAGE_HOME/output"
# 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"

# unfortunately, we can't trigger a build from here because `build.sh` runs in a different sandbox
# 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"
11 changes: 0 additions & 11 deletions bindings/swift/OuisyncLib/config.sh

This file was deleted.

This file was deleted.

86 changes: 0 additions & 86 deletions bindings/swift/OuisyncLib/reset-output.sh

This file was deleted.

0 comments on commit 93e60ad

Please sign in to comment.