Skip to content

Commit

Permalink
Build the ouisync ffi as part of the package on MacOS
Browse files Browse the repository at this point in the history
Also remove automatic loading of the dylib library from Flutter. It'll
only be loaded when session is created using the `Session.create`
function but not when created with the `Session.createChanneled` function).
  • Loading branch information
inetic committed Jul 26, 2024
1 parent c0fde0b commit b9ddede
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 37 deletions.
6 changes: 5 additions & 1 deletion bindings/dart/lib/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart' show kReleaseMode;

export 'bindings.g.dart';

final bindings = Bindings(_defaultLib());
Bindings? bindings;

typedef PostCObject = Int8 Function(Int64, Pointer<Dart_CObject>);

Expand Down Expand Up @@ -86,6 +86,10 @@ class Bindings {
.lookup<NativeFunction<_free_string_c>>('free_string')
.asFunction();

static Bindings loadDefault() {
return Bindings(_defaultLib());
}

final session_create_dart session_create;
final session_channel_send_dart session_channel_send;
final session_close_dart session_close;
Expand Down
11 changes: 6 additions & 5 deletions bindings/dart/lib/internal/direct_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ class DirectClient extends Client {
int _handle;
final Stream<Uint8List> _stream;
final MessageMatcher _messageMatcher = MessageMatcher();
final Bindings _bindings;

DirectClient(this._handle, ReceivePort port) : _stream = port.cast<Uint8List>(), super() {
DirectClient(this._handle, ReceivePort port, this._bindings) : _stream = port.cast<Uint8List>(), super() {
unawaited(_receive());
}

Expand Down Expand Up @@ -50,7 +51,7 @@ class DirectClient extends Client {
_messageMatcher.close();

await _invokeNativeAsync(
(port) => bindings.session_close(
(port) => _bindings.session_close(
handle,
NativeApi.postCObject,
port,
Expand All @@ -68,7 +69,7 @@ class DirectClient extends Client {

_messageMatcher.close();

bindings.session_close_blocking(handle);
_bindings.session_close_blocking(handle);
}

Future<void> _receive() async {
Expand All @@ -79,7 +80,7 @@ class DirectClient extends Client {

Future<void> copyToRawFd(int fileHandle, int fd) {
return _invokeNativeAsync(
(port) => bindings.file_copy_to_raw_fd(
(port) => _bindings.file_copy_to_raw_fd(
handle,
fileHandle,
fd,
Expand All @@ -98,7 +99,7 @@ class DirectClient extends Client {

try {
buffer.asTypedList(data.length).setAll(0, data);
bindings.session_channel_send(_handle, buffer, data.length);
_bindings.session_channel_send(_handle, buffer, data.length);
} finally {
malloc.free(buffer);
}
Expand Down
13 changes: 9 additions & 4 deletions bindings/dart/lib/ouisync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ class Session {
}

final recvPort = ReceivePort();
final result = _withPoolSync((pool) => bindings.session_create(

if (bindings == null) {
bindings = Bindings.loadDefault();
}

final result = _withPoolSync((pool) => bindings!.session_create(
kind.encode(),
pool.toNativeUtf8(configPath),
logPath != null ? pool.toNativeUtf8(logPath) : nullptr,
Expand All @@ -71,7 +76,7 @@ class Session {
throw Error(errorCode, errorMessage);
}

final client = DirectClient(handle, recvPort);
final client = DirectClient(handle, recvPort, bindings!);

return Session._(client);
}
Expand Down Expand Up @@ -923,7 +928,7 @@ class File {

/// Print log message
void logPrint(LogLevel level, String scope, String message) =>
_withPoolSync((pool) => bindings.log_print(
_withPoolSync((pool) => bindings!.log_print(
level.encode(),
pool.toNativeUtf8(scope),
pool.toNativeUtf8(message),
Expand Down Expand Up @@ -992,5 +997,5 @@ extension Utf8Pointer on Pointer<Utf8> {

// Free a pointer that was allocated by the native side.
void freeString(Pointer<Utf8> ptr) {
bindings.free_string(ptr.cast<Char>());
bindings!.free_string(ptr.cast<Char>());
}
4 changes: 3 additions & 1 deletion bindings/swift/OuisyncLib/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ let package = Package(
name: "OuisyncLib",
dependencies: [.product(name:"MessagePack", package: "MessagePack.swift"), "OuisyncLibFFI"]
),
.target(name: "OuisyncLibFFI", dependencies: []),
.target(name: "OuisyncLibFFI", dependencies: ["OuisyncDyLibBuilder"]),
.plugin(name: "OuisyncDyLibBuilder", capability: .buildTool()),

.testTarget(
name: "OuisyncLibTests",
dependencies: ["OuisyncLib"]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// File.swift
//
//
// Created by Peter Jankuliak on 26/07/2024.
//

// A package plugin which builds the libouisync_ffi.dylib library and includes it in the package.
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md
//
// TODO: Right now it creates only Debug version of the library and only for the current architecture.
// Can we detect the build type and architecture here? This github ticket seems to indicate it's not
// currently possible:
// https://github.com/swiftlang/swift-package-manager/issues/7110

import Foundation
import PackagePlugin

@main
struct OuisyncDyLibBuilder: BuildToolPlugin {
func createBuildCommands(context: PackagePlugin.PluginContext, target: PackagePlugin.Target) async throws -> [PackagePlugin.Command] {
let workDir = context.pluginWorkDirectory
let dylibName = "libouisync_ffi.dylib"
let dylibPath = workDir.appending("debug").appending(dylibName)
let cargoPath = shell("which cargo").trimmingCharacters(in: .whitespacesAndNewlines)
let inputFiles = findInputFiles()

return [
.buildCommand(
displayName: "Build Ouisync FFI .dylib",
executable: Path(cargoPath),
arguments: ["build", "-p", "ouisync-ffi", "--target-dir", workDir],
environment: [:],
inputFiles: inputFiles.map { Path($0) },
outputFiles: [ dylibPath ])
]
}
}

// This finds files which when changed, the Swift builder will re-execute the build above build command.
// The implementation is not very good because if a new directory/module is added on which this package
// depends, the package builder won't rebuild the ffi library.
// TODO: use `cargo build --build-plan` once it's in stable.
func findInputFiles() -> [String] {
var files = [String]()
let startAts = [
URL(string: "../../../bridge")!,
URL(string: "../../../deadlock")!,
URL(string: "../../../ffi")!,
URL(string: "../../../lib")!,
URL(string: "../../../net")!,
URL(string: "../../../rand")!,
URL(string: "../../../scoped_task")!,
URL(string: "../../../state_monitor")!,
URL(string: "../../../tracing_fmt")!,
URL(string: "../../../utils")!,
URL(string: "../../../vfs")!,
]
for startAt in startAts {
if let enumerator = FileManager.default.enumerator(at: startAt, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
if fileAttributes.isRegularFile! {
files.append(fileURL.path)
}
} catch { fatalError("Error finding input files: \(error), \(fileURL)") }
}
}
}
return files
}

func shell(_ command: String) -> String {
let task = Process()
let pipe = Pipe()

task.standardOutput = pipe
task.standardError = pipe
task.arguments = ["-c", command]
task.launchPath = "/bin/zsh"
task.standardInput = nil
task.launch()

let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)!

return output
}
31 changes: 5 additions & 26 deletions bindings/swift/OuisyncLib/Sources/OuisyncLib/OuisyncFFI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ public class OuisyncFFI {
let sessionKindShared: FFISessionKind = 0;

public init() {
handle = dlopen("libouisync_ffi.dylib", RTLD_NOW)!
// The .dylib is created using the OuisyncDyLibBuilder package plugin in this Swift package.
let libraryName = "libouisync_ffi.dylib"
let resourcePath = Bundle.main.resourcePath! + "/OuisyncLib_OuisyncLibFFI.bundle/Contents/Resources"
handle = dlopen("\(resourcePath)/\(libraryName)", RTLD_NOW)!

ffiSessionGrab = unsafeBitCast(dlsym(handle, "session_grab"), to: FFISessionGrab.self)
ffiSessionChannelSend = unsafeBitCast(dlsym(handle, "session_channel_send"), to: FFISessionChannelSend.self)
ffiSessionClose = unsafeBitCast(dlsym(handle, "session_close"), to: FFISessionClose.self)
Expand Down Expand Up @@ -82,28 +86,3 @@ public class OuisyncFFI {
return Unmanaged<T>.fromOpaque(ptr).takeRetainedValue()
}
}

// ---------------------------------------------------------------------------------------

typealias Rx = AsyncStream<[UInt8]>
typealias Tx = AsyncStream<[UInt8]>.Continuation

class Wrap<T> {
let inner: T
init(_ inner: T) { self.inner = inner }
}

class Channel {
let rx: Rx
let tx: Tx

init(_ rx: Rx, _ tx: Tx) { self.rx = rx; self.tx = tx }
}

func makeStream() -> (Rx, Tx) {
var continuation: Rx.Continuation!
let stream = Rx() { continuation = $0 }
return (stream, continuation!)
}

// ---------------------------------------------------------------------------------------

0 comments on commit b9ddede

Please sign in to comment.