From 3a556988bf7670b84a377c287501acdbc2c0b170 Mon Sep 17 00:00:00 2001 From: Antonius Naumann Date: Sun, 15 Oct 2023 17:31:00 +0200 Subject: [PATCH] Package all crates that depend on UniFFI when invoked in a workspace Close #7 --- Cargo.lock | 103 +++++++--------------------------------- Cargo.toml | 2 +- src/commands/package.rs | 75 +++++++++++++++++++++-------- src/console/error.rs | 60 +++++++++++++++++++++++ src/console/messages.rs | 23 ++++++++- src/lib.rs | 1 + src/metadata.rs | 49 +++++++++++++++++++ src/targets.rs | 36 ++------------ 8 files changed, 209 insertions(+), 140 deletions(-) create mode 100644 src/metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 1ffe017..1d27cce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -147,13 +147,13 @@ dependencies = [ "askama", "camino", "cargo_metadata 0.18.0", - "cargo_toml", "clap", "console", "convert_case", "dialoguer", "execute", "indicatif", + "itertools", "lazy_static", "nonempty", "serde", @@ -189,16 +189,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cargo_toml" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f9629bc6c4388ea699781dc988c2b99766d7679b151c81990b4fa1208fafd3" -dependencies = [ - "serde", - "toml 0.8.1", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -285,16 +275,16 @@ dependencies = [ ] [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "either" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "equivalent" -version = "1.0.1" +name = "encode_unicode" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "execute" @@ -366,12 +356,6 @@ dependencies = [ "scroll", ] -[[package]] -name = "hashbrown" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" - [[package]] name = "heck" version = "0.4.1" @@ -387,16 +371,6 @@ dependencies = [ "libm", ] -[[package]] -name = "indexmap" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "indicatif" version = "0.17.7" @@ -419,6 +393,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -622,15 +605,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - [[package]] name = "shell-words" version = "1.1.0" @@ -689,40 +663,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "typenum" version = "1.17.0" @@ -774,7 +714,7 @@ dependencies = [ "paste", "serde", "serde_json", - "toml 0.5.11", + "toml", "uniffi_meta", "uniffi_testing", "weedle2", @@ -970,12 +910,3 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "winnow" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index ba5d736..2eed85c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ exclude = ["/readme", "/.github", "/testing", "/swift-examples", "*-DRAFT.md"] [dependencies] camino = "1.1.6" -cargo_toml = "0.16.3" cargo_metadata = "0.18.0" clap = { version = "4.4.6", features = ["derive"] } convert_case = "0.6.0" @@ -43,5 +42,6 @@ uniffi_bindgen = { version = "0.24.3" } anyhow = "1.0.75" thiserror = "1.0.49" lazy_static = "1.4.0" +itertools = "0.11.0" [features] diff --git a/src/commands/package.rs b/src/commands/package.rs index 37b14f6..c130d0e 100644 --- a/src/commands/package.rs +++ b/src/commands/package.rs @@ -1,11 +1,10 @@ use std::fmt::Display; -use std::fs::read; use std::ops::Not; use std::path::PathBuf; use std::process::{Command, Stdio}; use camino::Utf8PathBuf; -use cargo_toml::Manifest; +use cargo_metadata::Package; use clap::ValueEnum; use convert_case::{Case, Casing}; use dialoguer::{Input, MultiSelect}; @@ -16,6 +15,7 @@ use crate::bindings::generate_bindings; use crate::console::*; use crate::console::{run_step, run_step_with_commands}; use crate::lib_type::LibType; +use crate::metadata::{metadata, MetadataExt}; use crate::swiftpackage::{create_swiftpackage, recreate_output_dir}; use crate::targets::*; use crate::xcframework::create_xcframework; @@ -56,21 +56,60 @@ pub fn run( lib_type_arg: LibTypeArg, ) -> Result<()> { // TODO: Allow path as optional argument to take other directories than current directory - let manifest = Manifest::from_slice(&read("./Cargo.toml")?) - .expect("Could not find Cargo.toml in this directory!"); + let crates = metadata().uniffi_crates(); + + if crates.len() == 1 { + return run_for_crate( + crates[0], + platforms.clone(), + package_name, + &config, + mode, + lib_type_arg, + ); + } else if package_name.is_some() { + Err("Package name can only be specified when building a single crate!")?; + } - let lib = manifest - .lib + crates + .iter() + .map(|current_crate| { + info!(&config, "Packaging crate {}", current_crate.name); + run_for_crate( + current_crate, + platforms.clone(), + None, + &config, + mode, + lib_type_arg.clone(), + ) + }) + .filter_map(|result| result.err()) + .collect::() + .into() +} + +fn run_for_crate( + current_crate: &Package, + platforms: Option>, + package_name: Option, + config: &Config, + mode: Mode, + lib_type_arg: LibTypeArg, +) -> Result<()> { + let lib = current_crate + .targets + .iter() + .find(|t| t.kind.contains(&"lib".to_owned())) .ok_or("No library tag defined in Cargo.toml!")?; let lib_types = lib - .crate_type + .crate_types .iter() .filter_map(|t| t.parse().ok()) .collect::>(); - let lib_type = pick_lib_type(&lib_types, lib_type_arg.into(), &config)?; - let lib_name = lib.name.ok_or("No library name found in Cargo.toml!")?; + let lib_type = pick_lib_type(&lib_types, lib_type_arg.clone().into(), config)?; - let crate_name = manifest.package.unwrap().name.to_lowercase(); + let crate_name = current_crate.name.to_lowercase(); let package_name = package_name.unwrap_or_else(|| prompt_package_name(&crate_name, config.accept_all)); @@ -96,14 +135,14 @@ pub fn run( } for target in &targets { - build_with_output(target, &lib_name, mode, lib_type, &config)?; + build_with_output(target, &lib.name, mode, lib_type, config)?; } - generate_bindings_with_output(&targets, &lib_name, mode, lib_type, &config)?; + generate_bindings_with_output(&targets, &lib.name, mode, lib_type, config)?; recreate_output_dir(&package_name).expect("Could not create package output directory!"); - create_xcframework_with_output(&targets, &lib_name, &package_name, mode, lib_type, &config)?; - create_package_with_output(&package_name, &lib_name, &config)?; + create_xcframework_with_output(&targets, &lib.name, &package_name, mode, lib_type, config)?; + create_package_with_output(&package_name, &lib.name, config)?; Ok(()) } @@ -277,10 +316,8 @@ fn pick_lib_type( if let Some(suggested) = suggested { // TODO: Show part of Cargo.toml here to help user fix this - warn( - &format!("No matching library type for --lib-type {suggested} found in Cargo.toml.\n Building as {choosen} instead..."), - config, - ) + warning!(config, + "No matching library type for --lib-type {suggested} found in Cargo.toml.\n Building as {choosen} instead...") } Ok(choosen) } @@ -294,7 +331,7 @@ fn generate_bindings_with_output( ) -> Result<()> { run_step(config, "Generating Swift bindings...", || { let lib_file = library_file_name(lib_name, lib_type); - let target = target_dir(); + let target = metadata().target_dir(); let archs = targets .first() .ok_or("Could not generate UniFFI bindings: No target platform selected!")? diff --git a/src/console/error.rs b/src/console/error.rs index 5d37a25..cb08ad9 100644 --- a/src/console/error.rs +++ b/src/console/error.rs @@ -1,5 +1,8 @@ use std::fmt::Display; use std::io::{self, stderr, Write}; +use std::ops::Deref; + +use itertools::Itertools; #[derive(Debug)] enum ErrorMessage { @@ -7,6 +10,15 @@ enum ErrorMessage { Stderr(Vec), } +impl ErrorMessage { + fn into_bytes(self) -> Vec { + match self { + ErrorMessage::Simple(msg) => msg.into_bytes(), + ErrorMessage::Stderr(buf) => buf, + } + } +} + #[derive(Debug, thiserror::Error)] pub struct Error { message: ErrorMessage, @@ -44,6 +56,54 @@ impl From for Error { } } +pub(crate) struct Errors(Vec); + +impl Deref for Errors { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Errors { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl FromIterator for Errors { + fn from_iter>(iter: T) -> Self { + iter.into_iter().collect::>().into() + } +} + +impl IntoIterator for Errors { + type Item = Error; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl From for Result<()> { + fn from(value: Errors) -> Self { + if value.is_empty() { + Ok(()) + } else { + let message = ErrorMessage::Stderr( + value + .into_iter() + .map(|e| e.message) + .map(ErrorMessage::into_bytes) + .concat(), + ); + + Err(Error { message }) + } + } +} + impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.message { diff --git a/src/console/messages.rs b/src/console/messages.rs index ba669f4..277094e 100644 --- a/src/console/messages.rs +++ b/src/console/messages.rs @@ -3,11 +3,17 @@ use super::Config; use console::{style, Style}; /// Prints a formatted warning message to the console -pub fn warn(msg: &str, config: &Config) { +pub fn print_warning(msg: &str, config: &Config) { let style = Style::new().bold().yellow(); print_msg("!", msg, style, config) } +/// Prints a formatted info message to the console +pub fn print_info(msg: &str, config: &Config) { + let style = Style::new().bold().cyan(); + print_msg("ℹ", msg, style, config) +} + #[inline(always)] fn print_msg(tag: &str, msg: &str, s: Style, config: &Config) { if !config.silent { @@ -15,4 +21,17 @@ fn print_msg(tag: &str, msg: &str, s: Style, config: &Config) { } } -// TODO: macro to combine with format string +macro_rules! info { + ($config:expr, $($arg:tt)*) => { + $crate::console::messages::print_info(&format!($($arg)*), $config) + }; +} + +macro_rules! warning { + ($config:expr, $($arg:tt)*) => { + $crate::console::messages::print_warning(&format!($($arg)*), $config) + }; +} + +pub(crate) use info; +pub(crate) use warning; diff --git a/src/lib.rs b/src/lib.rs index f5f03b6..9199b94 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub(crate) mod console { mod bindings; mod lib_type; +mod metadata; mod swiftpackage; mod targets; mod templating; diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 0000000..6988950 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,49 @@ +use camino::Utf8Path; +use cargo_metadata::{Metadata, MetadataCommand, Package}; +use lazy_static::lazy_static; + +pub(crate) fn metadata() -> &'static Metadata { + lazy_static! { + static ref METADATA: Metadata = MetadataCommand::new() + .no_deps() + .other_options(["--offline".to_string()]) + .exec() + // TODO: Error handling + .unwrap(); + } + + &METADATA +} + +pub(crate) trait MetadataExt { + fn target_dir(&self) -> &Utf8Path; + fn uniffi_crates(&self) -> Vec<&Package>; +} + +impl MetadataExt for Metadata { + fn target_dir(&self) -> &Utf8Path { + let target_dir = self.target_directory.as_path(); + let relative = target_dir + // TODO: Error handling + .strip_prefix(std::env::current_dir().unwrap()) + .ok(); + + match relative { + Some(dir) => dir, + None => target_dir, + } + } + + /// Returns the package metadata for all crates that depend on UniFFI and are below, at or above the current working directory. + fn uniffi_crates(&self) -> Vec<&Package> { + let cwd = std::env::current_dir().unwrap(); + // TODO: also include crates that are above the current working directory + let crates: Vec<_> = self + .workspace_packages() + .into_iter() + .filter(|package| package.manifest_path.starts_with(&cwd)) + .collect(); + + crates + } +} diff --git a/src/targets.rs b/src/targets.rs index 6fe24ba..c6f93ae 100644 --- a/src/targets.rs +++ b/src/targets.rs @@ -1,12 +1,10 @@ use std::{fmt::Display, process::Command}; -use camino::{Utf8Path, Utf8PathBuf}; -use cargo_metadata::MetadataCommand; use execute::command; use nonempty::{nonempty, NonEmpty}; -use lazy_static::lazy_static; use crate::lib_type::LibType; +use crate::metadata::{metadata, MetadataExt}; pub trait TargetInfo { fn target(&self) -> Target; @@ -51,11 +49,7 @@ impl Target { self.architectures() .into_iter() - .map(|arch| { - command(format!( - "cargo build --target {arch} {flag}" - )) - }) + .map(|arch| command(format!("cargo build --target {arch} {flag}"))) .collect() } @@ -65,7 +59,7 @@ impl Target { Target::Universal { architectures, .. } => { let path = self.library_directory(mode); - let target = target_dir(); + let target = metadata().target_dir(); let target_name = library_file_name(lib_name, lib_type); let component_paths: Vec<_> = architectures .iter() @@ -141,7 +135,7 @@ impl Target { Mode::Release => "release", }; - let target = target_dir(); + let target = metadata().target_dir(); match self { Target::Single { architecture, .. } => format!("{target}/{architecture}/{mode}"), @@ -162,28 +156,6 @@ pub fn library_file_name(lib_name: &str, lib_type: LibType) -> String { format!("lib{}.{}", lib_name, lib_type.file_extension()) } -pub fn target_dir() -> &'static Utf8Path { - lazy_static! { - static ref TARGET_DIR: Utf8PathBuf = MetadataCommand::new() - .no_deps() - .other_options(["--offline".to_string()]) - .exec() - // TODO: Error handling - .unwrap() - .target_directory; - } - - let relative = TARGET_DIR - // TODO: Error handling - .strip_prefix(std::env::current_dir().unwrap()) - .ok(); - - match relative { - Some(dir) => dir, - None => &TARGET_DIR, - } -} - #[derive(Clone, Copy, Debug)] pub enum ApplePlatform { IOS,