From 545d853a8093b48aa7cb843555db44118993785a Mon Sep 17 00:00:00 2001 From: Ian Chamberlain Date: Wed, 15 Nov 2023 23:47:02 -0500 Subject: [PATCH] Use `cargo --unit-graph` to figure out debuginfo We can check if `ctru-sys` is being built with debuginfo this way, and use that to build the stdlib with the same linker flags as ctru-sys is expected to use. --- Cargo.lock | 5 ++-- Cargo.toml | 1 + src/graph.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 46 +++++++++++++++++++++++----- 4 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 src/graph.rs diff --git a/Cargo.lock b/Cargo.lock index 073a874..21ac74a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,7 @@ dependencies = [ "rustc_version", "semver", "serde", + "serde_json", "shlex", "tee", "toml", @@ -279,9 +280,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index 6a8f244..2354eff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ tee = "0.1.0" toml = "0.5.6" clap = { version = "4.0.15", features = ["derive", "wrap_help"] } shlex = "1.1.0" +serde_json = "1.0.108" diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 0000000..e1cf323 --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,85 @@ +use std::error::Error; +use std::process::{Command, Stdio}; + +use cargo_metadata::Target; +use serde::Deserialize; + +use crate::print_command; + +/// In lieu of +/// and to avoid pulling in the real `cargo` +/// [data structures](https://docs.rs/cargo/latest/cargo/core/compiler/unit_graph/type.UnitGraph.html) +/// as a dependency, we define the subset of the build graph we care about. +#[derive(Deserialize)] +pub struct UnitGraph { + pub version: i32, + pub units: Vec, +} + +impl UnitGraph { + /// Collect the unit graph via Cargo's `--unit-graph` flag. + /// This runs the same command as the actual build, except nothing is actually + /// build and the graph is output instead. + /// + /// See . + pub fn from_cargo(cargo_cmd: &Command, verbose: bool) -> Result> { + // Since Command isn't Clone, copy it "by hand": + let mut cmd = Command::new(cargo_cmd.get_program()); + + // TODO: this should probably use "build" subcommand for "run", since right + // now there appears to be a crash in cargo when using `run`: + // + // thread 'main' panicked at src/cargo/ops/cargo_run.rs:83:5: + // assertion `left == right` failed + // left: 0 + // right: 1 + + let mut args = cargo_cmd.get_args(); + cmd.arg(args.next().unwrap()) + // These options must be added before any possible `--`, so the best + // place is to just stick them immediately after the first arg (subcommand) + .args(["-Z", "unstable-options", "--unit-graph"]) + .args(args) + .stdout(Stdio::piped()); + + if verbose { + print_command(&cmd); + } + + let mut proc = cmd.spawn()?; + let stdout = proc.stdout.take().unwrap(); + + let result: Self = serde_json::from_reader(stdout).map_err(|err| { + let _ = proc.wait(); + err + })?; + + let status = proc.wait()?; + if !status.success() { + return Err(format!("`cargo --unit-graph` exited with status {status:?}").into()); + } + + if result.version == 1 { + Ok(result) + } else { + Err(format!( + "unknown `cargo --unit-graph` output version {}", + result.version + ))? + } + } +} + +#[derive(Deserialize)] +pub struct Unit { + pub target: Target, + pub profile: Profile, +} + +/// This struct is very similar to [`cargo_metadata::ArtifactProfile`], but seems +/// to have some slight differences so we define a different version. We only +/// really care about `debuginfo` anyway. +#[derive(Deserialize)] +pub struct Profile { + pub debuginfo: Option, +} diff --git a/src/lib.rs b/src/lib.rs index 8311048..e74695c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod command; +mod graph; use core::fmt; use std::io::{BufRead, BufReader}; @@ -13,6 +14,7 @@ use semver::Version; use tee::TeeReader; use crate::command::{CargoCmd, Run}; +use crate::graph::UnitGraph; /// Build a command using [`make_cargo_build_command`] and execute it, /// parsing and returning the messages from the spawned process. @@ -22,6 +24,20 @@ use crate::command::{CargoCmd, Run}; pub fn run_cargo(input: &Input, message_format: Option) -> (ExitStatus, Vec) { let mut command = make_cargo_command(input, &message_format); + let libctru = if should_use_ctru_debuginfo(&command, input.verbose) { + "ctrud" + } else { + "ctru" + }; + + let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() + + &format!( + " -L{}/libctru/lib -l{libctru}", + env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable") + ); + + command.env("RUSTFLAGS", rust_flags); + if input.verbose { print_command(&command); } @@ -57,6 +73,29 @@ pub fn run_cargo(input: &Input, message_format: Option) -> (ExitStatus, (process.wait().unwrap(), messages) } +/// Ensure that we use the same `-lctru[d]` flag that `ctru-sys` is using in its build. +fn should_use_ctru_debuginfo(cargo_cmd: &Command, verbose: bool) -> bool { + match UnitGraph::from_cargo(cargo_cmd, verbose) { + Ok(unit_graph) => { + let Some(unit) = unit_graph + .units + .iter() + .find(|unit| unit.target.name == "ctru-sys") + else { + eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: `ctru-sys` not found"); + return false; + }; + + let debuginfo = unit.profile.debuginfo.unwrap_or(0); + debuginfo > 0 + } + Err(err) => { + eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: {err}"); + false + } + } +} + /// Create a cargo command based on the context. /// /// For "build" commands (which compile code, such as `cargo 3ds build` or `cargo 3ds clippy`), @@ -70,14 +109,7 @@ pub fn make_cargo_command(input: &Input, message_format: &Option) -> Com // Any command that needs to compile code will run under this environment. // Even `clippy` and `check` need this kind of context, so we'll just assume any other `Passthrough` command uses it too. if cargo_cmd.should_compile() { - let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() - + &format!( - " -L{}/libctru/lib -lctru", - env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable") - ); - command - .env("RUSTFLAGS", rust_flags) .arg("--target") .arg("armv6k-nintendo-3ds") .arg("--message-format")