From fbb047e16acb149ab57bd0477802b71504c65d04 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 9 Apr 2024 17:13:14 +1200 Subject: [PATCH 1/6] Add a command line tool to create xcframework --- Cargo.lock | 9 ++ Cargo.toml | 1 + tools/Cargo.toml | 9 ++ tools/src/main.rs | 29 ++++ tools/src/xcframework.rs | 323 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 371 insertions(+) create mode 100644 tools/Cargo.toml create mode 100644 tools/src/main.rs create mode 100644 tools/src/xcframework.rs diff --git a/Cargo.lock b/Cargo.lock index 690e83505..0d1f52f95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1359,6 +1359,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "tools" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "serde_json", +] + [[package]] name = "tower" version = "0.4.13" diff --git a/Cargo.toml b/Cargo.toml index 56e67a680..323f5be00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "wp_derive", "wp_networking", "wp_uniffi_bindgen", + "tools", ] resolver = "2" diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 000000000..7133cf6d7 --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tools" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +serde_json = "1.0" +anyhow = "1.0" diff --git a/tools/src/main.rs b/tools/src/main.rs new file mode 100644 index 000000000..ccb87ff26 --- /dev/null +++ b/tools/src/main.rs @@ -0,0 +1,29 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use crate::xcframework::CreateXCFramework; + +mod xcframework; + +#[derive(Parser)] +struct Opts { + #[clap(subcommand)] + action: Actions, +} + +#[derive(Debug, Subcommand)] +enum Actions { + #[clap(name = "create-xcframework")] + CreateXCFramework(CreateXCFramework), +} + +trait Action { + fn run(&self) -> Result<()>; +} + +fn main() -> Result<()> { + let opts = Opts::parse(); + match opts.action { + Actions::CreateXCFramework(action) => action.run(), + } +} diff --git a/tools/src/xcframework.rs b/tools/src/xcframework.rs new file mode 100644 index 000000000..f6acdcc7d --- /dev/null +++ b/tools/src/xcframework.rs @@ -0,0 +1,323 @@ +use anyhow::{Context, Error, Result}; +use clap::*; +use std::collections::HashMap; +use std::fmt::Display; +use std::path::{Path, PathBuf}; +use std::process::Command; + +use crate::Action; + +static LIBRARY_FILENAME: &str = "libwordpress.a"; + +#[derive(Debug, Args)] +pub struct CreateXCFramework { + // Non-empty list of targets + #[clap( + long, + num_args = 1.., + required = true, + help = "List of targets whose static libraries should be included in the xcframework" + )] + targets: Vec, + + #[clap( + long, + default_value = "release", + help = "Cargo profile used to build the targets" + )] + profile: String, +} + +impl Action for CreateXCFramework { + fn run(&self) -> Result<()> { + let temp_dir = std::env::temp_dir().join("wp-rs-xcframework"); + recreate_directory(&temp_dir)?; + + XCFramework::new(&self.targets, &self.profile)?.create(&temp_dir)?; + + Ok(()) + } +} + +// Represent a xcframework that contains static libraries for multiple platforms. +// +// Since `xcodebuild -create-xcframework` command requires its `-libraray` not +// having duplicated platform. This type along with `LibraryGroup` and `Slice` +// work together to make it easier to create a xcframework. +struct XCFramework { + libraries: Vec, + headers: PathBuf, +} + +// Represent a group of static libraries that are built for the same platform. +struct LibraryGroup { + id: LibraryGroupId, + slices: Vec, +} + +// Represent a thin static library which is built with `cargo build --target --profile ` +struct Slice { + target: String, + profile: String, +} + +impl XCFramework { + fn new(targets: &Vec, profile: &str) -> Result { + let headers = PathBuf::from("target/swift-bindings/headers"); + if !headers.exists() { + return Err(Error::msg(format!( + "Headers not found: {}", + headers.display() + ))); + } + + let mut groups = HashMap::::new(); + for target in targets { + let id = LibraryGroupId::from_target(target)?; + let id_clone = id.clone(); + groups + .entry(id) + .or_insert(LibraryGroup { + id: id_clone, + slices: Vec::new(), + }) + .slices + .push(Slice { + target: target.clone(), + profile: profile.to_owned(), + }); + } + + Ok(Self { + libraries: groups.into_values().collect(), + headers, + }) + } + + fn create(&self, temp_dir: &Path) -> Result { + self.preview(); + + let libraries = self.combine_libraries(temp_dir)?; + let temp_dest = self.create_xcframework(&libraries, temp_dir)?; + + let dest = PathBuf::from("target/libwordpressFFI.xcframework"); + recreate_directory(&dest)?; + std::fs::rename(temp_dest, &dest).with_context(|| "Failed to move xcframework")?; + + println!("xcframework created at {}", &dest.display()); + Ok(dest) + } + + fn preview(&self) { + println!("Creating xcframework to include the following targets:"); + for lib in &self.libraries { + println!(" Platform: {}", lib.id); + for slice in &lib.slices { + println!(" - {}", slice.target); + } + } + } + + fn combine_libraries(&self, temp_dir: &Path) -> Result> { + let mut libraries: Vec = Vec::new(); + for lib in &self.libraries { + libraries.push(lib.create(temp_dir)?); + } + Ok(libraries) + } + + fn create_xcframework(&self, libraries: &[PathBuf], temp_dir: &Path) -> Result { + let temp_dest = temp_dir.join("libwordpressFFI.xcframework"); + std::fs::remove_dir_all(&temp_dest).ok(); + + let library_args = libraries.iter().flat_map(|lib| { + [ + "-library".as_ref(), + lib.as_os_str(), + "-headers".as_ref(), + self.headers.as_os_str(), + ] + }); + Command::new("xcodebuild") + .arg("-create-xcframework") + .args(library_args) + .arg("-output") + .arg(&temp_dest) + .successful_output()?; + + Ok(temp_dest) + } +} + +impl LibraryGroup { + fn create(&self, temp_dir: &Path) -> Result { + let mut libraries: Vec = Vec::new(); + for slice in &self.slices { + libraries.push(slice.create(temp_dir)?); + } + + let dir = temp_dir.join(self.id.to_string()); + recreate_directory(&dir)?; + + let dest = dir.join(LIBRARY_FILENAME); + Command::new("lipo") + .arg("-create") + .args(libraries) + .arg("-output") + .arg(&dest) + .successful_output()?; + + Ok(dest) + } +} + +impl Slice { + fn create(&self, temp_dir: &Path) -> Result { + let libs = self.built_libraries(); + + // If there are more static libraries (a.k.a cargo packages), we'll + // need to bundle them together into one static library. + // At the moment, we only have one libwp_api, so we can just copy it. + assert!( + libs.len() == 1, + "Expected exactly one library for each slice" + ); + + let lib = &libs[0]; + if !lib.exists() { + return Err(Error::msg(format!("Library not found: {}", lib.display()))); + } + + let dir = temp_dir.join(&self.target); + recreate_directory(&dir)?; + + let dest = dir.join(LIBRARY_FILENAME); + std::fs::copy(lib, &dest) + .with_context(|| format!("Failed to copy {} to {}", lib.display(), dest.display()))?; + + Ok(dest) + } + + fn built_libraries(&self) -> Vec { + let mut target_dir: PathBuf = ["target", &self.target].iter().collect(); + if self.profile == "dev" { + target_dir.push("debug"); + } else { + target_dir.push(&self.profile); + } + + vec![target_dir.join("libwp_api.a")] + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +struct LibraryGroupId { + os: ApplePlatform, + is_sim: bool, +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +enum ApplePlatform { + MacOS, + #[allow(clippy::upper_case_acronyms)] + IOS, + TvOS, + WatchOS, +} + +impl Display for ApplePlatform { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self { + ApplePlatform::MacOS => "macos", + ApplePlatform::IOS => "ios", + ApplePlatform::TvOS => "tvos", + ApplePlatform::WatchOS => "watchos", + }; + write!(f, "{}", name) + } +} + +impl LibraryGroupId { + fn from_target(target: &str) -> Result { + let mut parts = target.split('-'); + _ /* arch */= parts.next(); + if parts.next() != Some("apple") { + return Err(Error::msg(format!("{} is not an Apple platform", target))); + } + + let os = match parts.next() { + Some("darwin") => ApplePlatform::MacOS, + Some("ios") => ApplePlatform::IOS, + Some("tvos") => ApplePlatform::TvOS, + Some("watchos") => ApplePlatform::WatchOS, + _ => return Err(Error::msg(format!("Unknown OS in target: {}", target))), + }; + + let output = Command::new("rustc") + .env("RUSTC_BOOTSTRAP", "1") + .args([ + "-Z", + "unstable-options", + "--print", + "target-spec-json", + "--target", + ]) + .arg(target) + .successful_output()?; + let json = serde_json::from_slice::(&output.stdout) + .with_context(|| "Failed to parse command output as JSON")?; + let llvm_target = json + .get("llvm-target") + .and_then(|t| t.as_str()) + .ok_or(Error::msg("No llvm-target in command output"))?; + + Ok(Self { + os, + is_sim: llvm_target.ends_with("-simulator"), + }) + } +} + +impl Display for LibraryGroupId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.os)?; + + if self.is_sim { + write!(f, "-sim") + } else { + Ok(()) + } + } +} + +trait ExecuteCommand { + fn successful_output(&mut self) -> Result; +} + +impl ExecuteCommand for Command { + fn successful_output(&mut self) -> Result { + let output = self + .output() + .with_context(|| format!("Command failed: $ {:?}", self))?; + if output.status.success() { + Ok(output) + } else { + Err(Error::msg(format!( + "Command failed with exit code: {}\n$ {:?}", + output.status, self + ))) + } + } +} + +fn recreate_directory(dir: &PathBuf) -> Result<()> { + if dir.exists() { + std::fs::remove_dir_all(dir) + .with_context(|| format!("Failed to remove directory at {:?}", dir))?; + } + + std::fs::create_dir_all(dir) + .with_context(|| format!("Failed to create directory: {:?}", dir))?; + + Ok(()) +} From e416aede5bb73c20135696fb648732750db0283f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 9 Apr 2024 17:13:22 +1200 Subject: [PATCH 2/6] Update Makefile to use the new create-xcframework tool --- Makefile | 135 +++++++++++++++++++++---------------------------------- 1 file changed, 51 insertions(+), 84 deletions(-) diff --git a/Makefile b/Makefile index beaf5ff07..7352be7da 100644 --- a/Makefile +++ b/Makefile @@ -74,67 +74,6 @@ _test-android: _publish-android-local: ./native/android/gradlew -p ./native/android publishToMavenLocal -exclude-task prepareToPublishToS3 - -# Builds the library for all the various architectures / systems required in an XCFramework -xcframework-libraries: - # macOS - env MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos) $(MAKE) x86_64-apple-darwin-xcframework-library - env MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos) $(MAKE) aarch64-apple-darwin-xcframework-library - - # iOS - env IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) $(MAKE) aarch64-apple-ios-xcframework-library - env IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) $(MAKE) x86_64-apple-ios-xcframework-library - env IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) $(MAKE) aarch64-apple-ios-sim-xcframework-library - - # tvOS - env TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) $(MAKE) aarch64-apple-tvos-xcframework-library-with-nightly - env TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) $(MAKE) aarch64-apple-tvos-sim-xcframework-library-with-nightly - env TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) $(MAKE) x86_64-apple-tvos-xcframework-library-with-nightly - - # watchOS - env WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) $(MAKE) arm64_32-apple-watchos-xcframework-library-with-nightly - env WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) $(MAKE) aarch64-apple-watchos-sim-xcframework-library-with-nightly - env WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) $(MAKE) x86_64-apple-watchos-sim-xcframework-library-with-nightly - -%-xcframework-library: - cargo build --target $* --package wp_api --release - $(MAKE) $*-combine-libraries - -%-xcframework-library-with-nightly: - cargo +$(rust_nightly_toolchain) build --target $* --package wp_api --release -Zbuild-std - $(MAKE) $*-combine-libraries - -# Xcode doesn't properly support multiple XCFrameworks being used by the same target, so we need -# to combine the binaries -%-combine-libraries: - xcrun libtool -static -o target/$*/release/libwordpress.a target/$*/release/libwp_api.a #target/$*/release/libwp_networking.a - -# Some libraries need to be created in a multi-binary format, so we combine them here -xcframework-combined-libraries: xcframework-libraries - - rm -rf target/universal-* - mkdir -p target/universal-macos/release target/universal-ios/release target/universal-tvos/release target/universal-watchos/release - - # Combine the macOS Binaries - lipo -create target/aarch64-apple-darwin/release/libwordpress.a target/x86_64-apple-darwin/release/libwordpress.a \ - -output target/universal-macos/release/libwordpress.a - lipo -info target/universal-macos/release/libwordpress.a - - # Combine iOS Simulator Binaries - lipo -create target/aarch64-apple-ios-sim/release/libwordpress.a target/x86_64-apple-ios/release/libwordpress.a \ - -output target/universal-ios/release/libwordpress.a - lipo -info target/universal-ios/release/libwordpress.a - - # Combine tvOS Simulator Binaries - lipo -create target/aarch64-apple-tvos-sim/release/libwordpress.a target/x86_64-apple-tvos/release/libwordpress.a \ - -output target/universal-tvos/release/libwordpress.a - lipo -info target/universal-tvos/release/libwordpress.a - - # Combine watchOS Simulator Binaries - lipo -create target/aarch64-apple-watchos-sim/release/libwordpress.a target/x86_64-apple-watchos-sim/release/libwordpress.a \ - -output target/universal-watchos/release/libwordpress.a - lipo -info target/universal-watchos/release/libwordpress.a - # An XCFramework relies on the .h file and the modulemap to interact with the precompiled binary xcframework-headers: bindings rm -rvf target/swift-bindings/headers @@ -143,33 +82,61 @@ xcframework-headers: bindings cp target/swift-bindings/*.h target/swift-bindings/headers cp target/swift-bindings/libwordpressFFI.modulemap target/swift-bindings/headers/module.modulemap + +# TODO: Add arm64_32-apple-watchos to the list +apple-platform-targets-macos := x86_64-apple-darwin aarch64-apple-darwin +apple-platform-targets-ios := aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim +apple-platform-targets-tvos := aarch64-apple-tvos aarch64-apple-tvos-sim +apple-platform-targets-watchos := x86_64-apple-watchos-sim aarch64-apple-watchos-sim +apple-platform-targets := \ + $(apple-platform-targets-macos) \ + $(apple-platform-targets-ios) \ + $(apple-platform-targets-tvos) \ + $(apple-platform-targets-watchos) + +ifeq ($(BUILDKITE), true) +CARGO_PROFILE ?= release +else +CARGO_PROFILE ?= dev +endif + +# Set deployment targets for each platform +_build-apple-%-darwin: export MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos) +_build-apple-%-ios _build-apple-%-ios-sim: export IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) +_build-apple-%-tvos _build-apple-%-tvos-sim: export TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) +_build-apple-%-watchos _build-apple-%-watchos-sim: export WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) + +# Use nightly toolchain for tvOS and watchOS +_build-apple-%-tvos _build-apple-%-tvos-sim _build-apple-%-watchos _build-apple-%-watchos-sim: \ + CARGO_OPTS = +$(rust_nightly_toolchain) -Z build-std=panic_abort,std + +# Build the library for a specific target +_build-apple-%: xcframework-headers + cargo $(CARGO_OPTS) build --target $* --package wp_api --profile $(CARGO_PROFILE) + +# Build the library for one single platform, including real device and simulator. +build-apple-platform-macos := $(addprefix _build-apple-,$(apple-platform-targets-macos)) +build-apple-platform-ios := $(addprefix _build-apple-,$(apple-platform-targets-ios)) +build-apple-platform-tvos := $(addprefix _build-apple-,$(apple-platform-targets-tvos)) +build-apple-platform-watchos := $(addprefix _build-apple-,$(apple-platform-targets-watchos)) + +# Creating xcframework for one single platform, including real device and simulator. +xcframework-only-macos: $(build-apple-platform-macos) +xcframework-only-ios: $(build-apple-platform-ios) +xcframework-only-tvos: $(build-apple-platform-tvos) +xcframework-only-watchos: $(build-apple-platform-watchos) +xcframework-only-%: + cargo run --quiet --bin tools -- create-xcframework --profile $(CARGO_PROFILE) --targets $(apple-platform-targets-$*) + +# Creating xcframework for all platforms. +xcframework-all: $(build-apple-platform-macos) $(build-apple-platform-ios) $(build-apple-platform-tvos) $(build-apple-platform-watchos) + cargo run --quiet --bin tools -- create-xcframework --profile $(CARGO_PROFILE) --targets $(apple-platform-targets) + ifeq ($(SKIP_PACKAGE_WP_API),true) xcframework: @echo "Skip building libwordpressFFI.xcframework" else - -# Generate the xcframework -# -# Run `make setup-rust` to install required rust toolchain. -xcframework: bindings xcframework-combined-libraries xcframework-headers - - rm -rf target/libwordpressFFI.xcframework - - xcodebuild -create-xcframework \ - -library target/aarch64-apple-ios/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -library target/universal-macos/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -library target/universal-ios/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -library target/aarch64-apple-tvos/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -library target/universal-tvos/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -library target/universal-watchos/release/libwordpress.a \ - -headers target/swift-bindings/headers \ - -output target/libwordpressFFI.xcframework - +xcframework: xcframework-all endif docker-image-swift: From 0ca24011a5787654010ee3937334e72f23dc6882 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Tue, 9 Apr 2024 17:20:08 +1200 Subject: [PATCH 3/6] Remove tabs --- Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 7352be7da..9d11bdb4d 100644 --- a/Makefile +++ b/Makefile @@ -84,10 +84,10 @@ xcframework-headers: bindings # TODO: Add arm64_32-apple-watchos to the list -apple-platform-targets-macos := x86_64-apple-darwin aarch64-apple-darwin -apple-platform-targets-ios := aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim -apple-platform-targets-tvos := aarch64-apple-tvos aarch64-apple-tvos-sim -apple-platform-targets-watchos := x86_64-apple-watchos-sim aarch64-apple-watchos-sim +apple-platform-targets-macos := x86_64-apple-darwin aarch64-apple-darwin +apple-platform-targets-ios := aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim +apple-platform-targets-tvos := aarch64-apple-tvos aarch64-apple-tvos-sim +apple-platform-targets-watchos := x86_64-apple-watchos-sim aarch64-apple-watchos-sim apple-platform-targets := \ $(apple-platform-targets-macos) \ $(apple-platform-targets-ios) \ @@ -101,10 +101,10 @@ CARGO_PROFILE ?= dev endif # Set deployment targets for each platform -_build-apple-%-darwin: export MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos) -_build-apple-%-ios _build-apple-%-ios-sim: export IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) -_build-apple-%-tvos _build-apple-%-tvos-sim: export TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) -_build-apple-%-watchos _build-apple-%-watchos-sim: export WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) +_build-apple-%-darwin: export MACOSX_DEPLOYMENT_TARGET=$(swift_package_platform_macos) +_build-apple-%-ios _build-apple-%-ios-sim: export IPHONEOS_DEPLOYMENT_TARGET=$(swift_package_platform_ios) +_build-apple-%-tvos _build-apple-%-tvos-sim: export TVOS_DEPLOYMENT_TARGET=$(swift_package_platform_tvos) +_build-apple-%-watchos _build-apple-%-watchos-sim: export WATCHOS_DEPLOYMENT_TARGET=$(swift_package_platform_watchos) # Use nightly toolchain for tvOS and watchOS _build-apple-%-tvos _build-apple-%-tvos-sim _build-apple-%-watchos _build-apple-%-watchos-sim: \ @@ -115,10 +115,10 @@ _build-apple-%: xcframework-headers cargo $(CARGO_OPTS) build --target $* --package wp_api --profile $(CARGO_PROFILE) # Build the library for one single platform, including real device and simulator. -build-apple-platform-macos := $(addprefix _build-apple-,$(apple-platform-targets-macos)) -build-apple-platform-ios := $(addprefix _build-apple-,$(apple-platform-targets-ios)) -build-apple-platform-tvos := $(addprefix _build-apple-,$(apple-platform-targets-tvos)) -build-apple-platform-watchos := $(addprefix _build-apple-,$(apple-platform-targets-watchos)) +build-apple-platform-macos := $(addprefix _build-apple-,$(apple-platform-targets-macos)) +build-apple-platform-ios := $(addprefix _build-apple-,$(apple-platform-targets-ios)) +build-apple-platform-tvos := $(addprefix _build-apple-,$(apple-platform-targets-tvos)) +build-apple-platform-watchos := $(addprefix _build-apple-,$(apple-platform-targets-watchos)) # Creating xcframework for one single platform, including real device and simulator. xcframework-only-macos: $(build-apple-platform-macos) From 28212dfff6fc43f61847b096fb9c067ed4708e12 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 3 May 2024 09:18:53 +1200 Subject: [PATCH 4/6] Build for watchOS real devices (#71) --- .buildkite/swift-test.sh | 6 ------ Makefile | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.buildkite/swift-test.sh b/.buildkite/swift-test.sh index e2d9f3b91..647627902 100755 --- a/.buildkite/swift-test.sh +++ b/.buildkite/swift-test.sh @@ -18,12 +18,6 @@ function run_tests() { function build_for_real_device() { local platform; platform=$1 - # See https://github.com/Automattic/wordpress-rs/issues/48 - if [[ $platform == "watchOS" ]]; then - echo "~~~ watchOS is not supported yet" - return - fi - echo "--- :swift: Building for $platform device" export NSUnbufferedIO=YES xcodebuild -destination "generic/platform=$platform" \ diff --git a/Makefile b/Makefile index 407e2508a..c719109da 100644 --- a/Makefile +++ b/Makefile @@ -81,12 +81,10 @@ xcframework-headers: bindings cp target/swift-bindings/*.h target/swift-bindings/headers cp target/swift-bindings/libwordpressFFI.modulemap target/swift-bindings/headers/module.modulemap - -# TODO: Add arm64_32-apple-watchos to the list apple-platform-targets-macos := x86_64-apple-darwin aarch64-apple-darwin apple-platform-targets-ios := aarch64-apple-ios x86_64-apple-ios aarch64-apple-ios-sim apple-platform-targets-tvos := aarch64-apple-tvos aarch64-apple-tvos-sim -apple-platform-targets-watchos := x86_64-apple-watchos-sim aarch64-apple-watchos-sim +apple-platform-targets-watchos := arm64_32-apple-watchos x86_64-apple-watchos-sim aarch64-apple-watchos-sim apple-platform-targets := \ $(apple-platform-targets-macos) \ $(apple-platform-targets-ios) \ From 13425201d6d523e1bce70568ee4da7c48e223a76 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 29 May 2024 11:23:04 +1200 Subject: [PATCH 5/6] Address PR comments --- tools/src/xcframework.rs | 67 ++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/tools/src/xcframework.rs b/tools/src/xcframework.rs index f6acdcc7d..6cfb8688b 100644 --- a/tools/src/xcframework.rs +++ b/tools/src/xcframework.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Error, Result}; +use anyhow::{Context, Result}; use clap::*; use std::collections::HashMap; use std::fmt::Display; @@ -7,7 +7,9 @@ use std::process::Command; use crate::Action; -static LIBRARY_FILENAME: &str = "libwordpress.a"; +const XCFRAMEWORK_OUTPUT_PATH: &str = "target/libwordpressFFI.xcframework"; +const SWIFT_BINDINGS_HEADER_DIR: &str = "target/swift-bindings/headers"; +const LIBRARY_FILENAME: &str = "libwordpress.a"; #[derive(Debug, Args)] pub struct CreateXCFramework { @@ -63,12 +65,9 @@ struct Slice { impl XCFramework { fn new(targets: &Vec, profile: &str) -> Result { - let headers = PathBuf::from("target/swift-bindings/headers"); + let headers = PathBuf::from(SWIFT_BINDINGS_HEADER_DIR); if !headers.exists() { - return Err(Error::msg(format!( - "Headers not found: {}", - headers.display() - ))); + anyhow::bail!("Headers not found: {}", headers.display()) } let mut groups = HashMap::::new(); @@ -100,7 +99,7 @@ impl XCFramework { let libraries = self.combine_libraries(temp_dir)?; let temp_dest = self.create_xcframework(&libraries, temp_dir)?; - let dest = PathBuf::from("target/libwordpressFFI.xcframework"); + let dest = PathBuf::from(XCFRAMEWORK_OUTPUT_PATH); recreate_directory(&dest)?; std::fs::rename(temp_dest, &dest).with_context(|| "Failed to move xcframework")?; @@ -119,11 +118,10 @@ impl XCFramework { } fn combine_libraries(&self, temp_dir: &Path) -> Result> { - let mut libraries: Vec = Vec::new(); - for lib in &self.libraries { - libraries.push(lib.create(temp_dir)?); - } - Ok(libraries) + self.libraries + .iter() + .map(|lib| lib.create(temp_dir)) + .collect() } fn create_xcframework(&self, libraries: &[PathBuf], temp_dir: &Path) -> Result { @@ -185,7 +183,7 @@ impl Slice { let lib = &libs[0]; if !lib.exists() { - return Err(Error::msg(format!("Library not found: {}", lib.display()))); + anyhow::bail!("Library not found: {}", lib.display()) } let dir = temp_dir.join(&self.target); @@ -225,6 +223,20 @@ enum ApplePlatform { WatchOS, } +impl TryFrom<&str> for ApplePlatform { + type Error = anyhow::Error; + + fn try_from(s: &str) -> std::result::Result { + match s { + "darwin" => Ok(ApplePlatform::MacOS), + "ios" => Ok(ApplePlatform::IOS), + "tvos" => Ok(ApplePlatform::TvOS), + "watchos" => Ok(ApplePlatform::WatchOS), + _ => anyhow::bail!("Unknown Apple platform: {}", s), + } + } +} + impl Display for ApplePlatform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { @@ -242,16 +254,13 @@ impl LibraryGroupId { let mut parts = target.split('-'); _ /* arch */= parts.next(); if parts.next() != Some("apple") { - return Err(Error::msg(format!("{} is not an Apple platform", target))); + anyhow::bail!("{} is not an Apple platform", target) } - let os = match parts.next() { - Some("darwin") => ApplePlatform::MacOS, - Some("ios") => ApplePlatform::IOS, - Some("tvos") => ApplePlatform::TvOS, - Some("watchos") => ApplePlatform::WatchOS, - _ => return Err(Error::msg(format!("Unknown OS in target: {}", target))), - }; + let os: ApplePlatform = parts + .next() + .with_context(|| format!("No OS in target: {}", target))? + .try_into()?; let output = Command::new("rustc") .env("RUSTC_BOOTSTRAP", "1") @@ -269,7 +278,7 @@ impl LibraryGroupId { let llvm_target = json .get("llvm-target") .and_then(|t| t.as_str()) - .ok_or(Error::msg("No llvm-target in command output"))?; + .with_context(|| "No llvm-target in command output")?; Ok(Self { os, @@ -302,10 +311,11 @@ impl ExecuteCommand for Command { if output.status.success() { Ok(output) } else { - Err(Error::msg(format!( + anyhow::bail!( "Command failed with exit code: {}\n$ {:?}", - output.status, self - ))) + output.status, + self + ) } } } @@ -316,8 +326,5 @@ fn recreate_directory(dir: &PathBuf) -> Result<()> { .with_context(|| format!("Failed to remove directory at {:?}", dir))?; } - std::fs::create_dir_all(dir) - .with_context(|| format!("Failed to create directory: {:?}", dir))?; - - Ok(()) + std::fs::create_dir_all(dir).with_context(|| format!("Failed to create directory: {:?}", dir)) } From 4c28720893d8c26a6b6dc3693d9679ec1e8f8a8f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Wed, 29 May 2024 11:32:48 +1200 Subject: [PATCH 6/6] Rename tools to xcframework --- Cargo.lock | 18 ++++++------ Cargo.toml | 2 +- Makefile | 4 +-- tools/src/main.rs | 29 ------------------- {tools => xcframework}/Cargo.toml | 2 +- .../xcframework.rs => xcframework/src/main.rs | 10 ++++--- 6 files changed, 19 insertions(+), 46 deletions(-) delete mode 100644 tools/src/main.rs rename {tools => xcframework}/Cargo.toml (87%) rename tools/src/xcframework.rs => xcframework/src/main.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index e5f3ca2a0..b0919fc1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2346,15 +2346,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tools" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap", - "serde_json", -] - [[package]] name = "tower" version = "0.4.13" @@ -3000,6 +2991,15 @@ dependencies = [ "uniffi", ] +[[package]] +name = "xcframework" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "serde_json", +] + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 471d224af..8a4aca886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [ "wp_api", "wp_contextual", "wp_uniffi_bindgen", - "tools", + "xcframework", ] resolver = "2" diff --git a/Makefile b/Makefile index 343212126..d8e06c87d 100644 --- a/Makefile +++ b/Makefile @@ -101,11 +101,11 @@ xcframework-only-ios: $(build-apple-platform-ios) xcframework-only-tvos: $(build-apple-platform-tvos) xcframework-only-watchos: $(build-apple-platform-watchos) xcframework-only-%: - cargo run --quiet --bin tools -- create-xcframework --profile $(CARGO_PROFILE) --targets $(apple-platform-targets-$*) + cargo run --quiet --bin xcframework -- --profile $(CARGO_PROFILE) --targets $(apple-platform-targets-$*) # Creating xcframework for all platforms. xcframework-all: $(build-apple-platform-macos) $(build-apple-platform-ios) $(build-apple-platform-tvos) $(build-apple-platform-watchos) - cargo run --quiet --bin tools -- create-xcframework --profile $(CARGO_PROFILE) --targets $(apple-platform-targets) + cargo run --quiet --bin xcframework -- --profile $(CARGO_PROFILE) --targets $(apple-platform-targets) ifeq ($(SKIP_PACKAGE_WP_API),true) xcframework: diff --git a/tools/src/main.rs b/tools/src/main.rs deleted file mode 100644 index ccb87ff26..000000000 --- a/tools/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; - -use crate::xcframework::CreateXCFramework; - -mod xcframework; - -#[derive(Parser)] -struct Opts { - #[clap(subcommand)] - action: Actions, -} - -#[derive(Debug, Subcommand)] -enum Actions { - #[clap(name = "create-xcframework")] - CreateXCFramework(CreateXCFramework), -} - -trait Action { - fn run(&self) -> Result<()>; -} - -fn main() -> Result<()> { - let opts = Opts::parse(); - match opts.action { - Actions::CreateXCFramework(action) => action.run(), - } -} diff --git a/tools/Cargo.toml b/xcframework/Cargo.toml similarity index 87% rename from tools/Cargo.toml rename to xcframework/Cargo.toml index 7133cf6d7..3e3a80e24 100644 --- a/tools/Cargo.toml +++ b/xcframework/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tools" +name = "xcframework" version = "0.1.0" edition = "2021" diff --git a/tools/src/xcframework.rs b/xcframework/src/main.rs similarity index 98% rename from tools/src/xcframework.rs rename to xcframework/src/main.rs index 6cfb8688b..30ad9173c 100644 --- a/tools/src/xcframework.rs +++ b/xcframework/src/main.rs @@ -5,13 +5,15 @@ use std::fmt::Display; use std::path::{Path, PathBuf}; use std::process::Command; -use crate::Action; - const XCFRAMEWORK_OUTPUT_PATH: &str = "target/libwordpressFFI.xcframework"; const SWIFT_BINDINGS_HEADER_DIR: &str = "target/swift-bindings/headers"; const LIBRARY_FILENAME: &str = "libwordpress.a"; -#[derive(Debug, Args)] +fn main() -> Result<()> { + CreateXCFramework::parse().run() +} + +#[derive(Debug, Parser)] pub struct CreateXCFramework { // Non-empty list of targets #[clap( @@ -30,7 +32,7 @@ pub struct CreateXCFramework { profile: String, } -impl Action for CreateXCFramework { +impl CreateXCFramework { fn run(&self) -> Result<()> { let temp_dir = std::env::temp_dir().join("wp-rs-xcframework"); recreate_directory(&temp_dir)?;