From f872aa2a9c510eefb8d8a44c72c9d8fd2925bfe3 Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Wed, 23 Oct 2024 13:07:03 +1100 Subject: [PATCH] get build plan --- Cargo.lock | 38 +++++++++++++++- nih_plug_xtask/Cargo.toml | 2 + nih_plug_xtask/src/lib.rs | 91 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6876ac6..11fc0f12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1513,6 +1513,18 @@ dependencies = [ "dtoa", ] +[[package]] +name = "duct" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c" +dependencies = [ + "libc", + "once_cell", + "os_pipe", + "shared_child", +] + [[package]] name = "dwrote" version = "0.11.0" @@ -3132,9 +3144,11 @@ version = "0.1.0" dependencies = [ "anyhow", "cargo_metadata", + "duct", "goblin", "reflink", "serde", + "serde_json", "toml", ] @@ -3448,6 +3462,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "owned_ttf_parser" version = "0.24.0" @@ -4302,9 +4326,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -4364,6 +4388,16 @@ dependencies = [ "digest", ] +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/nih_plug_xtask/Cargo.toml b/nih_plug_xtask/Cargo.toml index 4d97297e..c755cadd 100644 --- a/nih_plug_xtask/Cargo.toml +++ b/nih_plug_xtask/Cargo.toml @@ -14,3 +14,5 @@ goblin = "0.6.1" reflink = { git = "https://github.com/nicokoch/reflink.git", rev = "e8d93b465f5d9ad340cd052b64bbc77b8ee107e2" } serde = { version = "1.0", features = ["derive"] } toml = "0.7.2" +duct = "0.13.7" +serde_json = "1.0.132" diff --git a/nih_plug_xtask/src/lib.rs b/nih_plug_xtask/src/lib.rs index 71003bc7..dc7b1387 100644 --- a/nih_plug_xtask/src/lib.rs +++ b/nih_plug_xtask/src/lib.rs @@ -71,6 +71,97 @@ pub fn main() -> Result<()> { main_with_args("cargo xtask", args) } +pub fn get_build_plan( + command_name: &str, + args: impl IntoIterator, +) -> Result, usize)>> { + chdir_workspace_root()?; + + let mut args = args.into_iter().chain( + [ + "-Zunstable-options", + "--build-plan", + "--message-format=json-render-diagnostics", + ] + .into_iter() + .map(String::from), + ); + let usage_string = build_usage_string(command_name); + let command = args + .next() + .with_context(|| format!("Missing command name\n\n{usage_string}",))?; + match command.as_str() { + "bundle" => { + // For convenience's sake we'll allow building multiple packages with `-p` just like + // cargo build, but you can also build a single package without specifying `-p`. Since + // multiple packages can be built in parallel if we pass all of these flags to a single + // `cargo build` we'll first build all of these packages and only then bundle them. + let (packages, other_args) = split_bundle_args(args, &usage_string)?; + + // As explained above, for efficiency's sake this is a two step process + let output = build_plan(&packages, &other_args)?; + + let plan: serde_json::Value = serde_json::from_str(output.lines().nth(1).unwrap())?; + let num_invocations = plan["invocations"].as_array().unwrap().len(); + Ok(vec![(None, num_invocations)]) + } + "bundle-universal" => { + // The same as `--bundle`, but builds universal binaries for macOS Cargo will also error + // out on duplicate `--target` options, but it seems like a good idea to preemptively + // abort the bundling process if that happens + let (packages, other_args) = split_bundle_args(args, &usage_string)?; + + for arg in &other_args { + if arg == "--target" || arg.starts_with("--target=") { + anyhow::bail!( + "'{command_name} xtask bundle-universal' is incompatible with the '{arg}' \ + option." + ) + } + } + + // We can just use the regular build function here. There's sadly no way to build both + // targets in parallel, so this will likely take twice as logn as a regular build. + // TODO: Explicitly specifying the target even on the native target causes a rebuild in + // the target `target/` directory. This makes bundling much simpler + // because there's no conditional logic required based on the current platform, + // but it does waste some resources and requires a rebuild if the native target + // was already built. + let mut x86_64_args = other_args.clone(); + x86_64_args.push(String::from("--target=x86_64-apple-darwin")); + let mut aarch64_args = other_args.clone(); + aarch64_args.push(String::from("--target=aarch64-apple-darwin")); + + let x86_output = build_plan(&packages, &x86_64_args)?; + let x86_plan: serde_json::Value = + serde_json::from_str(x86_output.lines().nth(1).unwrap())?; + let x86_num_invocations = x86_plan["invocations"].as_array().unwrap().len(); + let aarch64_output = build_plan(&packages, &aarch64_args)?; + let aarch64_plan: serde_json::Value = + serde_json::from_str(aarch64_output.lines().nth(1).unwrap())?; + let aarch64_num_invocations = aarch64_plan["invocations"].as_array().unwrap().len(); + Ok(vec![ + (Some(String::from("x86")), x86_num_invocations), + (Some(String::from("aarch64")), aarch64_num_invocations), + ]) + } + _ => anyhow::bail!("Unknown command '{command}'\n\n{usage_string}"), + } +} + +fn build_plan(packages: &[String], args: &[String]) -> Result { + let package_args = packages.iter().flat_map(|package| ["-p", package]); + let output = duct::cmd( + "cargo", + ["build"] + .into_iter() + .chain(package_args) + .chain(args.iter().map(String::as_str)), + ) + .read()?; + Ok(output) +} + /// The main xtask entry point function, but with custom command line arguments. `args` should not /// contain the command name, so you should always skip at least one argument from /// `std::env::args()` before passing it to this function.