Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ THIS_DIR=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
ROOT_DIR="$THIS_DIR"
cd $ROOT_DIR

cargo build --target x86_64-apple-darwin --target-dir "$(pwd)/target"
cargo build --target aarch64-apple-darwin --target-dir "$(pwd)/target"
RUST_TARGET="${RUST_TARGET:-aarch64-apple-darwin}"

mkdir -p "$(pwd)/target/universal/"
cargo build --target "$RUST_TARGET" --target-dir "$(pwd)/target"

lipo \
$(pwd)/target/aarch64-apple-darwin/debug/libtest_swift_packages.a \
$(pwd)/target/x86_64-apple-darwin/debug/libtest_swift_packages.a -create -output \
$(pwd)/target/universal/libtest_swift_packages.a
mkdir -p "$(pwd)/target/universal/"

cp \
"$(pwd)/target/$RUST_TARGET/debug/libtest_swift_packages.a" \
"$(pwd)/target/universal/libtest_swift_packages.a"
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ let package = Package(
.library(
name: "swift-package-test-package",
targets: ["swift-package-test-package"]),
.executable(
name: "swift-package-test-packageTestsRunner",
targets: ["swift-package-test-packageTestsRunner"]),
],
dependencies: [
.package(path: "../swift-package-rust-library-fixture/MySwiftPackage")
Expand All @@ -16,6 +19,9 @@ let package = Package(
.target(
name: "swift-package-test-package",
dependencies: []),
.executableTarget(
name: "swift-package-test-packageTestsRunner",
dependencies: ["MySwiftPackage"]),
.testTarget(
name: "swift-package-test-packageTests",
dependencies: ["swift-package-test-package", "MySwiftPackage"]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import MySwiftPackage

func expect(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() {
fatalError(message)
}
}

expect(hello_rust().toString() == "Hello, From Rust!", "Rust string did not match")
expect(SomeStruct(field: 1).field == 1, "Shared struct field did not match")
expect(UnnamedStruct(_0: 1)._0 == 1, "Unnamed shared struct field did not match")
145 changes: 128 additions & 17 deletions crates/swift-bridge-build/src/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,20 @@ pub fn create_package(config: CreatePackageConfig) {
}

// Generate RustXcframework //
gen_xcframework(&output_dir, &config);
let rust_target = gen_xcframework(&output_dir, &config);

// Generate Swift Package //
gen_package(&output_dir, &config);
gen_package(&output_dir, &config, rust_target);
}

#[derive(Copy, Clone)]
enum RustTarget {
Xcframework,
SwiftpmTarget,
}

/// Generates the RustXcframework
fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) {
fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) -> RustTarget {
// Create directories
let temp_dir = tempdir().expect("Couldn't create temporary directory");
let tmp_framework_path = &temp_dir.path().join("swiftbridge._tmp_framework");
Expand Down Expand Up @@ -229,16 +235,33 @@ fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) {

let output = Command::new("xcodebuild")
.current_dir(&tmp_framework_path)
.args(args)
.args(&args)
.stdout(Stdio::piped())
.spawn()
.expect("Failed to spawn xcodebuild")
.wait_with_output()
.expect("Failed to execute xcodebuild");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr).unwrap();
panic!("{}", stderr);
}
.stderr(Stdio::piped())
.spawn();
let rust_target = match output {
Ok(child) => {
let output = child
.wait_with_output()
.expect("Failed to execute xcodebuild");
if !output.status.success() {
let stderr = std::str::from_utf8(&output.stderr).unwrap();
if stderr.contains("tool 'xcodebuild' not found") {
gen_swiftpm_rust_target(output_dir, &tmp_framework_path, config);
RustTarget::SwiftpmTarget
} else {
panic!("{}", stderr);
}
} else {
RustTarget::Xcframework
}
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
gen_swiftpm_rust_target(output_dir, &tmp_framework_path, config);
RustTarget::SwiftpmTarget
}
Err(err) => panic!("Failed to spawn xcodebuild: {}", err),
};

// Remove temporary directory
let temp_dir_string = temp_dir.path().to_str().unwrap().to_string();
Expand All @@ -248,6 +271,55 @@ fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) {
temp_dir_string, err
);
}

rust_target
}

fn gen_swiftpm_rust_target(
output_dir: &Path,
tmp_framework_path: &Path,
config: &CreatePackageConfig,
) {
if config.paths.len() != 1 || !config.paths.contains_key(&ApplePlatform::MacOS) {
panic!("SwiftPM Rust target fallback only supports a single macOS library");
}

let xcframework_dir = output_dir.join("RustXcframework.xcframework");
if xcframework_dir.exists() {
fs::remove_dir_all(&xcframework_dir).expect("Couldn't delete unused xcframework directory");
}

let target_dir = output_dir.join("RustXcframework");
if target_dir.exists() {
fs::remove_dir_all(&target_dir).expect("Couldn't delete previous SwiftPM Rust target");
}
fs::create_dir_all(target_dir.join("include"))
.expect("Couldn't create SwiftPM Rust target include directory");
fs::create_dir_all(target_dir.join("lib"))
.expect("Couldn't create SwiftPM Rust target lib directory");
fs::write(
target_dir.join("shim.c"),
"void swift_bridge_static_library_linker_shim(void) {}\n",
)
.expect("Couldn't write SwiftPM Rust target shim");

for header in fs::read_dir(tmp_framework_path.join("include")).expect("Couldn't read headers") {
let header = header.expect("Couldn't read header").path();
fs::copy(
&header,
target_dir.join("include").join(header.file_name().unwrap()),
)
.expect("Couldn't copy header into SwiftPM Rust target");
}

let lib_path: &Path = config.paths.get(&ApplePlatform::MacOS).unwrap().as_ref();
fs::copy(
tmp_framework_path
.join(ApplePlatform::MacOS.dir_name())
.join(lib_path.file_name().unwrap()),
target_dir.join("lib").join(lib_path.file_name().unwrap()),
)
.expect("Couldn't copy library into SwiftPM Rust target");
}

/// Generates the Swift Package.
Expand All @@ -259,7 +331,7 @@ fn gen_xcframework(output_dir: &Path, config: &CreatePackageConfig) {
/// The alternative would be to use something like `@_exported import RustXcframework`, but this
/// would make the Rust xcframework (i.e. methods like __swift_bridge__$some_method) available to
/// the Swift Package's consumer, which we don't want.
fn gen_package(output_dir: &Path, config: &CreatePackageConfig) {
fn gen_package(output_dir: &Path, config: &CreatePackageConfig, rust_target: RustTarget) {
let sources_dir = output_dir.join("Sources").join(&config.package_name);
if !sources_dir.exists() {
fs::create_dir_all(&sources_dir).expect("Couldn't create directory for source files");
Expand Down Expand Up @@ -311,9 +383,41 @@ fn gen_package(output_dir: &Path, config: &CreatePackageConfig) {

// Generate Package.swift
let package_name = &config.package_name;
let package_directory = match rust_target {
RustTarget::Xcframework => "".to_string(),
RustTarget::SwiftpmTarget => {
"let packageDirectory = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path\n"
.to_string()
}
};
let rust_target_package_swift = match rust_target {
RustTarget::Xcframework => r#" .binaryTarget(
name: "RustXcframework",
path: "RustXcframework.xcframework"
),"#
.to_string(),
RustTarget::SwiftpmTarget => {
let lib_name = swiftpm_link_library_name(config);
r#" .target(
name: "RustXcframework",
path: "RustXcframework",
publicHeadersPath: "include",
linkerSettings: [
.unsafeFlags([
"-L",
"\(packageDirectory)/RustXcframework/lib",
"-lLIB_NAME"
])
]
),"#
.replace("LIB_NAME", &lib_name)
}
};
let package_swift = format!(
r#"// swift-tools-version:5.5.0
import PackageDescription
import Foundation
{package_directory}
let package = Package(
name: "{package_name}",
products: [
Expand All @@ -323,10 +427,7 @@ let package = Package(
],
dependencies: [],
targets: [
.binaryTarget(
name: "RustXcframework",
path: "RustXcframework.xcframework"
),
{rust_target_package_swift}
.target(
name: "{package_name}",
dependencies: ["RustXcframework"])
Expand All @@ -338,3 +439,13 @@ let package = Package(
fs::write(output_dir.join("Package.swift"), package_swift)
.expect("Couldn't write Package.swift file");
}

fn swiftpm_link_library_name(config: &CreatePackageConfig) -> String {
let lib_path: &Path = config.paths.get(&ApplePlatform::MacOS).unwrap().as_ref();
let file_name = lib_path.file_name().unwrap().to_str().unwrap();
let without_prefix = file_name.strip_prefix("lib").unwrap_or(file_name);
without_prefix
.strip_suffix(".a")
.unwrap_or(without_prefix)
.to_string()
}
27 changes: 27 additions & 0 deletions flake.lock

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

30 changes: 30 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
description = "swift-bridge development shell";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};

outputs =
{ nixpkgs, ... }:
let
system = "aarch64-darwin";
pkgs = import nixpkgs { inherit system; };
in
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
cargo
rustc
swift
swiftPackages.swiftpm
swiftPackages.XCTest
];

shellHook = ''
export CARGO_BUILD_TARGET=aarch64-apple-darwin
export PATH="$PATH:/usr/bin:/bin"
'';
};
};
}
12 changes: 9 additions & 3 deletions test-swift-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ ROOT_DIR="$THIS_DIR"
cd $ROOT_DIR

# Make a temp directory
TEMP_DIR=$(mktemp -d)
mkdir -p "$ROOT_DIR/target/test-tmp"
TEMP_DIR=$(mktemp -d "$ROOT_DIR/target/test-tmp/test-swift-packages.XXXXXX")

# Delete the temp directory before the shell exits
trap 'rm -rf $TEMP_DIR' EXIT
if [ -z "${KEEP_TEST_SWIFT_PACKAGES_TEMP:-}" ]; then
trap 'rm -rf $TEMP_DIR' EXIT
else
echo "Keeping temp directory: $TEMP_DIR"
fi

# Copy directories related to all of the building and test running to the temp directory
for DIR in crates src examples SwiftRustIntegrationTestRunner
Expand All @@ -36,4 +41,5 @@ cargo run -p integration-test-create-swift-package

# Test Swift Package
cd swift-package-test-package
swift test
swift-build --disable-sandbox --product swift-package-test-packageTestsRunner
swift-run --disable-sandbox --skip-build swift-package-test-packageTestsRunner