diff --git a/Cargo.lock b/Cargo.lock index 141c2d7..415ffed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "anyhow", "cargo_metadata", "dunce", + "filetime", "fwdansi", "gumdrop", "is-terminal", @@ -66,6 +67,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "dunce" version = "1.0.4" @@ -94,6 +101,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "filetime" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "fruity__bbqsrc" version = "0.2.0" @@ -188,6 +207,17 @@ version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" version = "0.4.12" @@ -275,6 +305,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + [[package]] name = "rustix" version = "0.38.27" @@ -507,7 +546,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -527,17 +575,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -548,9 +597,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -560,9 +609,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -572,9 +621,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -584,9 +639,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -596,9 +651,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -608,9 +663,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -620,9 +675,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 43a2d11..9e2fa20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ maintenance = { status = "actively-developed" } anyhow = "1.0.75" cargo_metadata = "0.18.1" dunce = "1.0.4" +filetime = "0.2.24" gumdrop = "0.8.1" is-terminal = "0.4.9" libc = "0.2.147" diff --git a/src/cargo.rs b/src/cargo.rs index 6f027d3..6616ba5 100644 --- a/src/cargo.rs +++ b/src/cargo.rs @@ -2,11 +2,13 @@ use std::{ collections::BTreeMap, env, ffi::OsString, + io::BufReader, path::{Path, PathBuf}, - process::Command, + process::{Command, Stdio}, }; -use cargo_metadata::{camino::Utf8PathBuf, semver::Version}; +use anyhow::{Context, Result}; +use cargo_metadata::{camino::Utf8PathBuf, semver::Version, Artifact, Message}; use crate::shell::Shell; @@ -203,7 +205,7 @@ pub(crate) fn run( cargo_manifest: &Path, bindgen: bool, #[allow(unused_variables)] out_dir: &Utf8PathBuf, -) -> std::process::ExitStatus { +) -> Result<(std::process::ExitStatus, Vec)> { if version.major < 23 { shell.error("NDK versions less than r23 are not supported. Install an up-to-date version of the NDK.").unwrap(); std::process::exit(1); @@ -259,7 +261,32 @@ pub(crate) fn run( cargo_args.insert(arg_insertion_position, triple.into()); cargo_args.insert(arg_insertion_position, "--target".into()); - cargo_cmd.args(cargo_args).status().expect("cargo crashed") + cargo_args.insert(arg_insertion_position, "json-render-diagnostics".into()); + cargo_args.insert(arg_insertion_position, "--message-format".into()); + + let mut child = cargo_cmd + .args(cargo_args) + .stdin(Stdio::inherit()) + .stderr(Stdio::inherit()) + .stdout(Stdio::piped()) + .spawn() + .context("failed spawning cargo process")?; + + let reader = BufReader::new(child.stdout.take().context("no stdout available")?); + let mut artifacts = Vec::new(); + + for msg in Message::parse_stream(reader) { + match msg? { + Message::CompilerArtifact(artifact) => artifacts.push(artifact), + Message::CompilerMessage(msg) => println!("{msg}"), + Message::TextLine(line) => println!("{line}"), + _ => {} + } + } + + let status = child.wait().context("cargo crashed")?; + + Ok((status, artifacts)) } pub(crate) fn strip(ndk_home: &Path, bin_path: &Path) -> std::process::ExitStatus { diff --git a/src/cli.rs b/src/cli.rs index 8d7beaf..d42b619 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,7 @@ use std::{ collections::BTreeMap, env, - ffi::{OsStr, OsString}, + ffi::OsString, fmt::Display, fs, io::{self, ErrorKind}, @@ -10,7 +10,9 @@ use std::{ time::Instant, }; -use cargo_metadata::{semver::Version, MetadataCommand}; +use anyhow::Context; +use cargo_metadata::{camino::Utf8Path, semver::Version, Artifact, MetadataCommand}; +use filetime::FileTime; use gumdrop::Options; use crate::{ @@ -639,41 +641,48 @@ pub fn run(args: Vec) -> anyhow::Result<()> { let start_time = Instant::now(); - for target in &targets { - let triple = target.triple(); - shell.status("Building", format!("{} ({})", &target, &triple))?; - - shell.very_verbose(|shell| { - shell.status_with_color( - "Exporting", - format!("CARGO_NDK_ANDROID_TARGET={:?}", &target.to_string()), - termcolor::Color::Cyan, - ) - })?; + let targets = targets + .into_iter() + .map(|target| { + let triple = target.triple(); + shell.status("Building", format!("{} ({})", &target, &triple))?; + + shell.very_verbose(|shell| { + shell.status_with_color( + "Exporting", + format!("CARGO_NDK_ANDROID_TARGET={:?}", &target.to_string()), + termcolor::Color::Cyan, + ) + })?; - env::set_var("CARGO_NDK_ANDROID_TARGET", target.to_string()); - - let status = crate::cargo::run( - &mut shell, - &working_dir, - &ndk_home, - &ndk_version, - triple, - platform, - &args.cargo_args, - &cargo_manifest, - args.bindgen, - &out_dir, - ); - let code = status.code().unwrap_or(-1); + env::set_var("CARGO_NDK_ANDROID_TARGET", target.to_string()); + + let (status, artifacts) = crate::cargo::run( + &mut shell, + &working_dir, + &ndk_home, + &ndk_version, + triple, + platform, + &args.cargo_args, + &cargo_manifest, + args.bindgen, + &out_dir, + )?; + let code = status.code().unwrap_or(-1); + + if code != 0 { + shell.note( + "If the build failed due to a missing target, you can run this command:", + )?; + shell.note("")?; + shell.note(format!(" rustup target install {}", triple))?; + std::process::exit(code); + } - if code != 0 { - shell.note("If the build failed due to a missing target, you can run this command:")?; - shell.note("")?; - shell.note(format!(" rustup target install {}", triple))?; - std::process::exit(code); - } - } + Ok((target, artifacts)) + }) + .collect::>>()?; if let Some(output_dir) = args.output_dir.as_ref() { shell.concise(|shell| { @@ -686,57 +695,66 @@ pub fn run(args: Vec) -> anyhow::Result<()> { ) })?; - for target in targets.iter() { + for (target, artifacts) in targets.iter() { + shell.very_verbose(|shell| { + shell.note(format!("artifacts for {target}: {artifacts:?}")) + })?; + let arch_output_dir = output_dir.join(target.to_string()); fs::create_dir_all(&arch_output_dir).unwrap(); - let dir = out_dir.join(target.triple()).join(build_mode.to_string()); - - let lib_filename = format!("lib{}.so", config.lib_name); - let so_file = match fs::read_dir(&dir) { - Ok(dir) => dir - .filter_map(Result::ok) - .map(|x| x.path()) - .find(|x| x.file_name() == Some(OsStr::new(lib_filename.as_str()))), - Err(e) => { - shell.error(format!("Could not read directory: {:?}", dir))?; - shell.error(e)?; - std::process::exit(1); - } - }; - - if so_file.is_none() { - shell.error(format!( - "{:?} file not found in path {:?}", - lib_filename, dir - ))?; + if artifacts.is_empty() || !artifacts.iter().any(artifact_is_cdylib) { + shell.error("No usable artifacts produced by cargo")?; shell.error("Did you set the crate-type in Cargo.toml to include 'cdylib'?")?; shell.error("For more info, see .")?; std::process::exit(1); } - let so_file = so_file.unwrap(); - - let dest = arch_output_dir.join(so_file.file_name().unwrap()); - shell.verbose(|shell| { - shell.status( - "Copying", - format!( - "{} -> {}", - &dunce::canonicalize(&so_file).unwrap().display(), - &dest.display() - ), - ) - })?; - fs::copy(so_file, &dest).unwrap(); - if !args.no_strip { + for artifact in artifacts.iter().filter(|a| artifact_is_cdylib(a)) { + let Some(file) = artifact + .filenames + .iter() + .find(|name| name.extension() == Some("so")) + else { + // This should never happen because we filter for cdylib outputs above but you + // never know... and it still feels better than just unwrapping + shell.error("No cdylib file found to copy")?; + std::process::exit(1); + }; + + let dest = arch_output_dir.join(file.file_name().unwrap()); + + if is_fresh(file, &dest)? { + shell.status("Fresh", file)?; + continue; + } + shell.verbose(|shell| { - shell.status( - "Stripping", - format!("{}", &dunce::canonicalize(&dest).unwrap().display()), - ) + shell.status("Copying", format!("{file} -> {}", &dest.display())) })?; - let _ = crate::cargo::strip(&ndk_home, &dest); + + fs::copy(file, &dest) + .with_context(|| format!("failed to copy {file:?} over to {dest:?}"))?; + + filetime::set_file_mtime( + &dest, + FileTime::from_last_modification_time( + &dest + .metadata() + .with_context(|| format!("failed getting metadata for {dest:?}"))?, + ), + ) + .with_context(|| format!("unable to update the modification time of {dest:?}"))?; + + if !args.no_strip { + shell.verbose(|shell| { + shell.status( + "Stripping", + format!("{}", &dunce::canonicalize(&dest).unwrap().display()), + ) + })?; + let _ = crate::cargo::strip(&ndk_home, &dest); + } } } } @@ -751,7 +769,7 @@ pub fn run(args: Vec) -> anyhow::Result<()> { }; let t = targets .iter() - .map(ToString::to_string) + .map(|(target, _)| target.to_string()) .collect::>() .join(", "); @@ -760,3 +778,30 @@ pub fn run(args: Vec) -> anyhow::Result<()> { Ok(()) } + +/// Check whether the produced artifact is of use to use (has to be of type `cdylib`). +fn artifact_is_cdylib(artifact: &Artifact) -> bool { + artifact.target.crate_types.iter().any(|ty| ty == "cdylib") +} + +// Check if the source file has changed and should be copied over to the destination path. +fn is_fresh(src: &Utf8Path, dest: &Path) -> anyhow::Result { + if !dest.exists() { + return Ok(false); + } + + let src = src + .metadata() + .with_context(|| format!("failed getting metadata for {src:?}"))?; + let dest = dest + .metadata() + .with_context(|| format!("failed getting metadata for {dest:?}"))?; + + // Only errors if modification time isn't available on the OS. Therefore, + // we can't check it and always assume the file changed. + let Some((src, dest)) = src.modified().ok().zip(dest.modified().ok()) else { + return Ok(false); + }; + + Ok(src <= dest) +} diff --git a/src/meta.rs b/src/meta.rs index 77559af..61c0e05 100644 --- a/src/meta.rs +++ b/src/meta.rs @@ -17,12 +17,10 @@ fn default_targets() -> Vec { #[derive(Debug, Deserialize)] struct CargoToml { package: Option, - lib: Option, } #[derive(Debug, Deserialize)] struct Package { - name: String, metadata: Option, } @@ -31,11 +29,6 @@ struct Metadata { ndk: Option, } -#[derive(Debug, Deserialize)] -struct Lib { - name: Option, -} - #[derive(Debug, Deserialize, Clone)] pub(crate) struct Ndk { #[serde(default = "default_platform")] @@ -66,7 +59,6 @@ struct NdkTarget { #[derive(Debug)] pub struct Config { - pub lib_name: String, pub platform: u8, pub targets: Vec, } @@ -76,7 +68,6 @@ impl Default for Config { Self { platform: Ndk::default().platform, targets: default_targets(), - lib_name: String::new(), } } } @@ -159,17 +150,7 @@ pub(crate) fn config( ndk.debug.map_or_else(|| base_targets, |x| x.targets) }; - let lib_name = cargo_toml - .lib - .and_then(|x| x.name.clone()) - .or_else(|| package.as_ref().map(|x| x.name.to_string())); - - let Some(lib_name) = lib_name else { - anyhow::bail!("Could not derive library name from Cargo.toml"); - }; - Ok(Config { - lib_name: lib_name.replace('-', "_"), platform: ndk.platform, targets, })