From 9a59f287a424ed2b5fa64c0257f790cb79007866 Mon Sep 17 00:00:00 2001 From: David Herman Date: Mon, 27 Feb 2023 19:13:20 -0800 Subject: [PATCH] Self-hosted implementation of `cargo-cp-artifact`: - Core functionality implemented in crates/cargo-cp-artifact as a regular crate - crates/cargo-cp-artifact also has a bin so it can be used to build the self-hosted JS package - pkgs/cargo-cp-artifact has a Neon implementation that depends on crates/cargo-cp-artifact - pkgs/cargo-cp-artifact/package.json uses `cargo run -p cargo-cp-artifact` to build itself from the monorepo --- Cargo.lock | 174 +++++++- Cargo.toml | 1 + crates/cargo-cp-artifact/Cargo.toml | 13 + crates/cargo-cp-artifact/src/artifact.rs | 23 ++ crates/cargo-cp-artifact/src/bin.rs | 12 + crates/cargo-cp-artifact/src/cargo.rs | 131 ++++++ crates/cargo-cp-artifact/src/cli.rs | 384 ++++++++++++++++++ crates/cargo-cp-artifact/src/lib.rs | 3 + package-lock.json | 46 ++- pkgs/cargo-cp-artifact/Cargo.lock | 269 ++++++++++++ pkgs/cargo-cp-artifact/Cargo.toml | 21 + .../bin/cargo-cp-artifact.js | 4 +- pkgs/cargo-cp-artifact/package-lock.json | 18 + pkgs/cargo-cp-artifact/package.json | 13 +- pkgs/cargo-cp-artifact/src/args.js | 131 ------ pkgs/cargo-cp-artifact/src/index.js | 163 -------- pkgs/cargo-cp-artifact/src/lib.rs | 24 ++ pkgs/cargo-cp-artifact/test/args.js | 132 ------ 18 files changed, 1111 insertions(+), 451 deletions(-) create mode 100644 crates/cargo-cp-artifact/Cargo.toml create mode 100644 crates/cargo-cp-artifact/src/artifact.rs create mode 100644 crates/cargo-cp-artifact/src/bin.rs create mode 100644 crates/cargo-cp-artifact/src/cargo.rs create mode 100644 crates/cargo-cp-artifact/src/cli.rs create mode 100644 crates/cargo-cp-artifact/src/lib.rs create mode 100644 pkgs/cargo-cp-artifact/Cargo.lock create mode 100644 pkgs/cargo-cp-artifact/Cargo.toml create mode 100644 pkgs/cargo-cp-artifact/package-lock.json delete mode 100644 pkgs/cargo-cp-artifact/src/args.js delete mode 100644 pkgs/cargo-cp-artifact/src/index.js create mode 100644 pkgs/cargo-cp-artifact/src/lib.rs delete mode 100644 pkgs/cargo-cp-artifact/test/args.js diff --git a/Cargo.lock b/Cargo.lock index 18d2d5070..5364056fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,54 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "camino" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-cp-artifact" +version = "0.2.0" +dependencies = [ + "cargo_metadata", +] + +[[package]] +name = "cargo-cp-artifact-adapter" +version = "0.2.0" +dependencies = [ + "cargo-cp-artifact", + "cargo_metadata", + "neon 0.10.1", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.13", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -105,7 +153,7 @@ checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.7.3", ] [[package]] @@ -154,7 +202,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" name = "electron-tests" version = "0.1.0" dependencies = [ - "neon", + "neon 1.0.0-alpha.2", ] [[package]] @@ -227,6 +275,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + [[package]] name = "lazy_static" version = "1.4.0" @@ -248,6 +302,16 @@ version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.7.3" @@ -298,12 +362,25 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" name = "napi-tests" version = "0.1.0" dependencies = [ - "neon", + "neon 1.0.0-alpha.2", "num-bigint-dig", "once_cell", "tokio", ] +[[package]] +name = "neon" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +dependencies = [ + "neon-build", + "neon-macros 0.10.1", + "neon-runtime", + "semver 0.9.0", + "smallvec", +] + [[package]] name = "neon" version = "1.0.0-alpha.2" @@ -313,18 +390,35 @@ dependencies = [ "doc-comment", "easy-cast", "getrandom", - "libloading", + "libloading 0.7.3", "linkify", - "neon-macros", + "neon-macros 1.0.0-alpha.2", "nodejs-sys", "once_cell", "psd", - "semver", + "semver 1.0.13", "smallvec", "tokio", "widestring", ] +[[package]] +name = "neon-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" + +[[package]] +name = "neon-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +dependencies = [ + "quote", + "syn", + "syn-mid", +] + [[package]] name = "neon-macros" version = "1.0.0-alpha.2" @@ -334,6 +428,17 @@ dependencies = [ "syn-mid", ] +[[package]] +name = "neon-runtime" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +dependencies = [ + "cfg-if", + "libloading 0.6.7", + "smallvec", +] + [[package]] name = "nodejs-sys" version = "0.14.0" @@ -466,9 +571,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] @@ -544,17 +649,66 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "shlex" @@ -582,9 +736,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 937e60cb3..ee1782e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,4 +2,5 @@ members = [ "crates/*", "test/*", + "pkgs/cargo-cp-artifact", ] diff --git a/crates/cargo-cp-artifact/Cargo.toml b/crates/cargo-cp-artifact/Cargo.toml new file mode 100644 index 000000000..e5b973116 --- /dev/null +++ b/crates/cargo-cp-artifact/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cargo-cp-artifact" +version = "0.2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "cargo-cp-artifact" +path = "src/bin.rs" + +[dependencies] +cargo_metadata = "0.15.3" diff --git a/crates/cargo-cp-artifact/src/artifact.rs b/crates/cargo-cp-artifact/src/artifact.rs new file mode 100644 index 000000000..11736bdc7 --- /dev/null +++ b/crates/cargo-cp-artifact/src/artifact.rs @@ -0,0 +1,23 @@ +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum ArtifactKind { + Bin, + CDylib, + Dylib, +} + +impl ArtifactKind { + pub fn parse(str: &impl AsRef) -> Option { + match str.as_ref() { + "bin" => Some(ArtifactKind::Bin), + "cdylib" => Some(ArtifactKind::CDylib), + "dylib" => Some(ArtifactKind::Dylib), + _ => None, + } + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +pub struct Artifact { + pub kind: ArtifactKind, + pub crate_name: String, +} diff --git a/crates/cargo-cp-artifact/src/bin.rs b/crates/cargo-cp-artifact/src/bin.rs new file mode 100644 index 000000000..65798eb67 --- /dev/null +++ b/crates/cargo-cp-artifact/src/bin.rs @@ -0,0 +1,12 @@ +mod artifact; +mod cargo; +mod cli; + +use cargo::Status; + +fn main() { + // Skip the native binary name (argv[0]). + if let Status::Failure = cli::run(1) { + std::process::exit(1); + } +} diff --git a/crates/cargo-cp-artifact/src/cargo.rs b/crates/cargo-cp-artifact/src/cargo.rs new file mode 100644 index 000000000..7a77ceda8 --- /dev/null +++ b/crates/cargo-cp-artifact/src/cargo.rs @@ -0,0 +1,131 @@ +use crate::artifact::{Artifact, ArtifactKind}; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fs::{copy, create_dir_all, remove_file}; +use std::io::ErrorKind; +use std::process::{Stdio, Command}; +use std::path::{Path, PathBuf}; +use cargo_metadata::{Message, Target}; + +#[derive(Debug, PartialEq, Eq)] +pub struct CargoCommand { + pub artifacts: HashMap>, + pub command: String, + pub args: Vec, +} + +pub fn push_artifact( + map: &mut HashMap>, + kind: ArtifactKind, + crate_name: String, + output_file: String, +) { + let key = Artifact { kind, crate_name }; + + if !map.contains_key(&key) { + let _ = map.insert(key, vec![output_file]); + } else { + map.get_mut(&key).unwrap().push(output_file); + } +} + +pub enum Status { + Success, + Failure, +} + +fn is_newer(filename: &impl AsRef, output_file: &impl AsRef) -> bool { + let filename = filename.as_ref(); + let output_file = output_file.as_ref(); + + if let (Ok(meta1), Ok(meta2)) = (filename.metadata(), output_file.metadata()) { + if let (Ok(mtime1), Ok(mtime2)) = (meta1.modified(), meta2.modified()) { + return mtime1 > mtime2; + } + } + + return true; +} + +// FIXME: return Result +fn copy_artifact(_artifact: &Artifact, filename: PathBuf, output_file: PathBuf) { + if !is_newer(&filename, &output_file) { + return; + } + + if let Some(basename) = output_file.parent() { + // FIXME: panic + create_dir_all(basename).expect("Couldn't create directories for output file"); + } + + // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, + // the macOS code signing cache isn't cleared when overwriting a file. + // Deleting the file before copying works around the issue. + // + // Unfortunately, this workaround is incomplete because the file must be + // deleted from the location it is loaded. If further steps in the user's + // build process copy or move the file in place, the code signing cache + // will not be cleared. + // + // https://github.com/neon-bindings/neon/issues/911 + if output_file.extension() == Some(OsStr::new("node")) { + if let Err(err) = remove_file(&output_file) { + match err.kind() { + ErrorKind::NotFound => {} + // FIXME: panic + _ => { panic!("Couldn't overwrite {}", output_file.to_string_lossy()); } + } + } + } + + // FIXME: panic + copy(&filename, &output_file).expect("Couldn't copy file"); +} + +impl CargoCommand { + pub fn new( + artifacts: HashMap>, + command: String, + args: Vec, + ) -> Self { + Self { artifacts, command, args } + } + + pub fn exec(self) -> Status { + let mut command = Command::new(self.command) + .args(&self.args) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); // FIXME: unwrap + + let reader = std::io::BufReader::new(command.stdout.take().unwrap()); // FIXME: unwrap + for message in cargo_metadata::Message::parse_stream(reader) { + if let Message::CompilerArtifact(artifact) = message.unwrap() { // FIXME: unwrap + let Target { kind: kinds, name, .. } = artifact.target; + for (kind, filename) in kinds.iter().zip(artifact.filenames) { + if let Some(kind) = ArtifactKind::parse(kind) { + let crate_name = name.clone(); + let artifact = Artifact { kind, crate_name }; + if let Some(output_files) = self.artifacts.get(&artifact) { + for output_file in output_files { + copy_artifact( + &artifact, + filename.clone().into(), + Path::new(output_file).to_path_buf(), + ); + } + } + } + } + } + } + + // FIXME: panic + if command.wait().expect("Couldn't get cargo's exit status").success() { + Status::Success + } else { + Status::Failure + } + } +} \ No newline at end of file diff --git a/crates/cargo-cp-artifact/src/cli.rs b/crates/cargo-cp-artifact/src/cli.rs new file mode 100644 index 000000000..bb351be3d --- /dev/null +++ b/crates/cargo-cp-artifact/src/cli.rs @@ -0,0 +1,384 @@ +use std::collections::HashMap; + +use crate::artifact::ArtifactKind; +use crate::cargo::{CargoCommand, push_artifact, Status}; + +#[derive(Debug, PartialEq, Eq)] +pub enum ParseError { + UnexpectedArtifactKind(String), + CommandNotFound, + EnvVarNotFound, + UnexpectedOption(String), +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::UnexpectedArtifactKind(found) => { + write!(f, "Unexpected artifact type: {found}") + } + ParseError::CommandNotFound => { + writeln!(f, "Missing command to execute.")?; + writeln!(f, "")?; + write!(f, "cargo-cp-artifact -a cdylib my-crate index.node ")?; + write!(f, "-- cargo build --message-format=json-render-diagnostics") + } + ParseError::EnvVarNotFound => { + write!(f, "Could not find the `{NPM_ENV}` environment variable. ")?; + write!(f, "Expected to be executed from an `npm` command.") + } + ParseError::UnexpectedOption(found) => { + write!(f, "Unexpected option: {found}") + } + } + } +} + +//struct Args(std::iter::Skip); +pub struct Args(T); + +impl Args> { + fn new(skip: usize) -> Self { + Self(std::env::args().skip(skip)) + } +} + +impl<'a> Args> { + #[allow(dead_code)] + fn from_vec(v: Vec) -> Self { + Self(v.into_iter()) + } +} + +#[allow(unused_macros)] +macro_rules! args { + [$($s:literal),*] => { + Args::from_vec(vec![$($s.to_string()),*]) + } +} + +impl> Args { + fn next(&mut self) -> Result { + let Self(args) = self; + match args.next() { + Some(token) => Ok(token), + None => Err(ParseError::CommandNotFound), + } + } + + fn rest(self) -> Vec { + let Self(args) = self; + args.collect() + } + + fn get_artifact_kind(&mut self, token: &str) -> Result { + if token.len() == 3 && &token[1..2] != "-" { + validate_artifact_kind(&token[2..3]) + } else { + validate_artifact_kind(self.next()?.as_str()) + } + } + + fn parse(mut self, get_crate_name: F) -> Result + where + F: Fn() -> Result, + { + let mut artifacts = HashMap::new(); + + loop { + let token = self.next()?; + let token = token.as_str(); + + if token == "--" { + break; + } + + if token == "--artifact" || (token.len() <= 3 && token.starts_with("-a")) { + let kind = self.get_artifact_kind(token)?; + let crate_name = self.next()?; + let output_file = self.next()?; + push_artifact(&mut artifacts, kind, crate_name, output_file); + continue; + } + + if token == "--npm" || (token.len() <= 3 && token.starts_with("-n")) { + let kind = self.get_artifact_kind(token)?; + let mut crate_name = get_crate_name()?; + + if let Some((left, right)) = crate_name.split_once('/') { + // This is a namespaced package; assume the crate is the un-namespaced version + if left.starts_with("@") { + crate_name = right.to_string(); + } + } + + let output_file = self.next()?; + push_artifact(&mut artifacts, kind, crate_name, output_file); + continue; + } + + return Err(ParseError::UnexpectedOption(token.to_string())); + } + + let command = self.next()?; + + Ok(CargoCommand::new(artifacts, command, self.rest())) + } +} + +fn validate_artifact_kind(kind: &str) -> Result { + match kind { + "b" | "bin" => Ok(ArtifactKind::Bin), + "c" | "cdylib" => Ok(ArtifactKind::CDylib), + "d" | "dylib" => Ok(ArtifactKind::Dylib), + _ => Err(ParseError::UnexpectedArtifactKind(kind.to_string())), + } +} + +const NPM_ENV: &'static str = "npm_package_name"; + +fn get_crate_name_from_env() -> Result { + std::env::var(NPM_ENV).or(Err(ParseError::EnvVarNotFound)) +} + +pub fn run(skip: usize) -> Status { + match Args::new(skip).parse(get_crate_name_from_env) { + Ok(cargo) => { cargo.exec() } + Err(err) => { + eprintln!("{err}"); + Status::Failure + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::artifact::{Artifact, ArtifactKind}; + + macro_rules! assert_err { + ($actual:expr, $expected:expr, $($arg:tt)+) => { + { + match $actual { + Ok(_) => { panic!($($arg)+); } + Err(error) => { + assert_eq!(error, $expected, $($arg)+); + } + } + } + } + } + + fn get_crate_name_ok() -> Result { + Ok("my-crate".to_string()) + } + + fn get_crate_name_with_namespace() -> Result { + Ok("@my-namespace/my-crate".to_string()) + } + + fn get_crate_name_err() -> Result { + Err(ParseError::EnvVarNotFound) + } + + #[test] + fn test_invalid_artifact_type() { + let r = args!["-an", "a", "b", "--"].parse(get_crate_name_ok); + assert_err!( + r, + ParseError::UnexpectedArtifactKind("n".to_string()), + "expected artifact type parse error", + ); + } + + #[test] + fn test_missing_env_var() { + let r = args!["-nc", "a", "b", "--"].parse(get_crate_name_err); + assert_err!(r, ParseError::EnvVarNotFound, "expected env var error"); + } + + #[test] + fn test_missing_command() { + let r = args!["-ac", "a", "b"].parse(get_crate_name_ok); + assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + let r = args!["-ac", "a", "b", "--"].parse(get_crate_name_ok); + assert_err!(r, ParseError::CommandNotFound, "expected command not found error"); + } + + #[test] + fn test_invalid_option() { + let r = args!["-q"].parse(get_crate_name_ok); + assert_err!(r, ParseError::UnexpectedOption("-q".to_string()), "expected bad option error"); + } + + fn example_artifact1() -> Artifact { + Artifact { + kind: ArtifactKind::Bin, + crate_name: "my-crate".to_string(), + } + } + + fn example_artifact2() -> Artifact { + Artifact { + kind: ArtifactKind::Dylib, + crate_name: "a".to_string(), + } + } + + fn example_artifact3() -> Artifact { + Artifact { + kind: ArtifactKind::CDylib, + crate_name: "my-crate".to_string(), + } + } + + fn example_cargo_command() -> CargoCommand { + let mut artifacts = HashMap::new(); + let artifact = example_artifact1(); + artifacts.insert(artifact, vec!["my-bin".to_string()]); + + let command = "a".to_string(); + let args = vec!["b".to_string(), "c".to_string()]; + + CargoCommand { artifacts, command, args } + } + + fn example_complex_cargo_command() -> CargoCommand { + let mut artifacts = HashMap::new(); + + artifacts.insert(example_artifact1(), vec!["my-bin".to_string(), "other-copy".to_string()]); + artifacts.insert(example_artifact2(), vec!["b".to_string()]); + artifacts.insert(example_artifact3(), vec!["index.node".to_string()]); + + let command = "a".to_string(); + let args = vec!["b".to_string(), "c".to_string()]; + + CargoCommand { artifacts, command, args } + } + + #[test] + fn test_artifact_option() { + let cmd = args![ + "--artifact", + "bin", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-a", + "bin", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-ab", + "my-crate", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_npm_option() { + let cmd = args![ + "--npm", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-n", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + + let cmd = args![ + "-nb", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_namespace_removal() { + let cmd = args![ + "--npm", + "bin", + "my-bin", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_with_namespace) + .expect("expected successful parse"); + + assert_eq!(cmd, example_cargo_command(), "improperly parsed: {:?}", cmd); + } + + #[test] + fn test_complex_command() { + let cmd: CargoCommand = args![ + "-nb", + "my-bin", + "--artifact", + "d", + "a", + "b", + "-ac", + "my-crate", + "index.node", + "--npm", + "bin", + "other-copy", + "--", + "a", + "b", + "c" + ].parse(get_crate_name_ok) + .expect("expected successful parse"); + + assert_eq!(cmd, example_complex_cargo_command(), "improperly parsed: {:?}", cmd); + } +} diff --git a/crates/cargo-cp-artifact/src/lib.rs b/crates/cargo-cp-artifact/src/lib.rs new file mode 100644 index 000000000..32d453527 --- /dev/null +++ b/crates/cargo-cp-artifact/src/lib.rs @@ -0,0 +1,3 @@ +pub mod artifact; +pub mod cargo; +pub mod cli; diff --git a/package-lock.json b/package-lock.json index 9c6907b64..2a126a1db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2284,14 +2284,13 @@ } }, "pkgs/cargo-cp-artifact": { - "version": "0.1.7", + "version": "0.2.0", + "hasInstallScript": true, "license": "MIT", "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, - "devDependencies": { - "mocha": "^10.0.0" - } + "devDependencies": {} }, "pkgs/create-neon": { "version": "0.2.0", @@ -2328,6 +2327,15 @@ "playwright": "^1.23.1" } }, + "test/electron/node_modules/cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true, + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + } + }, "test/napi": { "name": "napi-tests", "version": "0.1.0", @@ -2338,6 +2346,15 @@ "chai": "^4.3.6", "mocha": "^10.0.0" } + }, + "test/napi/node_modules/cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true, + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + } } }, "dependencies": { @@ -2570,10 +2587,7 @@ "dev": true }, "cargo-cp-artifact": { - "version": "file:pkgs/cargo-cp-artifact", - "requires": { - "mocha": "^10.0.0" - } + "version": "file:pkgs/cargo-cp-artifact" }, "chai": { "version": "4.3.6", @@ -2826,6 +2840,14 @@ "cargo-cp-artifact": "^0.1.7", "electron": "^19.0.11", "playwright": "^1.23.1" + }, + "dependencies": { + "cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true + } } }, "emoji-regex": { @@ -3496,6 +3518,14 @@ "cargo-cp-artifact": "^0.1.7", "chai": "^4.3.6", "mocha": "^10.0.0" + }, + "dependencies": { + "cargo-cp-artifact": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz", + "integrity": "sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg==", + "dev": true + } } }, "neo-async": { diff --git a/pkgs/cargo-cp-artifact/Cargo.lock b/pkgs/cargo-cp-artifact/Cargo.lock new file mode 100644 index 000000000..f590344a5 --- /dev/null +++ b/pkgs/cargo-cp-artifact/Cargo.lock @@ -0,0 +1,269 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "camino" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-bootstrap" +version = "0.1.0" +dependencies = [ + "cargo_metadata", +] + +[[package]] +name = "cargo-cp-artifact" +version = "0.2.0" +dependencies = [ + "cargo-bootstrap", + "cargo_metadata", + "neon", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a1ec454bc3eead8719cb56e15dbbfecdbc14e4b3a3ae4936cc6e31f5fc0d07" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.16", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "libloading" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "neon" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e15415261d880aed48122e917a45e87bb82cf0260bb6db48bbab44b7464373" +dependencies = [ + "neon-build", + "neon-macros", + "neon-runtime", + "semver 0.9.0", + "smallvec", +] + +[[package]] +name = "neon-build" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bac98a702e71804af3dacfde41edde4a16076a7bbe889ae61e56e18c5b1c811" + +[[package]] +name = "neon-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7288eac8b54af7913c60e0eb0e2a7683020dffa342ab3fd15e28f035ba897cf" +dependencies = [ + "quote", + "syn", + "syn-mid", +] + +[[package]] +name = "neon-runtime" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4676720fa8bb32c64c3d9f49c47a47289239ec46b4bdb66d0913cc512cb0daca" +dependencies = [ + "cfg-if", + "libloading", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-mid" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/pkgs/cargo-cp-artifact/Cargo.toml b/pkgs/cargo-cp-artifact/Cargo.toml new file mode 100644 index 000000000..a165f9178 --- /dev/null +++ b/pkgs/cargo-cp-artifact/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cargo-cp-artifact-adapter" +version = "0.2.0" +authors = ["K.J. Valencik"] +license = "MIT" +edition = "2018" +exclude = ["index.node"] + +[lib] +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cargo-cp-artifact = { path = "../../crates/cargo-cp-artifact" } +cargo_metadata = "0.15.3" + +[dependencies.neon] +version = "0.10.1" +default-features = false +features = ["napi-6"] diff --git a/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js b/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js index 49bb916b2..59d26bdc3 100755 --- a/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js +++ b/pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js @@ -1,6 +1,6 @@ #!/usr/bin/env node "use strict"; -const run = require(".."); +const run = require("..").run; -run(process.argv.slice(2), process.env); +run(); diff --git a/pkgs/cargo-cp-artifact/package-lock.json b/pkgs/cargo-cp-artifact/package-lock.json new file mode 100644 index 000000000..ac2b0cdd8 --- /dev/null +++ b/pkgs/cargo-cp-artifact/package-lock.json @@ -0,0 +1,18 @@ +{ + "name": "cargo-cp-artifact", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cargo-cp-artifact", + "version": "0.2.0", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "cargo-cp-artifact": "bin/cargo-cp-artifact.js" + }, + "devDependencies": {} + } + } +} diff --git a/pkgs/cargo-cp-artifact/package.json b/pkgs/cargo-cp-artifact/package.json index 79ee7f3d9..de996fb35 100644 --- a/pkgs/cargo-cp-artifact/package.json +++ b/pkgs/cargo-cp-artifact/package.json @@ -1,17 +1,21 @@ { "name": "cargo-cp-artifact", - "version": "0.1.7", + "version": "0.2.0", "description": "Copies compiler artifacts emitted by rustc by parsing Cargo metadata", - "main": "src/index.js", + "main": "index.node", "files": [ "bin", - "src" + "index.node" ], "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, "scripts": { - "test": "mocha test" + "build": "cargo run -p cargo-cp-artifact -- -ac cargo-cp-artifact-adapter index.node -- cargo build --message-format=json-render-diagnostics", + "build-debug": "npm run build --", + "build-release": "npm run build -- --release", + "install": "npm run build-release", + "test": "cargo test" }, "repository": { "type": "git", @@ -29,6 +33,5 @@ }, "homepage": "https://github.com/neon-bindings/neon/tree/main/pkgs/cargo-cp-artifact", "devDependencies": { - "mocha": "^10.0.0" } } diff --git a/pkgs/cargo-cp-artifact/src/args.js b/pkgs/cargo-cp-artifact/src/args.js deleted file mode 100644 index 36fbd3e2a..000000000 --- a/pkgs/cargo-cp-artifact/src/args.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; - -class ParseError extends Error {} - -const NPM_ENV = "npm_package_name"; -const EXPECTED_COMMAND = [ - "Missing command to execute.", - [ - "cargo-cp-artifct -a cdylib my-crate index.node", - "--", - "cargo build --message-format=json-render-diagnostics", - ].join(" "), -].join("\n"); - -function validateArtifactType(artifactType) { - switch (artifactType) { - case "b": - case "bin": - return "bin"; - case "c": - case "cdylib": - return "cdylib"; - case "d": - case "dylib": - return "dylib"; - default: - } - - throw new ParseError(`Unexpected artifact type: ${artifactType}`); -} - -function getArtifactName({ artifactType, crateName }) { - return `${artifactType}:${crateName}`; -} - -function getCrateNameFromEnv(env) { - if (!env.hasOwnProperty(NPM_ENV)) { - throw new ParseError( - [ - `Could not find the \`${NPM_ENV}\` environment variable.`, - "Expected to be executed from an `npm` command.", - ].join(" ") - ); - } - - const name = env[NPM_ENV]; - const firstSlash = name.indexOf("/"); - - // This is a namespaced package; assume the crate is the un-namespaced version - if (name[0] === "@" && firstSlash > 0) { - return name.slice(firstSlash + 1); - } - - return name; -} - -function parse(argv, env) { - const artifacts = {}; - let tokens = argv; - - function getNext() { - if (!tokens.length) { - throw new ParseError(EXPECTED_COMMAND); - } - - const next = tokens[0]; - tokens = tokens.slice(1); - return next; - } - - function getArtifactType(token) { - if (token[1] !== "-" && token.length === 3) { - return validateArtifactType(token[2]); - } - - return validateArtifactType(getNext()); - } - - function pushArtifact(artifact) { - const name = getArtifactName(artifact); - - artifacts[name] = artifacts[name] || []; - artifacts[name].push(artifact.outputFile); - } - - while (tokens.length) { - const token = getNext(); - - // End of CLI arguments - if (token === "--") { - break; - } - - if ( - token === "--artifact" || - (token.length <= 3 && token.startsWith("-a")) - ) { - const artifactType = getArtifactType(token); - const crateName = getNext(); - const outputFile = getNext(); - - pushArtifact({ artifactType, crateName, outputFile }); - continue; - } - - if (token === "--npm" || (token.length <= 3 && token.startsWith("-n"))) { - const artifactType = getArtifactType(token); - const crateName = getCrateNameFromEnv(env); - const outputFile = getNext(); - - pushArtifact({ artifactType, crateName, outputFile }); - continue; - } - - throw new ParseError(`Unexpected option: ${token}`); - } - - if (!tokens.length) { - throw new ParseError(EXPECTED_COMMAND); - } - - const cmd = getNext(); - - return { - artifacts, - cmd, - args: tokens, - }; -} - -module.exports = { ParseError, getArtifactName, parse }; diff --git a/pkgs/cargo-cp-artifact/src/index.js b/pkgs/cargo-cp-artifact/src/index.js deleted file mode 100644 index 9eda8eeb4..000000000 --- a/pkgs/cargo-cp-artifact/src/index.js +++ /dev/null @@ -1,163 +0,0 @@ -"use strict"; - -const { spawn } = require("child_process"); -const { - promises: { copyFile, mkdir, stat, unlink }, -} = require("fs"); -const { dirname, extname } = require("path"); -const readline = require("readline"); - -const { ParseError, getArtifactName, parse } = require("./args"); - -function run(argv, env) { - const options = parseArgs(argv, env); - const copied = {}; - - const cp = spawn(options.cmd, options.args, { - stdio: ["inherit", "pipe", "inherit"], - }); - - const rl = readline.createInterface({ input: cp.stdout }); - - cp.on("error", (err) => { - if (options.cmd === "cargo" && err.code === "ENOENT") { - console.error(`Error: could not find the \`cargo\` executable. - -You can find instructions for installing Rust and Cargo at: - - https://www.rust-lang.org/tools/install - -`); - } else { - console.error(err); - } - process.exitCode = 1; - }); - - cp.on("exit", (code) => { - if (!process.exitCode) { - process.exitCode = code; - } - }); - - rl.on("line", (line) => { - try { - processCargoBuildLine(options, copied, line); - } catch (err) { - console.error(err); - process.exitCode = 1; - } - }); - - process.on("exit", () => { - Object.keys(options.artifacts).forEach((name) => { - if (!copied[name]) { - console.error(`Did not copy "${name}"`); - - if (!process.exitCode) { - process.exitCode = 1; - } - } - }); - }); -} - -function processCargoBuildLine(options, copied, line) { - const data = JSON.parse(line); - const { filenames, reason, target } = data; - - if (!data || reason !== "compiler-artifact" || !target) { - return; - } - - const { kind: kinds, name } = data.target; - - if (!Array.isArray(kinds) || !Array.isArray(filenames)) { - return; - } - - // `kind` and `filenames` zip up as key/value pairs - kinds.forEach((kind, i) => { - const filename = filenames[i]; - const key = getArtifactName({ artifactType: kind, crateName: name }); - const outputFiles = options.artifacts[key]; - - if (!outputFiles || !filename) { - return; - } - - Promise.all( - outputFiles.map((outputFile) => copyArtifact(filename, outputFile)) - ) - .then(() => { - copied[key] = true; - }) - .catch((err) => { - process.exitCode = 1; - console.error(err); - }); - }); -} - -async function isNewer(filename, outputFile) { - try { - const prevStats = await stat(outputFile); - const nextStats = await stat(filename); - - return nextStats.mtime > prevStats.mtime; - } catch (_err) {} - - return true; -} - -async function copyArtifact(filename, outputFile) { - if (!(await isNewer(filename, outputFile))) { - return; - } - - const outputDir = dirname(outputFile); - - // Don't try to create the current directory - if (outputDir && outputDir !== ".") { - await mkdir(outputDir, { recursive: true }); - } - - // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, - // the macOS code signing cache isn't cleared when overwriting a file. - // Deleting the file before copying works around the issue. - // - // Unfortunately, this workaround is incomplete because the file must be - // deleted from the location it is loaded. If further steps in the user's - // build process copy or move the file in place, the code signing cache - // will not be cleared. - // - // https://github.com/neon-bindings/neon/issues/911 - if (extname(outputFile) === ".node") { - try { - await unlink(outputFile); - } catch (_e) { - // Ignore errors; the file might not exist - } - } - - await copyFile(filename, outputFile); -} - -function parseArgs(argv, env) { - try { - return parse(argv, env); - } catch (err) { - if (err instanceof ParseError) { - quitError(err.message); - } else { - throw err; - } - } -} - -function quitError(msg) { - console.error(msg); - process.exit(1); -} - -module.exports = run; diff --git a/pkgs/cargo-cp-artifact/src/lib.rs b/pkgs/cargo-cp-artifact/src/lib.rs new file mode 100644 index 000000000..c9380dffc --- /dev/null +++ b/pkgs/cargo-cp-artifact/src/lib.rs @@ -0,0 +1,24 @@ +use neon::prelude::*; + +use cargo_cp_artifact::cargo::Status; +use cargo_cp_artifact::cli; + +fn run(mut cx: FunctionContext) -> JsResult { + // Skip the node binary name (argv[0]) and the script name (argv[1]). + if let Status::Failure = cli::run(2) { + cx.global() + .get::(&mut cx, "process")? + .get::(&mut cx, "exit")? + .call_with(&cx) + .arg(cx.number(1)) + .exec(&mut cx)?; + } + + Ok(cx.undefined()) +} + +#[neon::main] +fn main(mut cx: ModuleContext) -> NeonResult<()> { + cx.export_function("run", run)?; + Ok(()) +} diff --git a/pkgs/cargo-cp-artifact/test/args.js b/pkgs/cargo-cp-artifact/test/args.js deleted file mode 100644 index abb953d90..000000000 --- a/pkgs/cargo-cp-artifact/test/args.js +++ /dev/null @@ -1,132 +0,0 @@ -"use strict"; - -const assert = require("assert"); - -const { parse } = require("../src/args"); - -describe("Argument Parsing", () => { - it("throws on invalid artifact type", () => { - assert.throws(() => parse(["-an", "a", "b", "--"]), /artifact type/); - }); - - it("npm must have an environment variable", () => { - assert.throws(() => parse(["-nc", "a", "b", "--"], {}), /environment/); - }); - - it("must provide a command", () => { - assert.throws(() => parse(["-ac", "a", "b"]), /Missing command/); - assert.throws(() => parse(["-ac", "a", "b", "--"]), /Missing command/); - }); - - it("cannot provide invalid option", () => { - assert.throws(() => parse(["-q"], {}), /Unexpected option/); - }); - - it("should be able to use --artifact", () => { - const args = "bin my-crate my-bin -- a b c".split(" "); - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(["--artifact", ...args]), expected); - assert.deepStrictEqual(parse(["-a", ...args]), expected); - }); - - it("should be able to use --npm", () => { - const args = "bin my-bin -- a b c".split(" "); - const env = { - npm_package_name: "my-crate", - }; - - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(["--npm", ...args], env), expected); - assert.deepStrictEqual(parse(["-n", ...args], env), expected); - }); - - it("should be able to use short-hand for crate type with -a", () => { - const args = "-ab my-crate my-bin -- a b c".split(" "); - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args), expected); - }); - - it("should be able to use short-hand for crate type with -n", () => { - const args = "-nb my-bin -- a b c".split(" "); - const env = { - npm_package_name: "my-crate", - }; - - const expected = { - artifacts: { - "bin:my-crate": ["my-bin"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args, env), expected); - }); - - it("should remove namespace from package name", () => { - const args = "-nc index.node -- a b c".split(" "); - const env = { - npm_package_name: "@my-namespace/my-crate", - }; - - const expected = { - artifacts: { - "cdylib:my-crate": ["index.node"], - }, - cmd: "a", - args: ["b", "c"], - }; - - assert.deepStrictEqual(parse(args, env), expected); - }); - - it("should be able to provide multiple artifacts", () => { - const args = ` - -nb my-bin - --artifact d a b - -ac my-crate index.node - --npm bin other-copy - -- a b c - ` - .trim() - .split("\n") - .map((line) => line.trim()) - .join(" ") - .split(" "); - - const env = { - npm_package_name: "my-crate", - }; - - assert.deepStrictEqual(parse(args, env), { - artifacts: { - "bin:my-crate": ["my-bin", "other-copy"], - "dylib:a": ["b"], - "cdylib:my-crate": ["index.node"], - }, - cmd: "a", - args: ["b", "c"], - }); - }); -});