| 1 | use std::{ |
| 2 | collections::HashMap, |
| 3 | fs::{copy, create_dir_all, remove_dir_all, remove_file, rename}, |
| 4 | }; |
| 5 | |
| 6 | use camino::{Utf8Path, Utf8PathBuf}; |
| 7 | use clap::{Args, Subcommand}; |
| 8 | use uniffi_bindgen::bindings::{GenerateOptions, TargetLanguage, generate}; |
| 9 | use xshell::cmd; |
| 10 | |
| 11 | use crate::{Result, sh, workspace}; |
| 12 | |
| 13 | /// Builds the SDK for Swift as a Static Library or XCFramework. |
| 14 | #[derive(Args)] |
| 15 | pub struct SwiftArgs { |
| 16 | #[clap(subcommand)] |
| 17 | cmd: SwiftCommand, |
| 18 | } |
| 19 | |
| 20 | #[derive(Subcommand)] |
| 21 | enum SwiftCommand { |
| 22 | /// Builds the SDK for Swift as a static lib. |
| 23 | BuildLibrary, |
| 24 | |
| 25 | /// Builds the SDK for Swift as an XCFramework. |
| 26 | BuildFramework { |
| 27 | /// Build with the release profile |
| 28 | #[clap(long)] |
| 29 | release: bool, |
| 30 | |
| 31 | /// Build with a custom profile, takes precedence over `--release` |
| 32 | #[clap(long)] |
| 33 | profile: Option<String>, |
| 34 | |
| 35 | /// Build the given target. This option can be specified multiple times |
| 36 | /// to build more than one. Omitting this option will build all |
| 37 | /// supported targets. |
| 38 | #[clap(long)] |
| 39 | target: Option<Vec<String>>, |
| 40 | |
| 41 | /// Includes the Tier 3 targets (such as watchOS) when building all |
| 42 | /// supported targets. Requires a nightly toolchain with the `rust-src` |
| 43 | /// component installed. |
| 44 | #[clap(long)] |
| 45 | tier3_targets: bool, |
| 46 | |
| 47 | /// Move the generated xcframework and swift sources into the given |
| 48 | /// components-folder |
| 49 | #[clap(long)] |
| 50 | components_path: Option<Utf8PathBuf>, |
| 51 | |
| 52 | /// The iOS deployment target to use when building the framework. |
| 53 | /// |
| 54 | /// Defaults to not being set, which implies that the build will use the |
| 55 | /// default values provided by the Rust and Xcode toolchains. |
| 56 | #[clap(long)] |
| 57 | ios_deployment_target: Option<String>, |
| 58 | |
| 59 | /// The wachOS deployment target to use when building the framework. |
| 60 | /// |
| 61 | /// Defaults to not being set, which implies that the build will use the |
| 62 | /// default values provided by the Rust and Xcode toolchains. |
| 63 | #[clap(long)] |
| 64 | watchos_deployment_target: Option<String>, |
| 65 | |
| 66 | /// Build the targets one by one instead of passing all of them |
| 67 | /// to cargo in one go, which makes it hang on lesser devices like plain |
| 68 | /// Apple Silicon M1s |
| 69 | #[clap(long)] |
| 70 | sequentially: bool, |
| 71 | }, |
| 72 | } |
| 73 | |
| 74 | impl SwiftArgs { |
| 75 | pub fn run(self) -> Result<()> { |
| 76 | let sh = sh(); |
| 77 | let _p = sh.push_dir(workspace::root_path()?); |
| 78 | |
| 79 | match self.cmd { |
| 80 | SwiftCommand::BuildLibrary => build_library(), |
| 81 | SwiftCommand::BuildFramework { |
| 82 | release, |
| 83 | profile, |
| 84 | target: targets, |
| 85 | tier3_targets, |
| 86 | components_path, |
| 87 | ios_deployment_target, |
| 88 | watchos_deployment_target, |
| 89 | sequentially, |
| 90 | } => { |
| 91 | // The dev profile seems to cause crashes on some platforms so we default to |
| 92 | // reldbg (https://github.com/matrix-org/matrix-rust-sdk/issues/4009) |
| 93 | let profile = |
| 94 | profile.as_deref().unwrap_or(if release { "release" } else { "reldbg" }); |
| 95 | build_xcframework( |
| 96 | profile, |
| 97 | targets, |
| 98 | tier3_targets, |
| 99 | components_path, |
| 100 | sequentially, |
| 101 | ios_deployment_target.as_deref(), |
| 102 | watchos_deployment_target.as_deref(), |
| 103 | ) |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | } |
| 108 | |
| 109 | /// A specific build target supported by the SDK. |
| 110 | struct Target { |
| 111 | triple: &'static str, |
| 112 | platform: Platform, |
| 113 | status: TargetStatus, |
| 114 | description: &'static str, |
| 115 | } |
| 116 | |
| 117 | #[derive(Hash, PartialEq, Eq, Clone)] |
| 118 | enum TargetStatus { |
| 119 | /// A tier 1 or 2 target that can be built with stable Rust. |
| 120 | TopTier, |
| 121 | /// A tier 3 target that requires nightly Rust and `-Zbuild-std`. |
| 122 | Tier3, |
| 123 | } |
| 124 | |
| 125 | /// The platform for which a particular target can run on. |
| 126 | #[derive(Hash, PartialEq, Eq, Clone)] |
| 127 | enum Platform { |
| 128 | Macos, |
| 129 | Ios, |
| 130 | IosSimulator, |
| 131 | Watchos, |
| 132 | WatchosSimulator, |
| 133 | } |
| 134 | |
| 135 | impl Platform { |
| 136 | /// The human-readable name of the platform. |
| 137 | fn as_str(&self) -> &str { |
| 138 | match self { |
| 139 | Platform::Macos => "macOS", |
| 140 | Platform::Ios => "iOS", |
| 141 | Platform::IosSimulator => "iOS Simulator", |
| 142 | Platform::Watchos => "watchOS", |
| 143 | Platform::WatchosSimulator => "watchOS Simulator", |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | /// The name of the subfolder in which to place the library for the platform |
| 148 | /// once all architectures are lipo'd together. |
| 149 | fn lib_folder_name(&self) -> &str { |
| 150 | match self { |
| 151 | Platform::Macos => "macos", |
| 152 | Platform::Ios => "ios", |
| 153 | Platform::IosSimulator => "ios-simulator", |
| 154 | Platform::Watchos => "watchos", |
| 155 | Platform::WatchosSimulator => "watchos-simulator", |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | /// The base name of the FFI library. |
| 160 | const FFI_LIBRARY_NAME: &str = "libmatrix_sdk_ffi.a"; |
| 161 | |
| 162 | /// The features enabled for the FFI library. |
| 163 | const FFI_FEATURES: &str = "sentry"; |
| 164 | |
| 165 | /// The list of targets supported by the SDK. |
| 166 | const TARGETS: &[Target] = &[ |
| 167 | Target { |
| 168 | triple: "aarch64-apple-ios", |
| 169 | platform: Platform::Ios, |
| 170 | status: TargetStatus::TopTier, |
| 171 | description: "iOS", |
| 172 | }, |
| 173 | Target { |
| 174 | triple: "aarch64-apple-darwin", |
| 175 | platform: Platform::Macos, |
| 176 | status: TargetStatus::TopTier, |
| 177 | description: "macOS (Apple Silicon)", |
| 178 | }, |
| 179 | Target { |
| 180 | triple: "x86_64-apple-darwin", |
| 181 | platform: Platform::Macos, |
| 182 | status: TargetStatus::TopTier, |
| 183 | description: "macOS (Intel)", |
| 184 | }, |
| 185 | Target { |
| 186 | triple: "aarch64-apple-ios-sim", |
| 187 | platform: Platform::IosSimulator, |
| 188 | status: TargetStatus::TopTier, |
| 189 | description: "iOS Simulator (Apple Silicon)", |
| 190 | }, |
| 191 | Target { |
| 192 | triple: "x86_64-apple-ios", |
| 193 | platform: Platform::IosSimulator, |
| 194 | status: TargetStatus::TopTier, |
| 195 | description: "iOS Simulator (Intel) ", |
| 196 | }, |
| 197 | Target { |
| 198 | triple: "aarch64-apple-watchos", |
| 199 | platform: Platform::Watchos, |
| 200 | status: TargetStatus::Tier3, |
| 201 | description: "watchOS (ARM64)", |
| 202 | }, |
| 203 | Target { |
| 204 | triple: "arm64_32-apple-watchos", |
| 205 | platform: Platform::Watchos, |
| 206 | status: TargetStatus::Tier3, |
| 207 | description: "watchOS (ARM64_32)", |
| 208 | }, |
| 209 | Target { |
| 210 | triple: "aarch64-apple-watchos-sim", |
| 211 | platform: Platform::WatchosSimulator, |
| 212 | status: TargetStatus::Tier3, |
| 213 | description: "watchOS Simulator (ARM64)", |
| 214 | }, |
| 215 | Target { |
| 216 | triple: "x86_64-apple-watchos-sim", |
| 217 | platform: Platform::WatchosSimulator, |
| 218 | status: TargetStatus::Tier3, |
| 219 | description: "watchOS Simulator (Intel)", |
| 220 | }, |
| 221 | ]; |
| 222 | |
| 223 | fn build_library() -> Result<()> { |
| 224 | println!("Running debug library build."); |
| 225 | |
| 226 | let root_directory = workspace::root_path()?; |
| 227 | let target_directory = workspace::target_path()?; |
| 228 | let ffi_directory = root_directory.join("bindings/apple/generated/matrix_sdk_ffi"); |
| 229 | let lib_output_dir = target_directory.join("debug"); |
| 230 | |
| 231 | create_dir_all(ffi_directory.as_path())?; |
| 232 | |
| 233 | let sh = sh(); |
| 234 | cmd!(sh, "rustup run stable cargo build -p matrix-sdk-ffi --features {FFI_FEATURES}").run()?; |
| 235 | |
| 236 | rename(lib_output_dir.join(FFI_LIBRARY_NAME), ffi_directory.join(FFI_LIBRARY_NAME))?; |
| 237 | let swift_directory = root_directory.join("bindings/apple/generated/swift"); |
| 238 | create_dir_all(swift_directory.as_path())?; |
| 239 | |
| 240 | generate_uniffi(&ffi_directory.join(FFI_LIBRARY_NAME), &ffi_directory)?; |
| 241 | |
| 242 | let module_map_file = ffi_directory.join("module.modulemap"); |
| 243 | if module_map_file.exists() { |
| 244 | remove_file(module_map_file.as_path())?; |
| 245 | } |
| 246 | |
| 247 | consolidate_modulemap_files(&ffi_directory, &ffi_directory)?; |
| 248 | move_files("swift", &ffi_directory, &swift_directory)?; |
| 249 | update_swift_module_imports(&swift_directory)?; |
| 250 | Ok(()) |
| 251 | } |
| 252 | |
| 253 | fn generate_uniffi(library_path: &Utf8Path, ffi_directory: &Utf8Path) -> Result<()> { |
| 254 | generate(GenerateOptions { |
| 255 | languages: vec![TargetLanguage::Swift], |
| 256 | source: library_path.to_path_buf(), |
| 257 | out_dir: ffi_directory.to_path_buf(), |
| 258 | ..GenerateOptions::default() |
| 259 | })?; |
| 260 | Ok(()) |
| 261 | } |
| 262 | |
| 263 | fn build_xcframework( |
| 264 | profile: &str, |
| 265 | targets: Option<Vec<String>>, |
| 266 | tier3_targets: bool, |
| 267 | components_path: Option<Utf8PathBuf>, |
| 268 | sequentially: bool, |
| 269 | ios_deployment_target: Option<&str>, |
| 270 | watchos_deployment_target: Option<&str>, |
| 271 | ) -> Result<()> { |
| 272 | let root_dir = workspace::root_path()?; |
| 273 | let apple_dir = root_dir.join("bindings/apple"); |
| 274 | let generated_dir = apple_dir.join("generated"); |
| 275 | |
| 276 | // Cleanup destination folder |
| 277 | let _ = remove_dir_all(&generated_dir); |
| 278 | create_dir_all(&generated_dir)?; |
| 279 | |
| 280 | let headers_dir = generated_dir.join("headers"); |
| 281 | // Use a subdirectory to fix conflicts with other UniFFI libraries. |
| 282 | let headers_module_dir = headers_dir.join("MatrixSDKFFI"); |
| 283 | let swift_dir = generated_dir.join("swift"); |
| 284 | create_dir_all(headers_module_dir.clone())?; |
| 285 | create_dir_all(swift_dir.clone())?; |
| 286 | |
| 287 | let targets = if let Some(triples) = targets { |
| 288 | triples |
| 289 | .iter() |
| 290 | .map(|t| { |
| 291 | TARGETS.iter().find(|target| target.triple == *t).expect("Invalid target specified") |
| 292 | }) |
| 293 | .collect() |
| 294 | } else if tier3_targets { |
| 295 | TARGETS.iter().collect() |
| 296 | } else { |
| 297 | TARGETS.iter().filter(|target| target.status == TargetStatus::TopTier).collect() |
| 298 | }; |
| 299 | |
| 300 | let platform_build_paths = build_targets( |
| 301 | targets, |
| 302 | profile, |
| 303 | sequentially, |
| 304 | ios_deployment_target, |
| 305 | watchos_deployment_target, |
| 306 | )?; |
| 307 | let libs = lipo_platform_libraries(&platform_build_paths, &generated_dir)?; |
| 308 | |
| 309 | println!("-- Generating uniffi files"); |
| 310 | let uniffi_lib_path = platform_build_paths.values().next().unwrap().first().unwrap().clone(); |
| 311 | generate_uniffi(&uniffi_lib_path, &generated_dir)?; |
| 312 | |
| 313 | move_files("h", &generated_dir, &headers_module_dir)?; |
| 314 | consolidate_modulemap_files(&generated_dir, &headers_module_dir)?; |
| 315 | |
| 316 | move_files("swift", &generated_dir, &swift_dir)?; |
| 317 | update_swift_module_imports(&swift_dir)?; |
| 318 | |
| 319 | println!("-- Generating MatrixSDKFFI.xcframework framework"); |
| 320 | let xcframework_path = generated_dir.join("MatrixSDKFFI.xcframework"); |
| 321 | if xcframework_path.exists() { |
| 322 | remove_dir_all(&xcframework_path)?; |
| 323 | } |
| 324 | let sh = sh(); |
| 325 | let mut cmd = cmd!(sh, "xcodebuild -create-xcframework"); |
| 326 | for p in libs { |
| 327 | cmd = cmd.arg("-library").arg(p).arg("-headers").arg(&headers_dir) |
| 328 | } |
| 329 | cmd.arg("-output").arg(&xcframework_path).run()?; |
| 330 | |
| 331 | // Copy the Swift package manifest to the SDK root for local development. |
| 332 | copy(apple_dir.join("Debug-Package.swift"), root_dir.join("Package.swift"))?; |
| 333 | |
| 334 | // Copy an empty package to folders we want ignored |
| 335 | let ignored_package_folders = ["target"]; |
| 336 | for path in ignored_package_folders { |
| 337 | copy( |
| 338 | apple_dir.join("Debug-Empty-Package.swift"), |
| 339 | root_dir.join(path).join("Package.swift"), |
| 340 | )?; |
| 341 | } |
| 342 | |
| 343 | // cleaning up the intermediate data |
| 344 | remove_dir_all(headers_dir.as_path())?; |
| 345 | |
| 346 | if let Some(path) = components_path { |
| 347 | println!("-- Copying MatrixSDKFFI.xcframework to {path}"); |
| 348 | let framework_target = path.join("MatrixSDKFFI.xcframework"); |
| 349 | let swift_target = path.join("Sources/MatrixRustSDK"); |
| 350 | if framework_target.exists() { |
| 351 | remove_dir_all(&framework_target)?; |
| 352 | } |
| 353 | if swift_target.exists() { |
| 354 | remove_dir_all(&swift_target)?; |
| 355 | } |
| 356 | create_dir_all(&framework_target)?; |
| 357 | create_dir_all(&swift_target)?; |
| 358 | |
| 359 | let copy_options = fs_extra::dir::CopyOptions { content_only: true, ..Default::default() }; |
| 360 | |
| 361 | fs_extra::dir::copy(&xcframework_path, &framework_target, ©_options)?; |
| 362 | fs_extra::dir::copy(&swift_dir, &swift_target, ©_options)?; |
| 363 | } |
| 364 | |
| 365 | println!("-- All done and hunky dory. Enjoy!"); |
| 366 | |
| 367 | Ok(()) |
| 368 | } |
| 369 | |
| 370 | /// Builds the SDK for the specified targets and profile. |
| 371 | fn build_targets( |
| 372 | targets: Vec<&Target>, |
| 373 | profile: &str, |
| 374 | sequentially: bool, |
| 375 | ios_deployment_target: Option<&str>, |
| 376 | watchos_deployment_target: Option<&str>, |
| 377 | ) -> Result<HashMap<Platform, Vec<Utf8PathBuf>>> { |
| 378 | let sh = sh(); |
| 379 | |
| 380 | // Note: `push_env` stores environment variables and returns a RAII guard that |
| 381 | // will restore the environment variable to its previous value when dropped. |
| 382 | let _env_guard1 = |
| 383 | sh.push_env("CARGO_TARGET_AARCH64_APPLE_IOS_RUSTFLAGS", "-Clinker=/usr/bin/clang"); |
| 384 | let _env_guard2 = sh.push_env("AARCH64_APPLE_IOS_CC", "/usr/bin/clang"); |
| 385 | let _env_guard3 = |
| 386 | ios_deployment_target.map(|target| sh.push_env("IPHONEOS_DEPLOYMENT_TARGET", target)); |
| 387 | let _env_guard4 = |
| 388 | watchos_deployment_target.map(|target| sh.push_env("WATCHOS_DEPLOYMENT_TARGET", target)); |
| 389 | |
| 390 | if sequentially { |
| 391 | for target in &targets { |
| 392 | let triple = target.triple; |
| 393 | |
| 394 | println!("-- Building for {}", target.description); |
| 395 | if target.status == TargetStatus::TopTier { |
| 396 | cmd!(sh, "rustup run stable cargo build -p matrix-sdk-ffi --target {triple} --profile {profile} --features {FFI_FEATURES}") |
| 397 | .run()?; |
| 398 | } else { |
| 399 | cmd!(sh, "rustup run nightly cargo build -p matrix-sdk-ffi -Zbuild-std --target {triple} --profile {profile} --features {FFI_FEATURES}") |
| 400 | .run()?; |
| 401 | } |
| 402 | } |
| 403 | } else { |
| 404 | let (stable_targets, tier3_targets): (Vec<&Target>, Vec<&Target>) = |
| 405 | targets.iter().partition(|t| t.status == TargetStatus::TopTier); |
| 406 | |
| 407 | if !stable_targets.is_empty() { |
| 408 | let triples = stable_targets.iter().map(|target| target.triple).collect::<Vec<_>>(); |
| 409 | let mut cmd = cmd!(sh, "rustup run stable cargo build -p matrix-sdk-ffi"); |
| 410 | for triple in &triples { |
| 411 | cmd = cmd.arg("--target").arg(triple); |
| 412 | } |
| 413 | cmd = cmd.arg("--profile").arg(profile).arg("--features").arg(FFI_FEATURES); |
| 414 | |
| 415 | println!("-- Building for {} targets", triples.len()); |
| 416 | cmd.run()?; |
| 417 | } |
| 418 | |
| 419 | if !tier3_targets.is_empty() { |
| 420 | let triples = tier3_targets.iter().map(|target| target.triple).collect::<Vec<_>>(); |
| 421 | let mut cmd = cmd!(sh, "rustup run nightly cargo build -p matrix-sdk-ffi -Zbuild-std"); |
| 422 | for triple in &triples { |
| 423 | cmd = cmd.arg("--target").arg(triple); |
| 424 | } |
| 425 | cmd = cmd.arg("--profile").arg(profile).arg("--features").arg(FFI_FEATURES); |
| 426 | |
| 427 | println!("-- Building for {} targets with nightly -Zbuild-std", triples.len()); |
| 428 | cmd.run()?; |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | // a hashmap of platform to array, where each array contains all the paths for |
| 433 | // that platform. |
| 434 | let mut platform_build_paths = HashMap::new(); |
| 435 | for target in targets { |
| 436 | let path = build_path_for_target(target, profile)?; |
| 437 | let paths = platform_build_paths.entry(target.platform.clone()).or_insert_with(Vec::new); |
| 438 | paths.push(path); |
| 439 | } |
| 440 | |
| 441 | Ok(platform_build_paths) |
| 442 | } |
| 443 | |
| 444 | /// The path of the built library for a specific target and profile. |
| 445 | fn build_path_for_target(target: &Target, profile: &str) -> Result<Utf8PathBuf> { |
| 446 | // The builtin dev profile has its files stored under target/debug, all |
| 447 | // other targets have matching directory names |
| 448 | let profile_dir_name = if profile == "dev" { "debug" } else { profile }; |
| 449 | Ok(workspace::target_path()?.join(target.triple).join(profile_dir_name).join(FFI_LIBRARY_NAME)) |
| 450 | } |
| 451 | |
| 452 | /// Lipo's together the libraries for each platform into a single library. |
| 453 | fn lipo_platform_libraries( |
| 454 | platform_build_paths: &HashMap<Platform, Vec<Utf8PathBuf>>, |
| 455 | generated_dir: &Utf8Path, |
| 456 | ) -> Result<Vec<Utf8PathBuf>> { |
| 457 | let mut libs = Vec::new(); |
| 458 | let sh = sh(); |
| 459 | for platform in platform_build_paths.keys() { |
| 460 | let paths = platform_build_paths.get(platform).unwrap(); |
| 461 | |
| 462 | if paths.len() == 1 { |
| 463 | libs.push(paths[0].clone()); |
| 464 | continue; |
| 465 | } |
| 466 | |
| 467 | let output_folder = generated_dir.join("lipo").join(platform.lib_folder_name()); |
| 468 | create_dir_all(&output_folder)?; |
| 469 | |
| 470 | let output_path = output_folder.join(FFI_LIBRARY_NAME); |
| 471 | let mut cmd = cmd!(sh, "lipo -create"); |
| 472 | for path in paths { |
| 473 | cmd = cmd.arg(path); |
| 474 | } |
| 475 | cmd = cmd.arg("-output").arg(&output_path); |
| 476 | |
| 477 | println!("-- Running Lipo for {}", platform.as_str()); |
| 478 | cmd.run()?; |
| 479 | |
| 480 | libs.push(output_path); |
| 481 | } |
| 482 | Ok(libs) |
| 483 | } |
| 484 | |
| 485 | /// Moves all files of the specified file extension from one directory into |
| 486 | /// another. |
| 487 | fn move_files(extension: &str, source: &Utf8Path, destination: &Utf8Path) -> Result<()> { |
| 488 | for entry in source.read_dir_utf8()? { |
| 489 | let entry = entry?; |
| 490 | |
| 491 | if entry.file_type()?.is_file() { |
| 492 | let path = entry.path(); |
| 493 | if path.extension() == Some(extension) { |
| 494 | let file_name = path.file_name().expect("Failed to get file name"); |
| 495 | rename(path, destination.join(file_name)).expect("Failed to move swift file"); |
| 496 | } |
| 497 | } |
| 498 | } |
| 499 | Ok(()) |
| 500 | } |
| 501 | |
| 502 | /// Updates all the swift files in the given directory to import the same module |
| 503 | /// that gets defined by the `consolidate_modulemap_files` function. |
| 504 | fn update_swift_module_imports(directory: &Utf8Path) -> Result<()> { |
| 505 | let regex = regex::Regex::new(r"#if canImport\(\w+FFI\)\nimport \w+FFI\n#endif")?; |
| 506 | |
| 507 | for entry in directory.read_dir_utf8()? { |
| 508 | let entry = entry?; |
| 509 | if entry.file_type()?.is_file() { |
| 510 | let path = entry.path(); |
| 511 | if path.extension() == Some("swift") { |
| 512 | let contents = std::fs::read_to_string(path)?; |
| 513 | let new_contents = regex.replace_all( |
| 514 | &contents, |
| 515 | "#if canImport(MatrixSDKFFI)\nimport MatrixSDKFFI\n#endif", |
| 516 | ); |
| 517 | if new_contents != contents { |
| 518 | std::fs::write(path, new_contents.as_ref())?; |
| 519 | } |
| 520 | } |
| 521 | } |
| 522 | } |
| 523 | |
| 524 | Ok(()) |
| 525 | } |
| 526 | |
| 527 | /// Consolidates the modulemap files found in the source directory into a single |
| 528 | /// `module.modulemap` file in the destination directory. |
| 529 | /// |
| 530 | /// The first modulemap file found is used as the base and all `header` |
| 531 | /// directives from the remaining modulemap files are spliced in after the |
| 532 | /// base's own header line. All other content in the base (e.g. `export *`, |
| 533 | /// `use` directives, and any future additions) is preserved verbatim. |
| 534 | fn consolidate_modulemap_files(source: &Utf8Path, destination: &Utf8Path) -> Result<()> { |
| 535 | let mut base_contents: Option<String> = None; |
| 536 | let mut extra_headers: Vec<String> = Vec::new(); |
| 537 | |
| 538 | for entry in source.read_dir_utf8()? { |
| 539 | let entry = entry?; |
| 540 | |
| 541 | if entry.file_type()?.is_file() { |
| 542 | let path = entry.path(); |
| 543 | if path.extension() == Some("modulemap") { |
| 544 | let contents = std::fs::read_to_string(path)?; |
| 545 | if base_contents.is_none() { |
| 546 | base_contents = Some(contents); |
| 547 | } else { |
| 548 | for line in contents.lines() { |
| 549 | if line.trim().starts_with("header ") |
| 550 | && !extra_headers.contains(&line.to_string()) |
| 551 | { |
| 552 | extra_headers.push(line.to_string()); |
| 553 | } |
| 554 | } |
| 555 | } |
| 556 | remove_file(path)?; |
| 557 | } |
| 558 | } |
| 559 | } |
| 560 | |
| 561 | let base = base_contents.expect("No modulemap files found"); |
| 562 | |
| 563 | // Rebuild the base line-by-line, renaming the module to MatrixSDKFFI and |
| 564 | // inserting the extra headers immediately after the base's own header line. |
| 565 | let mut lines: Vec<String> = Vec::new(); |
| 566 | let mut last_header_position: Option<usize> = None; |
| 567 | |
| 568 | for line in base.lines() { |
| 569 | if line.starts_with("module ") { |
| 570 | lines.push("module MatrixSDKFFI {".to_string()); |
| 571 | } else { |
| 572 | if line.trim().starts_with("header ") { |
| 573 | last_header_position = Some(lines.len()); |
| 574 | } |
| 575 | lines.push(line.to_string()); |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | if let Some(last_header_position) = last_header_position { |
| 580 | lines.splice(last_header_position + 1..last_header_position + 1, extra_headers); |
| 581 | } |
| 582 | |
| 583 | let modulemap = lines.join("\n") + "\n"; |
| 584 | std::fs::write(destination.join("module.modulemap"), modulemap)?; |
| 585 | Ok(()) |
| 586 | } |