mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-04-24 09:08:23 -04:00
383 lines
13 KiB
Rust
383 lines
13 KiB
Rust
use std::{
|
|
collections::HashMap,
|
|
fs::{copy, create_dir_all, remove_dir_all, remove_file, rename},
|
|
};
|
|
|
|
use camino::{Utf8Path, Utf8PathBuf};
|
|
use clap::{Args, Subcommand};
|
|
use uniffi_bindgen::{bindings::SwiftBindingGenerator, library_mode::generate_bindings};
|
|
use xshell::{cmd, pushd};
|
|
|
|
use crate::{workspace, Result};
|
|
|
|
/// Builds the SDK for Swift as a Static Library or XCFramework.
|
|
#[derive(Args)]
|
|
pub struct SwiftArgs {
|
|
#[clap(subcommand)]
|
|
cmd: SwiftCommand,
|
|
}
|
|
|
|
#[derive(Subcommand)]
|
|
enum SwiftCommand {
|
|
/// Builds the SDK for Swift as a static lib.
|
|
BuildLibrary,
|
|
|
|
/// Builds the SDK for Swift as an XCFramework.
|
|
BuildFramework {
|
|
/// Build with the release profile
|
|
#[clap(long)]
|
|
release: bool,
|
|
|
|
/// Build with a custom profile, takes precedence over `--release`
|
|
#[clap(long)]
|
|
profile: Option<String>,
|
|
|
|
/// Build the given target. This option can be specified multiple times
|
|
/// to build more than one. Omitting this option will build all
|
|
/// supported targets.
|
|
#[clap(long)]
|
|
target: Option<Vec<String>>,
|
|
|
|
/// Move the generated xcframework and swift sources into the given
|
|
/// components-folder
|
|
#[clap(long)]
|
|
components_path: Option<Utf8PathBuf>,
|
|
|
|
/// Build the targets one by one instead of passing all of them
|
|
/// to cargo in one go, which makes it hang on lesser devices like plain
|
|
/// Apple Silicon M1s
|
|
#[clap(long)]
|
|
sequentially: bool,
|
|
},
|
|
}
|
|
|
|
impl SwiftArgs {
|
|
pub fn run(self) -> Result<()> {
|
|
let _p = pushd(workspace::root_path()?)?;
|
|
|
|
match self.cmd {
|
|
SwiftCommand::BuildLibrary => build_library(),
|
|
SwiftCommand::BuildFramework {
|
|
release,
|
|
profile,
|
|
components_path,
|
|
target: targets,
|
|
sequentially,
|
|
} => {
|
|
// The dev profile seems to cause crashes on some platforms so we default to reldbg
|
|
// https://github.com/matrix-org/matrix-rust-sdk/issues/4009
|
|
let profile = profile.as_deref().unwrap_or(if release { "release" } else { "reldbg" });
|
|
build_xcframework(profile, targets, components_path, sequentially)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A specific build target supported by the SDK.
|
|
struct Target {
|
|
triple: &'static str,
|
|
platform: Platform,
|
|
description: &'static str,
|
|
}
|
|
|
|
/// The platform for which a particular target can run on.
|
|
#[derive(Hash, PartialEq, Eq, Clone)]
|
|
enum Platform {
|
|
Macos,
|
|
Ios,
|
|
IosSimulator,
|
|
}
|
|
|
|
impl Platform {
|
|
/// The human-readable name of the platform.
|
|
fn as_str(&self) -> &str {
|
|
match self {
|
|
Platform::Macos => "macOS",
|
|
Platform::Ios => "iOS",
|
|
Platform::IosSimulator => "iOS Simulator",
|
|
}
|
|
}
|
|
|
|
/// The name of the subfolder in which to place the library for the platform
|
|
/// once all architectures are lipo'd together.
|
|
fn lib_folder_name(&self) -> &str {
|
|
match self {
|
|
Platform::Macos => "macos",
|
|
Platform::Ios => "ios",
|
|
Platform::IosSimulator => "ios-simulator",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The base name of the FFI library.
|
|
const FFI_LIBRARY_NAME: &str = "libmatrix_sdk_ffi.a";
|
|
/// The list of targets supported by the SDK.
|
|
const TARGETS: &[Target] = &[
|
|
Target { triple: "aarch64-apple-ios", platform: Platform::Ios, description: "iOS" },
|
|
Target {
|
|
triple: "aarch64-apple-darwin",
|
|
platform: Platform::Macos,
|
|
description: "macOS (Apple Silicon)",
|
|
},
|
|
Target {
|
|
triple: "x86_64-apple-darwin",
|
|
platform: Platform::Macos,
|
|
description: "macOS (Intel)",
|
|
},
|
|
Target {
|
|
triple: "aarch64-apple-ios-sim",
|
|
platform: Platform::IosSimulator,
|
|
description: "iOS Simulator (Apple Silicon)",
|
|
},
|
|
Target {
|
|
triple: "x86_64-apple-ios",
|
|
platform: Platform::IosSimulator,
|
|
description: "iOS Simulator (Intel) ",
|
|
},
|
|
];
|
|
|
|
fn build_library() -> Result<()> {
|
|
println!("Running debug library build.");
|
|
|
|
let root_directory = workspace::root_path()?;
|
|
let target_directory = workspace::target_path()?;
|
|
let ffi_directory = root_directory.join("bindings/apple/generated/matrix_sdk_ffi");
|
|
let lib_output_dir = target_directory.join("debug");
|
|
|
|
create_dir_all(ffi_directory.as_path())?;
|
|
|
|
cmd!("rustup run stable cargo build -p matrix-sdk-ffi").run()?;
|
|
|
|
rename(lib_output_dir.join(FFI_LIBRARY_NAME), ffi_directory.join(FFI_LIBRARY_NAME))?;
|
|
let swift_directory = root_directory.join("bindings/apple/generated/swift");
|
|
create_dir_all(swift_directory.as_path())?;
|
|
|
|
generate_uniffi(&ffi_directory.join(FFI_LIBRARY_NAME), &ffi_directory)?;
|
|
|
|
let module_map_file = ffi_directory.join("module.modulemap");
|
|
if module_map_file.exists() {
|
|
remove_file(module_map_file.as_path())?;
|
|
}
|
|
|
|
consolidate_modulemap_files(&ffi_directory, &ffi_directory)?;
|
|
move_files("swift", &ffi_directory, &swift_directory)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_uniffi(library_path: &Utf8Path, ffi_directory: &Utf8Path) -> Result<()> {
|
|
generate_bindings(library_path, None, &SwiftBindingGenerator, None, ffi_directory, false)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn build_xcframework(
|
|
profile: &str,
|
|
targets: Option<Vec<String>>,
|
|
components_path: Option<Utf8PathBuf>,
|
|
sequentially: bool,
|
|
) -> Result<()> {
|
|
let root_dir = workspace::root_path()?;
|
|
let apple_dir = root_dir.join("bindings/apple");
|
|
let generated_dir = apple_dir.join("generated");
|
|
|
|
// Cleanup destination folder
|
|
let _ = remove_dir_all(&generated_dir);
|
|
create_dir_all(&generated_dir)?;
|
|
|
|
let headers_dir = generated_dir.join("headers");
|
|
// Use a subdirectory to fix conflicts with other UniFFI libraries.
|
|
let headers_module_dir = headers_dir.join("MatrixSDKFFI");
|
|
let swift_dir = generated_dir.join("swift");
|
|
create_dir_all(headers_module_dir.clone())?;
|
|
create_dir_all(swift_dir.clone())?;
|
|
|
|
let targets = if let Some(triples) = targets {
|
|
triples
|
|
.iter()
|
|
.map(|t| {
|
|
TARGETS.iter().find(|target| target.triple == *t).expect("Invalid target specified")
|
|
})
|
|
.collect()
|
|
} else {
|
|
TARGETS.iter().collect()
|
|
};
|
|
|
|
let platform_build_paths = build_targets(targets, profile, sequentially)?;
|
|
let libs = lipo_platform_libraries(&platform_build_paths, &generated_dir)?;
|
|
|
|
println!("-- Generating uniffi files");
|
|
let uniffi_lib_path = platform_build_paths.values().next().unwrap().first().unwrap().clone();
|
|
generate_uniffi(&uniffi_lib_path, &generated_dir)?;
|
|
|
|
move_files("h", &generated_dir, &headers_module_dir)?;
|
|
consolidate_modulemap_files(&generated_dir, &headers_module_dir)?;
|
|
|
|
move_files("swift", &generated_dir, &swift_dir)?;
|
|
|
|
println!("-- Generating MatrixSDKFFI.xcframework framework");
|
|
let xcframework_path = generated_dir.join("MatrixSDKFFI.xcframework");
|
|
if xcframework_path.exists() {
|
|
remove_dir_all(&xcframework_path)?;
|
|
}
|
|
let mut cmd = cmd!("xcodebuild -create-xcframework");
|
|
for p in libs {
|
|
cmd = cmd.arg("-library").arg(p).arg("-headers").arg(&headers_dir)
|
|
}
|
|
cmd.arg("-output").arg(&xcframework_path).run()?;
|
|
|
|
// Copy the Swift package manifest to the SDK root for local development.
|
|
copy(apple_dir.join("Debug-Package.swift"), root_dir.join("Package.swift"))?;
|
|
|
|
// Copy an empty package to folders we want ignored
|
|
let ignored_package_folders = ["target"];
|
|
for path in ignored_package_folders {
|
|
copy(
|
|
apple_dir.join("Debug-Empty-Package.swift"),
|
|
root_dir.join(path).join("Package.swift"),
|
|
)?;
|
|
}
|
|
|
|
// cleaning up the intermediate data
|
|
remove_dir_all(headers_dir.as_path())?;
|
|
|
|
if let Some(path) = components_path {
|
|
println!("-- Copying MatrixSDKFFI.xcframework to {path}");
|
|
let framework_target = path.join("MatrixSDKFFI.xcframework");
|
|
let swift_target = path.join("Sources/MatrixRustSDK");
|
|
if framework_target.exists() {
|
|
remove_dir_all(&framework_target)?;
|
|
}
|
|
if swift_target.exists() {
|
|
remove_dir_all(&swift_target)?;
|
|
}
|
|
create_dir_all(&framework_target)?;
|
|
create_dir_all(&swift_target)?;
|
|
|
|
let copy_options = fs_extra::dir::CopyOptions { content_only: true, ..Default::default() };
|
|
|
|
fs_extra::dir::copy(&xcframework_path, &framework_target, ©_options)?;
|
|
fs_extra::dir::copy(&swift_dir, &swift_target, ©_options)?;
|
|
}
|
|
|
|
println!("-- All done and hunky dory. Enjoy!");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Builds the SDK for the specified targets and profile.
|
|
fn build_targets(
|
|
targets: Vec<&Target>,
|
|
profile: &str,
|
|
sequentially: bool,
|
|
) -> Result<HashMap<Platform, Vec<Utf8PathBuf>>> {
|
|
if sequentially {
|
|
for target in &targets {
|
|
let triple = target.triple;
|
|
|
|
println!("-- Building for {}", target.description);
|
|
cmd!("rustup run stable cargo build -p matrix-sdk-ffi --target {triple} --profile {profile}")
|
|
.run()?;
|
|
}
|
|
} else {
|
|
let triples = &targets.iter().map(|target| target.triple).collect::<Vec<_>>();
|
|
let mut cmd = cmd!("rustup run stable cargo build -p matrix-sdk-ffi");
|
|
for triple in triples {
|
|
cmd = cmd.arg("--target").arg(triple);
|
|
}
|
|
cmd = cmd.arg("--profile").arg(profile);
|
|
|
|
println!("-- Building for {} targets", triples.len());
|
|
cmd.run()?;
|
|
}
|
|
|
|
// a hashmap of platform to array, where each array contains all the paths for
|
|
// that platform.
|
|
let mut platform_build_paths = HashMap::new();
|
|
for target in targets {
|
|
let path = build_path_for_target(target, profile)?;
|
|
let paths = platform_build_paths.entry(target.platform.clone()).or_insert_with(Vec::new);
|
|
paths.push(path);
|
|
}
|
|
|
|
Ok(platform_build_paths)
|
|
}
|
|
|
|
/// The path of the built library for a specific target and profile.
|
|
fn build_path_for_target(target: &Target, profile: &str) -> Result<Utf8PathBuf> {
|
|
// The builtin dev profile has its files stored under target/debug, all
|
|
// other targets have matching directory names
|
|
let profile_dir_name = if profile == "dev" { "debug" } else { profile };
|
|
Ok(workspace::target_path()?.join(target.triple).join(profile_dir_name).join(FFI_LIBRARY_NAME))
|
|
}
|
|
|
|
/// Lipo's together the libraries for each platform into a single library.
|
|
fn lipo_platform_libraries(
|
|
platform_build_paths: &HashMap<Platform, Vec<Utf8PathBuf>>,
|
|
generated_dir: &Utf8PathBuf,
|
|
) -> Result<Vec<Utf8PathBuf>> {
|
|
let mut libs = Vec::new();
|
|
for platform in platform_build_paths.keys() {
|
|
let paths = platform_build_paths.get(platform).unwrap();
|
|
|
|
if paths.len() == 1 {
|
|
libs.push(paths[0].clone());
|
|
continue;
|
|
}
|
|
|
|
let output_folder = generated_dir.join("lipo").join(platform.lib_folder_name());
|
|
create_dir_all(&output_folder)?;
|
|
|
|
let output_path = output_folder.join(FFI_LIBRARY_NAME);
|
|
let mut cmd = cmd!("lipo -create");
|
|
for path in paths {
|
|
cmd = cmd.arg(path);
|
|
}
|
|
cmd = cmd.arg("-output").arg(&output_path);
|
|
|
|
println!("-- Running Lipo for {}", platform.as_str());
|
|
cmd.run()?;
|
|
|
|
libs.push(output_path);
|
|
}
|
|
Ok(libs)
|
|
}
|
|
|
|
/// Moves all files of the specified file extension from one directory into
|
|
/// another.
|
|
fn move_files(extension: &str, source: &Utf8PathBuf, destination: &Utf8PathBuf) -> Result<()> {
|
|
for entry in source.read_dir_utf8()? {
|
|
let entry = entry?;
|
|
|
|
if entry.file_type()?.is_file() {
|
|
let path = entry.path();
|
|
if path.extension() == Some(extension) {
|
|
let file_name = path.file_name().expect("Failed to get file name");
|
|
rename(path, destination.join(file_name)).expect("Failed to move swift file");
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Consolidates the contents of each modulemap file found in the source
|
|
/// directory into a single `module.modulemap` file in the destination
|
|
/// directory.
|
|
fn consolidate_modulemap_files(source: &Utf8PathBuf, destination: &Utf8PathBuf) -> Result<()> {
|
|
let mut modulemap = String::new();
|
|
for entry in source.read_dir_utf8()? {
|
|
let entry = entry?;
|
|
|
|
if entry.file_type()?.is_file() {
|
|
let path = entry.path();
|
|
if path.extension() == Some("modulemap") {
|
|
let contents = std::fs::read_to_string(path)?;
|
|
modulemap.push_str(&contents);
|
|
modulemap.push_str("\n\n");
|
|
remove_file(path)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::fs::write(destination.join("module.modulemap"), modulemap)?;
|
|
Ok(())
|
|
}
|