diff --git a/Cargo.lock b/Cargo.lock index b1aa4886c..c2cda5bc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,7 +219,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -241,7 +241,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -252,7 +252,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -684,7 +684,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -964,7 +964,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1037,7 +1037,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1404,7 +1404,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -1962,7 +1962,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2165,9 +2165,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a73e9fe3c49d7afb2ace819fa181a287ce54a0983eda4e0eb05c22f82ffe534" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "jni" @@ -2638,7 +2638,7 @@ checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2697,7 +2697,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -2810,7 +2810,7 @@ version = "0.8.10" dependencies = [ "clap 4.5.21", "heck 0.5.0", - "syn 2.0.87", + "syn 2.0.89", "thiserror 1.0.69", ] @@ -3181,7 +3181,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -3307,9 +3307,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -3551,7 +3551,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -3959,7 +3959,7 @@ checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4211,7 +4211,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4232,7 +4232,7 @@ dependencies = [ "sha2", "sqlx-core", "sqlx-sqlite", - "syn 2.0.87", + "syn 2.0.89", "tempfile", "tokio", "url", @@ -4320,7 +4320,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta-derive", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4331,7 +4331,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4374,7 +4374,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4419,9 +4419,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -4430,9 +4430,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -4445,7 +4445,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4500,7 +4500,7 @@ dependencies = [ "proc-macro2", "quote", "structmeta", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4538,7 +4538,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4549,7 +4549,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4640,7 +4640,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -4756,7 +4756,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5071,7 +5071,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -5105,7 +5105,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5138,18 +5138,18 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c6dfa3ac045bc517de14c7b1384298de1dbd229d38e08e169d9ae8c170937c" +checksum = "9cd5da49bdf1f30054cfe0b8ce2958b8fbeb67c4d82c8967a598af481bef255c" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" dependencies = [ "rustls-pki-types", ] @@ -5516,7 +5516,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "synstructure", ] @@ -5547,7 +5547,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5558,7 +5558,7 @@ checksum = "593e7c96176495043fcb9e87cf7659f4d18679b5bab6b92bdef359c76a7795dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5578,7 +5578,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", "synstructure", ] @@ -5599,7 +5599,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] [[package]] @@ -5621,5 +5621,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.89", ] diff --git a/bindings/swift/OuisyncLib/.gitignore b/bindings/swift/OuisyncLib/.gitignore index 6cbf21b52..72917e5fd 100644 --- a/bindings/swift/OuisyncLib/.gitignore +++ b/bindings/swift/OuisyncLib/.gitignore @@ -1,4 +1,4 @@ -output +/output .DS_Store /.build /Packages diff --git a/bindings/swift/OuisyncLib/Package.swift b/bindings/swift/OuisyncLib/Package.swift index 1cb370250..592ff50cf 100644 --- a/bindings/swift/OuisyncLib/Package.swift +++ b/bindings/swift/OuisyncLib/Package.swift @@ -1,9 +1,10 @@ // swift-tools-version: 5.9 import PackageDescription + let package = Package( name: "OuisyncLib", - platforms: [.macOS(.v13), .iOS(.v13), .macCatalyst(.v13)], + platforms: [.macOS(.v13), .iOS(.v16)], products: [ .library(name: "OuisyncLib", type: .static, @@ -16,7 +17,8 @@ let package = Package( .target(name: "OuisyncLib", dependencies: [.product(name: "MessagePack", package: "MessagePack.swift"), - "FFIBuilder", "OuisyncLibFFI"], + "FFIBuilder", + "OuisyncLibFFI"], path: "Sources"), .testTarget(name: "OuisyncLibTests", dependencies: ["OuisyncLib"], @@ -29,7 +31,7 @@ let package = Package( path: "Plugins/Builder"), .plugin(name: "Update rust dependencies", capability: .command(intent: .custom(verb: "cargo-fetch", - description: "Updates rust dependencies"), + description: "Update rust dependencies"), permissions: [ .allowNetworkConnections(scope: .all(), reason: "Downloads dependencies defined by Cargo.toml"), diff --git a/bindings/swift/OuisyncLib/Plugins/Builder/builder.swift b/bindings/swift/OuisyncLib/Plugins/Builder/builder.swift index 139e266e3..99d8b8f22 100644 --- a/bindings/swift/OuisyncLib/Plugins/Builder/builder.swift +++ b/bindings/swift/OuisyncLib/Plugins/Builder/builder.swift @@ -1,5 +1,4 @@ -/* Swift package manager build plugin: currently invokes `Builder` before every build. - +/* Swift package manager build plugin: currently invokes `build.sh` before every build. Ideally, a `.buildTool()`[1] plugin[2][3][4] is expected to provide makefile-like rules mapping supplied files to their requirements, which are then used by the build system to only compile the @@ -20,83 +19,26 @@ import Foundation import PackagePlugin -@main struct TreeReconciler: BuildToolPlugin { - func panic(_ msg: String) -> Never { - Diagnostics.error(msg) - fatalError("Unable to build LibOuisyncFFI.xcframework") - } - +@main struct Builder: BuildToolPlugin { func createBuildCommands(context: PackagePlugin.PluginContext, target: PackagePlugin.Target) async throws -> [PackagePlugin.Command] { - let output = context.pluginWorkDirectory + let build = context.pluginWorkDirectory - let dependencies = output + // 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: dependencies.string) - else { panic("Please run `Update rust dependencies` on the OuisyncLib package") } - - let manifest = context.package.directory - .removingLastComponent() // OuisyncLib - .removingLastComponent() // swift - .removingLastComponent() // bindings - .appending("Cargo.toml") + 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").appending("build.sh"), - arguments: [which("cargo"), - which("rustc"), - manifest.string, - output.string, - dependencies], - environment: ProcessInfo.processInfo.environment, - outputFilesDirectory: output.appending("dummy"))] - } - - // runs `which binary` in the default shell and returns the path after confirming that it exists - private func which(_ binary: String) -> String { - let path = shell("which \(binary)").trimmingCharacters(in: .whitespacesAndNewlines) - - guard FileManager.default.fileExists(atPath: path) - else { panic("Unable to find `\(binary)` in environment.") } - - Diagnostics.remark("Found `\(binary)` at \(path)") - return path - } - - // runs `command` in the default shell and returns stdout on clean exit; throws otherwise - private func shell(_ command: String) -> String { - exec(command: ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh", - with: ["-c", command]) - } - - // runs `exe` using `args` in `env` and returns `stdout`; panics on non-zero exit - @discardableResult private func exec(command exe: String, - with args: [String] = [], - in cwd: String? = nil, - using env: [String: String]? = nil) -> String { - let pipe = Pipe() - let task = Process() - task.standardInput = nil - task.standardOutput = pipe - task.executableURL = URL(fileURLWithPath: exe) - task.arguments = args - if let env { task.environment = env } - if let cwd { task.currentDirectoryURL = URL(fileURLWithPath: cwd) } - - do { try task.run() } catch { panic("Unable to start \(exe): \(error)") } - var stdout: Data? - do { stdout = try pipe.fileHandleForReading.readToEnd() } - catch { Diagnostics.warning(String(describing: error)) } - task.waitUntilExit() - - guard task.terminationReason ~= .exit else { panic("\(exe) killed by \(task.terminationStatus)") } - guard task.terminationStatus == 0 else { panic("\(exe) returned \(task.terminationStatus)") } - guard let res = String(data: stdout ?? Data(), encoding: .utf8) else { panic("\(exe) produced binary data") } - - return res + executable: context.package.directory.appending(["Plugins", "build.sh"]), + arguments: [update.string, build.string], + outputFilesDirectory: build.appending("dummy"))] } } diff --git a/bindings/swift/OuisyncLib/Plugins/Updater/updater.swift b/bindings/swift/OuisyncLib/Plugins/Updater/updater.swift index f31b6296b..a32fb6d60 100644 --- a/bindings/swift/OuisyncLib/Plugins/Updater/updater.swift +++ b/bindings/swift/OuisyncLib/Plugins/Updater/updater.swift @@ -10,7 +10,7 @@ import Foundation import PackagePlugin -@main struct UpdateDeps: CommandPlugin { +@main struct Updater: CommandPlugin { func panic(_ msg: String) -> Never { Diagnostics.error(msg) fatalError("Unable to update rust dependencies") @@ -18,103 +18,23 @@ import PackagePlugin func performCommand(context: PackagePlugin.PluginContext, arguments: [String] = []) async throws { - let package = context.package.directory - let manifest = package - .removingLastComponent() // OuisyncLib - .removingLastComponent() // swift - .removingLastComponent() // bindings - .appending("Cargo.toml") + let update = context.pluginWorkDirectory - // install package deps - let cargo = which("cargo") - let rust = which("rustc") - let output = context.pluginWorkDirectory - exec(command: cargo, - with: ["fetch"], - using: ["CARGO_HOME": output.string, // package plugin sandbox forces us to write here - "CARGO_HTTP_CHECK_REVOKE": "false", // this fails in the sandbox, for "reasons" - "MANIFEST_PATH": manifest.string, // path to Cargo.toml, avoids having to chdir - "RUSTC": rust]) // without this cargo assumes $CARGO_HOME/bin/rustc - - // we also have to install cbindgen because running it automatically doesn't always work - exec(command:cargo, - with: ["install", "--force", "cbindgen"], - using: ["CARGO_HOME": output.string, // package plugin sandbox forces us to write here) - "CARGO_HTTP_CHECK_REVOKE": "false", // this fails in the sandbox, for "reasons" - "RUSTC": rust, // without this cargo assumes $CARGO_HOME/bin/rustc - "PATH": Path(which("cc")).removingLastComponent().string]) // (╯°□°)╯︵ ┻━┻ - Diagnostics.remark("Dependencies up to date in \(output)!") - - // link project workspace to output folder - let dest = 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") - .appending("OuisyncLib") - .appending("FFIBuilder") - let link = package.appending("output") - - // create stub framework in output folder - exec(command: package.appending("reset-output.sh").string, in: dest.string) - - // replace link - let fm = FileManager.default - try? fm.removeItem(atPath: link.string) - try fm.createSymbolicLink(atPath: link.string, withDestinationPath: dest.appending("output").string) - - // run a build if possible - do { - let res = try packageManager.build(PackageManager.BuildSubset.target("OuisyncLib"), - parameters: .init()) - guard res.succeeded else { - Diagnostics.warning(res.logText) - throw NSError(domain: "Build failed", code: 1) - } - } catch { - Diagnostics.warning("Unable to auto rebuild: \(error)") - } - } - - // runs `which binary` in the default shell and returns the path after confirming that it exists - private func which(_ binary: String) -> String { - let path = shell("which \(binary)").trimmingCharacters(in: .whitespacesAndNewlines) + .appending(["\(context.package.id).output", "OuisyncLib", "FFIBuilder"]) - guard FileManager.default.fileExists(atPath: path) - else { panic("Unable to find `\(binary)` in environment.") } - - Diagnostics.remark("Found `\(binary)` at \(path)") - return path - } - - // runs `command` in the default shell and returns stdout on clean exit; throws otherwise - private func shell(_ command: String) -> String { - exec(command: ProcessInfo.processInfo.environment["SHELL"] ?? "/bin/zsh", - with: ["-c", command]) - } - - // runs `exe` using `args` in `env` and returns `stdout`; panics on non-zero exit - @discardableResult private func exec(command exe: String, - with args: [String] = [], - in cwd: String? = nil, - using env: [String: String]? = nil) -> String { - let pipe = Pipe() let task = Process() + let exe = context.package.directory.appending(["Plugins", "update.sh"]).string task.standardInput = nil - task.standardOutput = pipe task.executableURL = URL(fileURLWithPath: exe) - task.arguments = args - if let env { task.environment = env } - if let cwd { task.currentDirectoryURL = URL(fileURLWithPath: cwd) } - + task.arguments = [update.string, build.string] do { try task.run() } catch { panic("Unable to start \(exe): \(error)") } - var stdout: Data? - do { stdout = try pipe.fileHandleForReading.readToEnd() } - catch { Diagnostics.warning(String(describing: error)) } task.waitUntilExit() guard task.terminationReason ~= .exit else { panic("\(exe) killed by \(task.terminationStatus)") } guard task.terminationStatus == 0 else { panic("\(exe) returned \(task.terminationStatus)") } - guard let res = String(data: stdout ?? Data(), encoding: .utf8) else { panic("\(exe) produced binary data") } - - return res + Diagnostics.remark("Dependencies up to date!") } } diff --git a/bindings/swift/OuisyncLib/Plugins/build.sh b/bindings/swift/OuisyncLib/Plugins/build.sh index 40ca9190c..a08e07b3c 100755 --- a/bindings/swift/OuisyncLib/Plugins/build.sh +++ b/bindings/swift/OuisyncLib/Plugins/build.sh @@ -1,70 +1,106 @@ -#!/usr/bin/env bash -# Command line tool which produces a `OuisyncLibFFI` framework for the host's llvm triple. +#!/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 the `output` folder and cannot access -# the network, so it relies on the `Updater` companion plugin to run a `cargo fetch` before hand. +# 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 tree reconciler plugin which passes its own environment as well as the input, output -# and dependency paths. The tree reconciler checks that the dependency folder exists, but does not +# 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 # -# Primarily intended for debug builds, the resulting framework is neither cross platform nor cross -# architecture. For distributions purposes, a `fat` framework is needed that includes at the very -# least 2 arm64 targets (iOS and macOS), another x86_64 pair if we want to support older macs and -# iOS simulators and _another_ pair if we want catalyst (x86_64 and arm64)[1]. -# -# Weighing in at ~500M, static library seems abnormally large and some 10 times larger than the -# dylib version. It will be stripped down by Xcode but given the above situation, we're currently -# looking at a 3G download, possibly reducible to 1G if we drop catalyst and lipo the platforms but -# FIXME: is there any way to tell cargo to strip the symbols that are not referenced by the ffi?[3] -# -# Additionally, because the tree reconciler can call the builder from a `.prebuildTool()` command, -# this script would have to be included in a `.binaryTarget()`[2] that is statically available at -# package resolution time, and cannot be bundled as a `.executableTarget()` dependency. Currently, -# these binaryTargets must be xcframeworks themselves, so instead of depending on a BuildToolBuilder -# plugin and product from another subpackage which almost certainly results in a dependency loop, -# we're running this file from the package directory after `cargo fetch` completes. +# Hic sunt dracones! These might be of interest to anyone thinking they can do better than this mess: # # [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 -TARGET_DIR="$4" -TARGET="debug" +PROJECT_HOME=$(realpath "$(dirname "$0")/../../../../") +PACKAGE_HOME=$(realpath "$PROJECT_HOME/bindings/swift/OuisyncLib") +export CARGO_HOME="$1" +export RUSTUP_HOME="$CARGO_HOME/.rustup" +BUILD_OUTPUT="$2" # cargo builds some things that confuse xcode such as fingerprints and depfiles which cannot be -# (easily) disabled; to prevent this, we generate the outputs we care about into $TARGET_DIR/target -# which is a sibling of the $TARGET_DIR/$BUILD_CONFIGURATION folder that cargo generates to -OUTPUT_DIR="$TARGET_DIR/output" -INCLUDE_DIR="$TARGET_DIR/$TARGET/include" +# (easily) disabled; additionally, xcode does pick up the xcframework and reports it as a duplicate +# target if present in the output folder, so other than the symlink hack from `update.sh`, we have +# to tell xcode that our output is in an empty `dummy` folder +mkdir -p "$BUILD_OUTPUT/dummy" + +# read config and prepare to build +source "$PACKAGE_HOME/config.sh" +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 -# use a dummy dir to avoid duplicate target errors in xcode -mkdir -p "$TARGET_DIR/dummy" +# build configured targets +cd $PROJECT_HOME +for TARGET in ${(k)TARGETS}; do + "$CARGO_HOME/bin/cross" build \ + --frozen \ + --package ouisync-ffi \ + --target $TARGET \ + --target-dir "$BUILD_OUTPUT" \ + $FLAGS || exit 1 +done -# prepare include folder -rm -Rf "$INCLUDE_DIR" -mkdir -p "$INCLUDE_DIR" +# generate include files +INCLUDE="$BUILD_OUTPUT/include" +mkdir -p "$INCLUDE" echo "module OuisyncLibFFI { header \"bindings.h\" export * -}" > "$INCLUDE_DIR/module.modulemap" - -# run cargo in offline mode, using the prefetched dependencies that reside in the same folder as -# this script; xcode calls this with a very limited environment, so the locations of `cargo` and -# `rustc` are passed from the caller -CARGO_HOME="$5" RUSTC="$2" \ -"$1" build --offline \ - --package ouisync-ffi \ - --manifest-path "$3" \ - --target-dir "$TARGET_DIR" || exit 1 - -# generate c bindings (xcode and cargo REALLY don't want to work together) -cd "$(dirname "$3")" -PATH=$(dirname "$1") "$5/bin/cbindgen" --lang C --crate ouisync-ffi > "$INCLUDE_DIR/bindings.h" || exit 2 +}" > "$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: speed this process up by moving intead of copying; investigate whether a symlink would work -rm -Rf $OUTPUT_DIR/OuisyncLibFFI.xcframework -xcodebuild -create-xcframework \ - -library "$TARGET_DIR/$TARGET/libouisync_ffi.a" \ - -headers "$INCLUDE_DIR" \ - -output "$OUTPUT_DIR/OuisyncLibFFI.xcframework" +# TODO: some symlinks would be lovely here instead, cargo already create two copies +rm -Rf $BUILD_OUTPUT/output/OuisyncLibFFI.xcframework + +# 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_ffi.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_ffi.a" + mkdir -p "$(dirname "$LIBRARY")" + lipo -create $MATCHED[@] -output $LIBRARY || exit 3 + fi + PARAMS+=("-library" "$LIBRARY" "-headers" "$INCLUDE") +done +echo ${PARAMS[@]} +xcodebuild \ + -create-xcframework ${PARAMS[@]} \ + -output "$BUILD_OUTPUT/output/OuisyncLibFFI.xcframework" || exit 4 diff --git a/bindings/swift/OuisyncLib/Plugins/update.sh b/bindings/swift/OuisyncLib/Plugins/update.sh new file mode 100755 index 000000000..570fe5859 --- /dev/null +++ b/bindings/swift/OuisyncLib/Plugins/update.sh @@ -0,0 +1,39 @@ +#!/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) +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 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" +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" + +# unfortunately, we can't trigger a build from here because `build.sh` runs in a different sandbox diff --git a/bindings/swift/OuisyncLib/config.sh b/bindings/swift/OuisyncLib/config.sh new file mode 100644 index 000000000..32dc0832a --- /dev/null +++ b/bindings/swift/OuisyncLib/config.sh @@ -0,0 +1,11 @@ +# this file is sourced by `build.sh`; keep as many options enabled as you have patience for +SKIP=1 +#DEBUG=1 # set to 0 to generate release builds (much faster) +TARGETS=( + aarch64-apple-darwin # mac on apple silicon + x86_64-apple-darwin # mac on intel + aarch64-apple-ios # all real devices (ios 11+ are 64 bit only) + aarch64-apple-ios-sim # simulators when running on M chips + x86_64-apple-ios # simulator running on intel chips +) +# make sure to re-run "Update rust dependencies" after making changes here diff --git a/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/Info.plist b/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/Info.plist index 876c4aa2f..670c55dbb 100644 --- a/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/Info.plist +++ b/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/Info.plist @@ -4,22 +4,6 @@ AvailableLibraries - - BinaryPath - libouisync_ffi.a - HeadersPath - Headers - LibraryIdentifier - macos-x86_64 - LibraryPath - libouisync_ffi.a - SupportedArchitectures - - x86_64 - - SupportedPlatform - macos - CFBundlePackageType XFWK diff --git a/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/macos-x86_64/Headers/module.modulemap b/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/macos-x86_64/Headers/module.modulemap deleted file mode 100644 index eae4973a9..000000000 --- a/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/macos-x86_64/Headers/module.modulemap +++ /dev/null @@ -1,3 +0,0 @@ -module OuisyncLibFFI { - export * -} diff --git a/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/macos-x86_64/libouisync_ffi.a b/bindings/swift/OuisyncLib/output/OuisyncLibFFI.xcframework/macos-x86_64/libouisync_ffi.a deleted file mode 100644 index e69de29bb..000000000