From 7ab8bcf308438a06419b9e895886b0dd0124e5a9 Mon Sep 17 00:00:00 2001 From: Alex Janka Date: Sun, 11 Aug 2024 12:21:29 +1000 Subject: [PATCH] xtask: build/run --- .cargo/config.toml | 3 +- Cargo.lock | 48 +++++++- xtask/Cargo.toml | 6 + xtask/src/args.rs | 57 ++++++++++ xtask/src/main.rs | 265 ++++++++++++++++++++++++++++++++++++++++++++- xtask/src/types.rs | 96 ++++++++++++++++ 6 files changed, 469 insertions(+), 6 deletions(-) create mode 100644 xtask/src/args.rs create mode 100644 xtask/src/types.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index ebca307..60b4318 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,8 +1,7 @@ [alias] -xtask = "run --package xtask --release --" +xtask = "run --package xtask --" [profile.dev] -opt-level = 1 lto = false [profile.release] diff --git a/Cargo.lock b/Cargo.lock index 429f805..c2f9b11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4344,11 +4344,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -4507,6 +4508,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.71", +] + [[package]] name = "syn" version = "1.0.109" @@ -5832,11 +5855,32 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" +[[package]] +name = "xshell" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db0ab86eae739efd1b054a8d3d16041914030ac4e01cd1dca0cf252fd8b6437" +dependencies = [ + "xshell-macros", +] + +[[package]] +name = "xshell-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d422e8e38ec76e2f06ee439ccc765e9c6a9638b9e7c9f2e8255e4d41e8bd852" + [[package]] name = "xtask" version = "0.1.0" dependencies = [ + "cargo_metadata", + "clap", + "env_logger", + "log", "nih_plug_xtask", + "strum", + "xshell", ] [[package]] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index b826c1c..6520fa2 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -6,4 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version = "4.5.15", features = ["derive"] } +strum = { version = "0.26.3", features = ["derive"] } +xshell = "0.2.6" +cargo_metadata = "0.18.1" nih_plug_xtask = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } diff --git a/xtask/src/args.rs b/xtask/src/args.rs new file mode 100644 index 0000000..4496ab7 --- /dev/null +++ b/xtask/src/args.rs @@ -0,0 +1,57 @@ +use clap::Parser; + +use crate::types::*; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Args { + #[command(subcommand)] + pub command: Commands, + + #[arg(long)] + /// Defaults to CARGO_WORKSPACE_DIR/build, unless XTASK_TARGET_DIR is set. + pub target_dir: Option, + + #[arg(long)] + /// Build in debug mode instead of release mode + pub debug: bool, +} + +#[derive(clap::Subcommand, Debug)] +pub enum Commands { + #[command(allow_external_subcommands = true)] + Bundle, + #[command(allow_external_subcommands = true)] + BundleUniversal, + Build(BuildArgs), + #[command(allow_external_subcommands = true)] + Run(RunArgs), +} + +#[derive(clap::Args, Debug)] +#[command(group(clap::ArgGroup::new("platforms").args(["platform","all_platforms"]).required(true)))] +#[command(group(clap::ArgGroup::new("binaries").args(["binary","all_binaries"]).required(true)))] +pub struct BuildArgs { + #[arg(long, short)] + pub platform: Vec, + #[arg(long)] + pub all_platforms: bool, + + #[arg(long, short)] + pub binary: Vec, + #[arg(long)] + pub all_binaries: bool, + + #[arg(long, short)] + pub renderer: Vec, +} + +#[derive(clap::Args, Debug)] +pub struct RunArgs { + #[arg(long, short)] + pub platform: Option, + #[arg(long, short)] + pub binary: Option, + #[arg(long, short)] + pub renderer: Option, +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 11fecb6..6fe094a 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,3 +1,264 @@ -fn main() -> nih_plug_xtask::Result<()> { - nih_plug_xtask::main() +use clap::Parser; +use strum::IntoEnumIterator; +use xshell::Shell; + +use args::*; +use types::*; +mod args; +mod types; + +static METADATA: std::sync::OnceLock = std::sync::OnceLock::new(); +static OUTPUT_DIR: std::sync::OnceLock = std::sync::OnceLock::new(); + +fn executable_dir(binary: Binary, platform: Platform, renderer: Renderer) -> String { + format!( + "{}_{}_{}", + binary.name(), + platform.as_target(), + renderer.as_feature() + ) +} + +fn main() -> Result<(), Box> { + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info"); + } + env_logger::init(); + let args = Args::parse(); + + let metadata = cargo_metadata::MetadataCommand::new().exec()?; + + let target_dir = args + .target_dir + .or(std::env::var("XTASK_TARGET_DIR") + .ok() + .map(std::path::PathBuf::from)) + .unwrap_or_else(|| std::path::PathBuf::from(&metadata.target_directory).join("xbuild")); + + OUTPUT_DIR.set(target_dir).map_err(|_| CouldntSetOnceLock)?; + METADATA.set(metadata).map_err(|_| CouldntSetOnceLock)?; + + match args.command { + Commands::Bundle | Commands::BundleUniversal => { + let args = std::env::args().skip(1).collect::>(); + nih_plug_xtask::main_with_args("cargo xtask", args.into_iter())?; + Ok(()) + } + Commands::Build(build_args) => build(build_args, args.debug), + Commands::Run(run_args) => run(run_args, args.debug), + } +} + +fn build(args: BuildArgs, debug: bool) -> Result<(), Box> { + let platforms = if args.all_platforms { + Platform::iter().collect() + } else { + args.platform + }; + log::info!("platforms: {platforms:?}"); + + let binaries = if args.all_binaries { + Binary::iter().collect() + } else { + args.binary + }; + log::info!("binaries: {binaries:?}"); + + let renderers = if args.renderer.is_empty() { + vec![Renderer::Wgpu] + } else { + args.renderer + }; + log::info!("renderers: {renderers:?}"); + + for binary in binaries { + for platform in &platforms { + for renderer in &renderers { + build_binary(binary, *platform, *renderer, debug)?; + } + } + } + + Ok(()) +} + +fn run(args: RunArgs, debug: bool) -> Result<(), Box> { + let platform = args.platform.unwrap_or(Platform::host_platform()); + let binary = args.binary.unwrap_or(Binary::get_default()); + let renderer = args.renderer.unwrap_or(Renderer::Wgpu); + match binary { + Binary::Gui => run_gui(platform, renderer, debug), + Binary::Cli => run_cli(platform, renderer, debug), + Binary::Vst => { + eprintln!("twinc_emu_vst doesn't support standalone usage!"); + std::process::exit(1); + } + } +} + +fn build_binary( + binary: Binary, + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + let output_dir = OUTPUT_DIR + .get() + .unwrap() + .join(executable_dir(binary, platform, renderer)); + + std::fs::create_dir_all(&output_dir)?; + match binary { + Binary::Gui => build_gui(output_dir, platform, renderer, debug), + Binary::Cli => build_cli(output_dir, platform, renderer, debug), + Binary::Vst => build_vst(platform, renderer, debug), + } +} + +fn build_gui( + output_dir: std::path::PathBuf, + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + let ui = platform.ui(); + + run_build( + "gui", + platform, + renderer, + debug, + Some(["-F", ui].into_iter().map(String::from).collect()), + output_dir, + ) +} + +fn run_gui( + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + let ui = platform.ui(); + + let _ = cargo_exec( + "run", + "gui", + platform, + renderer, + debug, + Some(["-F", ui].into_iter().map(String::from).collect()), + )?; + Ok(()) +} + +fn build_cli( + output_dir: std::path::PathBuf, + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + run_build("cli", platform, renderer, debug, None, output_dir) +} + +fn run_cli( + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + let args = std::env::args().skip(1).collect::>(); + let additional_flags = args + .iter() + .position(|arg| arg == "--") + .map(|extra_args_index| args[extra_args_index..].to_vec()); + + let _ = cargo_exec("run", "cli", platform, renderer, debug, additional_flags)?; + Ok(()) +} + +fn run_build( + package: &str, + platform: Platform, + renderer: Renderer, + debug: bool, + additional_flags: Option>, + output_dir: std::path::PathBuf, +) -> Result<(), Box> { + for (name, executable_path) in cargo_exec( + "build", + package, + platform, + renderer, + debug, + additional_flags, + )? + .filter_map(|m| as_artifact(m.ok()?)) + .filter_map(|a| a.executable.map(|e| (a.target.name, e))) + { + std::fs::copy(executable_path, output_dir.join(name))?; + } + + Ok(()) +} + +fn cargo_exec( + verb: &str, + package: &str, + platform: Platform, + renderer: Renderer, + debug: bool, + additional_flags: Option>, +) -> Result< + impl Iterator>, + Box, +> { + let sh = Shell::new()?; + let target = platform.as_target(); + let release = if debug { "" } else { "--release" }; + + let renderer = renderer.as_feature(); + let args=format!("{verb} -p {package} --target {target} {release} --no-default-features -F {renderer} --message-format=json"); + let args = args.split_whitespace().map(|s| s.to_string()); + let args = if let Some(additional_flags) = additional_flags { + args.chain(additional_flags).collect::>() + } else { + args.collect::>() + }; + + let output = sh.cmd("cargo").args(args).read()?; + + Ok(cargo_metadata::Message::parse_stream(std::io::Cursor::new( + output, + ))) +} + +fn as_artifact(message: cargo_metadata::Message) -> Option { + if let cargo_metadata::Message::CompilerArtifact(a) = message { + Some(a) + } else { + None + } +} + +fn build_vst( + platform: Platform, + renderer: Renderer, + debug: bool, +) -> Result<(), Box> { + let mut args = if platform == Platform::Mac { + vec!["bundle-universal"] + } else { + vec!["bundle", "--target", platform.as_target()] + }; + if !debug { + args.push("--release"); + } + let more_args = "-p twinc_emu_vst --no-default-features -F plugin"; + for arg in more_args.split_whitespace() { + args.push(arg); + } + args.push("-F"); + args.push(renderer.as_feature()); + + nih_plug_xtask::main_with_args("cargo xtask", args.into_iter().map(String::from))?; + Ok(()) } diff --git a/xtask/src/types.rs b/xtask/src/types.rs new file mode 100644 index 0000000..4304ae2 --- /dev/null +++ b/xtask/src/types.rs @@ -0,0 +1,96 @@ +#[derive(clap::ValueEnum, Clone, Copy, Debug, strum::EnumIter, PartialEq, Eq)] +pub enum Platform { + Windows, + Linux, + Mac, +} + +impl Platform { + pub fn as_target(&self) -> &str { + match self { + Platform::Windows => "x86_64-pc-windows-gnu", + Platform::Linux => "x86_64-unknown-linux-gnu", + Platform::Mac => "aarch64-apple-darwin", + } + } + + pub fn ui(&self) -> &str { + match self { + Platform::Windows | Platform::Linux => "crossplatform-ui", + Platform::Mac => "macos-ui", + } + } + + pub fn host_platform() -> Self { + if cfg!(target_os = "windows") { + Self::Windows + } else if cfg!(target_os = "macos") { + Self::Mac + } else if cfg!(target_os = "linux") { + Self::Linux + } else { + todo!() + } + } +} + +#[derive(clap::ValueEnum, Clone, Copy, Debug, strum::EnumIter, PartialEq, Eq)] +pub enum Binary { + Gui, + Cli, + Vst, +} + +impl Binary { + pub fn name(&self) -> &str { + match self { + Binary::Gui => "gui", + Binary::Cli => "cli", + Binary::Vst => "vst", + } + } + + pub fn get_default() -> Self { + Self::Cli + } +} + +#[derive(clap::ValueEnum, Clone, Copy, Debug, strum::EnumIter, PartialEq, Eq)] +pub enum Renderer { + Wgpu, + Vulkan, + Pixels, +} + +impl Renderer { + pub fn as_feature(&self) -> &str { + match self { + Renderer::Wgpu => "wgpu", + Renderer::Vulkan => "vulkan", + Renderer::Pixels => "pixels", + } + } +} + +#[derive(Debug)] +pub struct CouldntSetOnceLock; + +impl std::fmt::Display for CouldntSetOnceLock { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self, f) + } +} + +impl std::error::Error for CouldntSetOnceLock { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + + fn description(&self) -> &str { + "description() is deprecated; use Display" + } + + fn cause(&self) -> Option<&dyn std::error::Error> { + self.source() + } +}