diff --git a/Cargo.toml b/Cargo.toml index 2e94189..3daea86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xcframework" -version = "0.1.0-alpha.6" +version = "0.1.0" edition = "2021" license = "MIT" repository = "https://github.com/human-solutions/xcframework" @@ -12,14 +12,16 @@ categories = ["development-tools::cargo-plugins", "development-tools::ffi"] [dependencies] anyhow = "1.0" -cargo_metadata = { version = "0.18", features = ["builder"] } -camino = "1.1" +camino-fs = { git = "https://github.com/human-solutions/camino-fs", tag = "v0.1.3", features = [ + "serde", +] } +# camino-fs = { path = "../camino-fs", features = ["serde"] } +cargo_metadata = { version = "0.19", features = ["builder"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" yansi = "1.0" fs-err = "3.0" zip-extensions = { version = "0.8", default-features = false } -fs_extra = "1.2" xshell = "0.2.6" glob = "0.3.1" xflags = "0.3" diff --git a/README.md b/README.md index b3a06ff..ea603f4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ > **⚠️ WARNING** > -> This is work in progress and is not ready for use +> This has not yet been thoroughly tested. Use at your own risk. A Cargo plugin and library for building Apple XCFrameworks from Rust libraries @@ -46,7 +46,7 @@ The built XCFramework is named after the top-level module name declared in the ` A typical such file looks like this: ```cpp - The XCFramework will be named 'MyModuleName.xcframework' + // The XCFramework will be named 'MyModuleName.xcframework' framework module MyModuleName { // a header file in the same directory as the modulemap header "mylib.h" @@ -62,45 +62,55 @@ Cargo.toml parameters in section `[package.metadata.xcframework]`. # Note that the modulemap needs to be present in the directory because the # module name declared in it is used for the framework name. include-dir = "my-bin-name" + # The library type. Can be staticlib or cdylib # # Optional. This is only necessary if both library types are configured in the # [lib] sections `crate-type` parameter. Overridden by the command line parameter `--lib-type`. lib-type = "staticlib" + # Whether to zip the resulting XCFramework # # Optional. Defaults to true. -zip = true +zip = false + # Enable Cargo to compile the standard library itself as part of a crate graph compilation. # If enabled either run with `cargo +nightly xcframework`, set the default toolchain to nightly # or set run `rustup override set nightly` in the project directory. # # Optional. Defaults to false. Requires nightly. Only useful for staticlib's, ignored for cdylibs. build-std = false + # Whether to build for macOS # # Optional. Defaults to false. macOS = false + # The macOS target triples # # Optional. Defaults to ["x86_64-apple-darwin", "aarch64-apple-darwin"]. macOS-targets = ["x86_64-apple-darwin", "aarch64-apple-darwin"] + # Whether to build the simulator targets. Not used when building for macOS. # # Optional. Defaults to false simulators = false + # Whether to build for iOS # # Optional. Defaults to false. iOS = false + # The iOS target triples # # Optional. Defaults to ["aarch64-apple-ios"]. iOS-targets = ["aarch64-apple-ios"] + # The iOS simulator target triples. Only used if `simulators` and `iOS` are true. # # Optional. Defaults to ["aarch64-apple-ios-sim", "x86_64-apple-ios"] iOS-simulator-targets = ["aarch64-apple-ios-sim", "x86_64-apple-ios"] + # If there is interest, watchOS and tvOS can be added as well. ``` diff --git a/examples/end-to-end/mymath-lib/Cargo.toml b/examples/end-to-end/mymath-lib/Cargo.toml index 3572ffc..70bd25d 100644 --- a/examples/end-to-end/mymath-lib/Cargo.toml +++ b/examples/end-to-end/mymath-lib/Cargo.toml @@ -8,7 +8,7 @@ name = "mymath" crate-type = ["staticlib", "cdylib"] [package.metadata.xcframework] -lib-type = "staticlib" +lib-type = "cdylib" include-dir = "include" iOS = true macOS = true diff --git a/examples/end-to-end/swift-exe/Package.swift b/examples/end-to-end/swift-exe/Package.swift index 9e52a63..a18aca4 100644 --- a/examples/end-to-end/swift-exe/Package.swift +++ b/examples/end-to-end/swift-exe/Package.swift @@ -1,20 +1,21 @@ // swift-tools-version:5.7 import PackageDescription + let package = Package( - name: "SwiftExe", - products: [ - .library( - name: "MyMath", - targets: ["MyMath"]), - ], - dependencies: [], - targets: [ - .executableTarget( - name: "swift-cmd", - dependencies: ["MyMath"]), - .binaryTarget( - name: "MyMath", - path: "../mymath-lib/target/MyMath.xcframework.zip" - ), - ] + name: "SwiftExe", + products: [ + .library( + name: "MyMath", + targets: ["MyMath"]) + ], + dependencies: [], + targets: [ + .executableTarget( + name: "swift-cmd", + dependencies: ["MyMath"]), + .binaryTarget( + name: "MyMath", + path: "../mymath-lib/target/MyMath.xcframework" + ), + ] ) diff --git a/src/cmd/cargo.rs b/src/cmd/cargo.rs index cadbd5f..1b8ef13 100644 --- a/src/cmd/cargo.rs +++ b/src/cmd/cargo.rs @@ -6,6 +6,7 @@ pub fn build(conf: &Configuration) -> Result<()> { let mut args: Vec = vec![]; args.push("build".into()); + args.push("--color=always".into()); if conf.target_dir != "target" { args.push(format!("--target-dir={}", conf.target_dir)); diff --git a/src/cmd/modulemap.rs b/src/cmd/modulemap.rs index 6bc160f..c5a5550 100644 --- a/src/cmd/modulemap.rs +++ b/src/cmd/modulemap.rs @@ -2,11 +2,20 @@ use std::io; use crate::conf::Configuration; use anyhow::{bail, Context, Result}; -use fs_err::File; +use camino_fs::*; +use std::fs::File; pub fn get_module_name(conf: &Configuration) -> Result { - let mm = conf.cargo_section.include_dir.join("module.modulemap"); - let file = File::open(&mm)?; + let mm_files = ls_modulemap_files(&conf.cargo_section.include_dir)?; + if mm_files.len() != 1 { + bail!( + "Expected one modulemap file in include directory, found {count}: {mm_files:?} in {dir}", + count = mm_files.len(), + dir = conf.cargo_section.include_dir + ); + } + let mm = &mm_files[0]; + let file = File::open(mm)?; let content = io::read_to_string(&file)?; parse_module_name(&content).context(format!( @@ -32,3 +41,11 @@ fn parse_module_name(content: &str) -> Result { } Ok(module.to_string()) } + +fn ls_modulemap_files(dir: &Utf8Path) -> Result> { + Ok(dir + .ls() + .files() + .filter(|path| path.extension().map_or(false, |ext| ext == "modulemap")) + .collect()) +} diff --git a/src/conf/args.rs b/src/conf/args.rs index 31d38a0..c225aa3 100644 --- a/src/conf/args.rs +++ b/src/conf/args.rs @@ -1,4 +1,4 @@ -use camino::Utf8PathBuf; +use camino_fs::Utf8PathBuf; use super::LibType; diff --git a/src/conf/configuration.rs b/src/conf/configuration.rs index 2039dba..0b4be01 100644 --- a/src/conf/configuration.rs +++ b/src/conf/configuration.rs @@ -1,9 +1,9 @@ use std::cell::RefCell; use crate::cmd::modulemap; -use anyhow::{bail, Result}; -use camino::Utf8PathBuf; -use cargo_metadata::MetadataCommand; +use anyhow::{anyhow, bail, Context, Result}; +use camino_fs::Utf8PathBuf; +use cargo_metadata::{Metadata, MetadataCommand, Package, TargetKind}; use super::{CliArgs, LibType, XCFrameworkConfiguration}; @@ -23,52 +23,23 @@ pub struct Configuration { } impl Configuration { - pub fn load(mut cli: CliArgs) -> Result { - let manifest_path = cli - .manifest_path - .clone() - .unwrap_or_else(|| Utf8PathBuf::from("Cargo.toml")); - let mut dir = manifest_path.clone(); - dir.pop(); - - let target_dir = cli.target_dir.clone().unwrap_or_else(|| dir.join("target")); + pub fn new( + metadata: &Metadata, + package: &Package, + mut cli: CliArgs, + xc_conf: XCFrameworkConfiguration, + ) -> Result { + // Use the target directory from the CLI, or the one from the Cargo.toml + let target_dir = cli + .target_dir + .as_ref() + .unwrap_or(&metadata.target_directory) + .clone(); let build_dir = target_dir.join("xcframework"); - - let metadata = MetadataCommand::new().manifest_path(manifest_path).exec()?; - - let Some(package) = metadata.root_package() else { - anyhow::bail!("Could not find root package in metadata"); - }; - - let staticlib = package.targets.iter().find(|t| { - t.kind.contains(&"staticlib".to_string()) || t.kind.contains(&"staticlib".to_string()) - }); - let dylib = package.targets.iter().find(|t| { - t.kind.contains(&"cdylib".to_string()) || t.kind.contains(&"cdylib".to_string()) - }); - - let xc_conf = XCFrameworkConfiguration::parse(&package.metadata, &dir)?; - let wanted_lib_type = cli.lib_type.clone().or_else(|| xc_conf.lib_type.clone()); - use LibType::*; - let (lib_type, target) = match (staticlib, dylib, wanted_lib_type) { - (Some(staticlib), None, None) => (StaticLib, staticlib), - (Some(staticlib), _, Some(StaticLib)) => (StaticLib, staticlib), - (Some(_staticlib), None, Some(CDyLib)) => { - bail!("Please add 'cdylib' to '[lib] crate-type' in Cargo.toml") - } - (None, Some(dylib), None) => (CDyLib, dylib), - (_, Some(dylib), Some(CDyLib)) => (CDyLib, dylib), - (_, Some(_dylib), Some(StaticLib)) => { - bail!("Please add 'staticlib' to '[lib] crate-type' in Cargo.toml") - } - (Some(_), Some(_), None) => { - bail!("Please set '[package.metadata.xcframework] lib-type' in Cargo.toml") - } - (None, None, _) => bail!("Missing '[lib] crate-type' in Cargo.toml"), - }; + let (lib_type, target) = get_libtype(package, wanted_lib_type)?; if xc_conf.build_std && lib_type == LibType::StaticLib { let already_set = cli @@ -84,7 +55,6 @@ impl Configuration { } } } - Ok(Self { cargo_section: xc_conf, cli, @@ -96,6 +66,38 @@ impl Configuration { }) } + pub fn load(cli: CliArgs) -> Result { + let manifest_path = cli + .manifest_path + .clone() + .unwrap_or_else(|| Utf8PathBuf::from("Cargo.toml")); + let mut dir = manifest_path.clone(); + dir.pop(); + + let metadata = MetadataCommand::new().manifest_path(manifest_path).exec()?; + + let package = if let Some(package) = &cli.package { + metadata + .workspace_packages() + .iter() + .find(|p| &p.name == package) + .ok_or(anyhow!("Could not find package '{package}'"))? + } else { + metadata + .root_package() + .ok_or(anyhow!("Could not find root package in metadata"))? + }; + + let Some(section) = package.metadata.get("xcframework") else { + bail!("Missing [package.metadata.xcframework] section in Cargo.toml"); + }; + + let xc_conf = XCFrameworkConfiguration::parse(section, &dir, true) + .context("Error in Cargo.toml section [package.metadata.xcframework]")?; + + Self::new(&metadata, package, cli, xc_conf) + } + pub fn module_name(&self) -> Result { let name = self.module_name.borrow().clone(); if let Some(name) = name { @@ -115,3 +117,33 @@ impl Configuration { } } } + +fn get_libtype( + package: &Package, + libtype: Option, +) -> Result<(LibType, &cargo_metadata::Target)> { + let staticlib = package.targets.iter().find(|t| { + t.kind.contains(&TargetKind::StaticLib) || t.kind.contains(&TargetKind::StaticLib) + }); + let dylib = package + .targets + .iter() + .find(|t| t.kind.contains(&TargetKind::CDyLib) || t.kind.contains(&TargetKind::CDyLib)); + use LibType::*; + Ok(match (staticlib, dylib, libtype) { + (Some(staticlib), None, None) => (StaticLib, staticlib), + (Some(staticlib), _, Some(StaticLib)) => (StaticLib, staticlib), + (Some(_staticlib), None, Some(CDyLib)) => { + bail!("Please add 'cdylib' to '[lib] crate-type' in Cargo.toml") + } + (None, Some(dylib), None) => (CDyLib, dylib), + (_, Some(dylib), Some(CDyLib)) => (CDyLib, dylib), + (_, Some(_dylib), Some(StaticLib)) => { + bail!("Please add 'staticlib' to '[lib] crate-type' in Cargo.toml") + } + (Some(_), Some(_), None) => { + bail!("Please set '[package.metadata.xcframework] lib-type' in Cargo.toml") + } + (None, None, _) => bail!("Missing '[lib] crate-type' in Cargo.toml"), + }) +} diff --git a/src/conf/xcframework.rs b/src/conf/xcframework.rs index 0637156..566b456 100644 --- a/src/conf/xcframework.rs +++ b/src/conf/xcframework.rs @@ -1,8 +1,8 @@ #![allow(non_snake_case)] use super::Target; -use anyhow::{bail, Context, Result}; -use camino::{Utf8Path, Utf8PathBuf}; +use anyhow::{bail, Result}; +use camino_fs::*; use serde::Deserialize; use std::str::FromStr; @@ -38,6 +38,9 @@ impl LibType { #[serde(rename_all = "kebab-case")] pub struct XCFrameworkConfiguration { /// The include directory containing the headers and the module.modulemap file + /// This is set to default because sometimes it needs to be set manually after + /// parsing the config. + #[serde(default)] pub include_dir: Utf8PathBuf, /// The library type (staticlib or cdylib) @@ -72,7 +75,7 @@ pub struct XCFrameworkConfiguration { } pub fn zip_default() -> bool { - true + false } impl XCFrameworkConfiguration { @@ -92,22 +95,25 @@ impl XCFrameworkConfiguration { /// Parses the [package.metadata.xcframework] section of the Cargo.toml /// and updates the headers_directory to be relative to current working directory - pub fn parse(metadata: &serde_json::Value, dir: &Utf8Path) -> Result { - if let Some(xcfr) = metadata.get("xcframework") { - Self::parse_xcframework(xcfr, dir) - .context("Error in Cargo.toml section [package.metadata.xcframework]") + pub fn parse( + section: &serde_json::Value, + package_dir: &Utf8Path, + validate: bool, + ) -> Result { + let mut me = serde_json::from_value::(section.clone())?; + me.include_dir = package_dir.join(me.include_dir); + if validate { + me.validated() } else { - bail!("Missing [package.metadata.xcframework] section in Cargo.toml"); + Ok(me) } } - fn parse_xcframework(xcfr: &serde_json::Value, dir: &Utf8Path) -> Result { - let mut me = serde_json::from_value::(xcfr.clone())?; - me.include_dir = dir.join(me.include_dir); - me.validated() - } - fn validated(self) -> Result { + if self.include_dir.as_str().is_empty() { + bail!("The include-dir field is required"); + } + if !self.include_dir.exists() { bail!("The include-dir '{}' does not exist", self.include_dir); } diff --git a/src/core.rs b/src/core.rs index b1e6718..f3eb9d4 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,8 +1,7 @@ use std::collections::HashMap; use anyhow::{Context, Ok}; -use camino::Utf8PathBuf; -use fs_err as fs; +use camino_fs::*; use platform::ApplePlatform; use xshell::{cmd, Shell}; @@ -24,7 +23,7 @@ pub fn lipo_create_platform_libraries( output_dir: &Utf8PathBuf, ) -> anyhow::Result> { let sh = Shell::new()?; - fs::create_dir_all(output_dir)?; + output_dir.mkdirs()?; let mut libs = HashMap::new(); for (platform, paths) in platform_lib_paths.iter() { @@ -34,7 +33,7 @@ pub fn lipo_create_platform_libraries( continue; } let platform_dir = output_dir.join(format!("{:?}", platform)); - fs::create_dir_all(&platform_dir)?; + platform_dir.mkdirs()?; let output_path = platform_dir.join(output_lib_name); let mut cmd = cmd!(sh, "lipo -create"); @@ -44,7 +43,7 @@ pub fn lipo_create_platform_libraries( cmd = cmd.arg("-output").arg(&output_path); println!("🍭 Running lipo create for platform: {platform:?} ..."); cmd.run()?; - println!("✅ Run lipo create success, platform: {platform:?}, output:\n{output_path:?}"); + println!("✅ Run lipo create success, platform: {platform:?}, output:\n{output_path}"); libs.insert(platform.clone(), output_path); } Ok(libs) @@ -60,7 +59,7 @@ pub fn wrap_as_framework( crate_type: &CrateType, lib_path: &Utf8PathBuf, header_paths: Vec, - module_paths: Vec, + module_path: Utf8PathBuf, bundle_name: &str, output_dir: &Utf8PathBuf, ) -> anyhow::Result { @@ -73,7 +72,7 @@ pub fn wrap_as_framework( let output_path = output_dir .join(format!("{:?}", platform)) .join(format!("{}{}", bundle_name, SUFFIX)); - fs::create_dir_all(&output_path)?; + output_path.mkdirs()?; let plist = plist::InfoPlistBuilder::new(bundle_name, platform); let plist_path = output_path.join("Info.plist"); @@ -90,7 +89,7 @@ pub fn wrap_as_framework( .run()?; let to_binary = format!("{}/{}", &output_path, bundle_name); - fs::copy(lib_path.as_path(), to_binary)?; + lib_path.cp(to_binary)?; if let CrateType::Cdylib = crate_type { sh.cmd("install_name_tool") @@ -102,24 +101,16 @@ pub fn wrap_as_framework( .output()?; } - fs::create_dir_all(format!("{}/Headers", &output_path))?; - fs::create_dir_all(format!("{}/Modules", &output_path))?; + output_path.join("Headers").mkdirs()?; + output_path.join("Modules").mkdirs()?; for header_path in header_paths.iter() { let header_name = header_path.file_name().context("header path error")?; - fs::copy( - header_path, - format!("{}/Headers/{}", output_path, header_name), - )?; + header_path.cp(output_path.join("Headers").join(header_name))?; } - for module_path in module_paths.iter() { - let module_name = module_path.file_name().context("module path error")?; - fs::copy( - module_path, - format!("{}/Modules/{}", output_path, module_name), - )?; - } + let module_dest = output_path.join("Modules").join("module.modulemap"); + module_path.cp(module_dest)?; println!( "✅ Wrapped artifacts as framework success, output:\n{}", @@ -143,7 +134,7 @@ pub fn create_xcframework( let xcframework_path = output_dir.join(format!("{}{}", bundle_name, SUFFIX)); if xcframework_path.exists() { - fs::remove_dir_all(&xcframework_path)?; + xcframework_path.rm()?; } let mut cmd = sh.cmd("xcrun").args(["xcodebuild", "-create-xcframework"]); diff --git a/src/ext/mod.rs b/src/ext/mod.rs deleted file mode 100644 index d677c1d..0000000 --- a/src/ext/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod path; - -pub use path::PathBufExt; diff --git a/src/ext/path.rs b/src/ext/path.rs deleted file mode 100644 index 0f927f5..0000000 --- a/src/ext/path.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::path::Path; - -use anyhow::{Context, Result}; -use camino::Utf8PathBuf; -use fs_extra::dir::CopyOptions; - -pub trait PathBufExt { - fn resolve_home_dir(self) -> Result; - - fn create_dir_all_if_needed(&self) -> Result<()>; - - fn remove_dir_all_if_exists(&self) -> Result<()>; - - fn copy_dir>(&self, to: P) -> Result<()>; - - fn copy_dir_contents>(&self, to: P) -> Result<()>; -} - -impl PathBufExt for Utf8PathBuf { - fn create_dir_all_if_needed(&self) -> Result<()> { - if !self.exists() { - fs_err::create_dir_all(self)?; - } - Ok(()) - } - - fn resolve_home_dir(self) -> Result { - if self.starts_with("~") { - let home = std::env::var("HOME").context("Could not resolve $HOME")?; - let home = Utf8PathBuf::from(home); - Ok(home.join(self.strip_prefix("~").unwrap())) - } else { - Ok(self) - } - } - - fn remove_dir_all_if_exists(&self) -> Result<()> { - if self.exists() { - fs_err::remove_dir_all(self)?; - } - Ok(()) - } - - fn copy_dir>(&self, to: P) -> Result<()> { - let to_path = to.as_ref(); - if !to_path.exists() { - fs_err::create_dir_all(to_path)?; - } - fs_extra::dir::copy(self, to_path, &CopyOptions::new())?; - - Ok(()) - } - - fn copy_dir_contents>(&self, to: P) -> Result<()> { - let to_path = to.as_ref(); - if !to_path.exists() { - fs_err::create_dir_all(to_path)?; - } - let options = CopyOptions::new().content_only(true); - fs_extra::dir::copy(self, to_path, &options)?; - - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs index cca2a31..d7f892d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,20 +2,17 @@ mod cmd; mod conf; pub mod core; -pub mod ext; use core::platform::{ApplePlatform, Environment}; use std::collections::HashMap; -use crate::conf::Configuration; +pub use crate::conf::Configuration; use anyhow::{Context, Result}; -use camino::Utf8PathBuf; +use camino_fs::*; use cmd::cargo; pub use conf::CliArgs; use conf::Target; -pub use conf::XCFrameworkConfiguration; -use ext::PathBufExt; -use fs_err as fs; +pub use conf::{LibType, XCFrameworkConfiguration}; #[derive(Debug, PartialEq, Eq)] pub struct Produced { @@ -24,19 +21,21 @@ pub struct Produced { pub is_zipped: bool, } -pub fn build(cli: CliArgs) -> Result { - let conf = Configuration::load(cli).context("loading configuration")?; +pub fn build_from_cli(cli: CliArgs) -> Result { + let config = Configuration::load(cli).context("loading configuration")?; - conf.build_dir - .remove_dir_all_if_exists() - .context("cleaning build dir")?; + crate::build(&config) +} + +pub fn build(conf: &Configuration) -> Result { + conf.build_dir.rm().context("cleaning build dir")?; - cargo::build(&conf).context("running cargo build")?; + cargo::build(conf).context("running cargo build")?; let libs = { let conf = &conf; let libs_dir = conf.build_dir.join("libs"); - fs::create_dir_all(&libs_dir)?; + libs_dir.mkdirs()?; let mut platform_lib_paths = HashMap::new(); if conf.cargo_section.iOS { @@ -64,56 +63,56 @@ pub fn build(cli: CliArgs) -> Result { } .context("lipo: assembling libraries")?; - let bundle_name = conf.module_name()?; + let bundle_name = conf.module_name().context("finding module name")?; + let crate_type = match conf.lib_type { conf::LibType::StaticLib => &core::CrateType::Staticlib, conf::LibType::CDyLib => &core::CrateType::Cdylib, }; + let framework_paths = libs .into_iter() .map(|(platform, lib_path)| { let include_dir = &conf.cargo_section.include_dir; let header_paths = get_header_paths(include_dir)?; - let module_paths = get_module_paths(include_dir)?; + let module_path = get_module_path(include_dir)?; let frameworks_dir = conf.target_dir.join("frameworks"); - std::fs::create_dir_all(&frameworks_dir)?; + frameworks_dir.mkdirs()?; core::wrap_as_framework( platform, crate_type, &lib_path, header_paths, - module_paths, + module_path, &bundle_name, &frameworks_dir, ) }) - .collect::>>()?; + .collect::>>() + .context("collecting framework paths")?; let xcframework_path = - crate::core::create_xcframework(framework_paths, &conf.module_name()?, &conf.build_dir)?; + crate::core::create_xcframework(framework_paths, &conf.module_name()?, &conf.build_dir) + .context("creating xcframework")?; + let module_name = conf.module_name()?; - let (path, is_zipped) = if conf.cargo_section.zip { - ( - core::compress_xcframework(None, &xcframework_path, None, &conf.target_dir)?, - true, - ) + let path = if conf.cargo_section.zip { + core::compress_xcframework(None, &xcframework_path, None, &conf.target_dir)? } else { let to = conf.target_dir.join(format!("{module_name}.xcframework")); - if to.exists() { - fs::remove_dir_all(&to)?; - } - fs::rename(xcframework_path, &to)?; - (to, false) + to.rm()?; + xcframework_path.mv(&to)?; + to }; - conf.build_dir.remove_dir_all_if_exists()?; + conf.build_dir.rm().context("cleaning build dir")?; Ok(Produced { module_name, path, - is_zipped, + is_zipped: conf.cargo_section.zip, }) } @@ -131,16 +130,15 @@ fn get_header_paths(include_dir: &Utf8PathBuf) -> Result> { Ok(header_paths) } -fn get_module_paths(include_dir: &Utf8PathBuf) -> Result> { - let mut module_paths = Vec::new(); - let pattern = format!("{}/**/*.modulemap", include_dir); - for entry in glob::glob(&pattern)? { - match entry { - Ok(path) => module_paths.push(Utf8PathBuf::from_path_buf(path).unwrap()), - Err(e) => println!("{:?}", e), - } +fn get_module_path(include_dir: &Utf8PathBuf) -> Result { + let pattern = format!("{include_dir}/**/*.modulemap"); + let mut glob = glob::glob(&pattern)?; + let module_path = glob.next().context("modulemap not found")??; + if glob.next().is_some() { + anyhow::bail!("multiple modulemaps found"); } - Ok(module_paths) + + Ok(Utf8PathBuf::from_path_buf(module_path).unwrap()) } fn lib_paths_for_targets(conf: &Configuration, targets: &[Target]) -> Result> { diff --git a/src/main.rs b/src/main.rs index 2fd6fc7..0135cc6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,9 @@ -use xcframework::{build, CliArgs}; +use xcframework::CliArgs; fn main() { let args = CliArgs::from_env_or_exit(); - if let Err(e) = crate::build(args) { + if let Err(e) = xcframework::build_from_cli(args) { eprintln!("{:?}", e); std::process::exit(1); } diff --git a/tests/test.rs b/tests/test.rs index 8c28e5f..533be1a 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,9 +1,6 @@ use anyhow::Result; -use camino::Utf8PathBuf; -use fs_err as fs; -use std::path::{Path, PathBuf}; +use camino_fs::*; use std::process::Command; -use xcframework::ext::PathBufExt; use xcframework::CliArgs; fn args(vec: &[&str]) -> CliArgs { @@ -16,17 +13,15 @@ fn test_hello() { let cli = args(&["--quiet", "--manifest-path", "tests/project/Cargo.toml"]); - let produced = xcframework::build(cli).unwrap(); - assert!(produced.is_zipped); + let produced = xcframework::build_from_cli(cli).unwrap(); + assert!(!produced.is_zipped); assert_eq!(produced.module_name, "HelloTest"); } -fn create_output_dir(subfolder: &str) -> PathBuf { - let tmp_dir = PathBuf::from("tests").join("temp").join(subfolder); - if tmp_dir.exists() { - fs::remove_dir_all(&tmp_dir).unwrap(); - } - fs::create_dir_all(&tmp_dir).unwrap(); +fn create_output_dir(subfolder: &str) -> Utf8PathBuf { + let tmp_dir = Utf8PathBuf::from("tests").join("temp").join(subfolder); + tmp_dir.rm().unwrap(); + tmp_dir.mkdirs().unwrap(); tmp_dir } @@ -35,7 +30,7 @@ fn end_to_end_static() { let out_dir = create_output_dir("static"); let target_dir = out_dir.join("mymath-lib/target"); - fs::create_dir_all(&target_dir).unwrap(); + target_dir.mkdirs().unwrap(); let cli = args(&[ "--quiet", @@ -44,11 +39,11 @@ fn end_to_end_static() { "--lib-type", "staticlib", "--target-dir", - target_dir.to_str().unwrap(), + target_dir.as_str(), ]); - let produced = xcframework::build(cli).unwrap(); - assert!(produced.is_zipped); + let produced = xcframework::build_from_cli(cli).unwrap(); + assert!(!produced.is_zipped); assert_eq!(produced.module_name, "MyMath"); let swift_dir = cp_swift_exe(&out_dir).unwrap(); @@ -62,7 +57,7 @@ fn end_to_end_static() { let stdout = String::from_utf8_lossy(&cmd.stdout); let stderr = String::from_utf8_lossy(&cmd.stderr); eprintln!("{stderr}"); - assert!(stderr.contains("Build complete!")); + assert!(cmd.status.success()); assert_eq!("MyMath.rust_add(4 + 2) = 6\n", stdout); } @@ -71,7 +66,7 @@ fn end_to_end_dynamic() { let out_dir = create_output_dir("dynamic"); let target_dir = out_dir.join("mymath-lib/target"); - fs::create_dir_all(&target_dir).unwrap(); + target_dir.mkdirs().unwrap(); let cli = args(&[ "--quiet", @@ -80,11 +75,11 @@ fn end_to_end_dynamic() { "--lib-type", "cdylib", "--target-dir", - target_dir.to_str().unwrap(), + target_dir.as_str(), ]); - let produced = xcframework::build(cli).unwrap(); - assert!(produced.is_zipped); + let produced = xcframework::build_from_cli(cli).unwrap(); + assert!(!produced.is_zipped); assert_eq!(produced.module_name, "MyMath"); let swift_dir = cp_swift_exe(out_dir.as_path()).unwrap(); @@ -105,16 +100,16 @@ fn end_to_end_dynamic() { fn multi_platform_static() { let out_dir = create_output_dir("multi-platform-static"); let target_dir = out_dir.join("mymath-lib/target"); - fs::create_dir_all(&target_dir).unwrap(); + target_dir.mkdirs().unwrap(); let cli = args(&[ "--manifest-path", "examples/multi-platform/mymath-lib/Cargo.toml", "--lib-type", "staticlib", "--target-dir", - target_dir.to_str().unwrap(), + target_dir.as_str(), ]); - let produced = xcframework::build(cli).unwrap(); + let produced = xcframework::build_from_cli(cli).unwrap(); assert_eq!(produced.module_name, "MyMath"); let tuist_workspace_dir = cp_tuist_workspace(out_dir.as_path()).unwrap(); let cmd = Command::new("tuist") @@ -134,16 +129,17 @@ fn multi_platform_static() { fn multi_platform_dynamic() { let out_dir = create_output_dir("multi-platform-dynamic"); let target_dir = out_dir.join("mymath-lib/target"); - fs::create_dir_all(&target_dir).unwrap(); + target_dir.mkdirs().unwrap(); + let cli = args(&[ "--manifest-path", "examples/multi-platform/mymath-lib/Cargo.toml", "--lib-type", "cdylib", "--target-dir", - target_dir.to_str().unwrap(), + target_dir.as_str(), ]); - let produced = xcframework::build(cli).unwrap(); + let produced = xcframework::build_from_cli(cli).unwrap(); assert_eq!(produced.module_name, "MyMath"); let tuist_workspace_dir = cp_tuist_workspace(out_dir.as_path()).unwrap(); let cmd = Command::new("tuist") @@ -158,25 +154,22 @@ fn multi_platform_dynamic() { } } -fn cp_swift_exe(dest: &Path) -> Result { +fn cp_swift_exe(dest: &Utf8Path) -> Result { + println!("dest: {:?}", dest); let from = Utf8PathBuf::from("examples/end-to-end/swift-exe"); - let dest = Utf8PathBuf::from_path_buf(dest.to_path_buf()).unwrap(); + let dest = dest.join("swift-exe"); + dest.mkdirs()?; - dest.create_dir_all_if_needed()?; - - fs_extra::dir::copy(from, &dest, &fs_extra::dir::CopyOptions::new())?; - let build_tmp = dest.join("swift-exe/.build"); - if build_tmp.exists() { - fs::remove_dir_all(build_tmp)?; - } - Ok(dest.join("swift-exe")) + from.cp(&dest)?; + let build_tmp = dest.join(".build"); + build_tmp.rm()?; + Ok(dest) } -fn cp_tuist_workspace(dest: &Path) -> Result { +fn cp_tuist_workspace(dest: &Utf8Path) -> Result { let from = Utf8PathBuf::from("examples/multi-platform/tuist-workspace"); - let dest = Utf8PathBuf::from_path_buf(dest.to_path_buf()).unwrap(); - dest.create_dir_all_if_needed()?; - fs_extra::dir::copy(from, &dest, &fs_extra::dir::CopyOptions::new())?; + dest.mkdirs()?; + from.cp(dest)?; Ok(dest.join("tuist-workspace")) }