Browse Source

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.
pull/47/head
Ian Chamberlain 1 year ago
parent
commit
545d853a80
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 5
      Cargo.lock
  2. 1
      Cargo.toml
  3. 85
      src/graph.rs
  4. 46
      src/lib.rs

5
Cargo.lock generated

@ -74,6 +74,7 @@ dependencies = [ @@ -74,6 +74,7 @@ dependencies = [
"rustc_version",
"semver",
"serde",
"serde_json",
"shlex",
"tee",
"toml",
@ -279,9 +280,9 @@ dependencies = [ @@ -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",

1
Cargo.toml

@ -19,3 +19,4 @@ tee = "0.1.0" @@ -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"

85
src/graph.rs

@ -0,0 +1,85 @@ @@ -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 <https://github.com/oli-obk/cargo_metadata/issues/107>
/// 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<Unit>,
}
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 <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph>.
pub fn from_cargo(cargo_cmd: &Command, verbose: bool) -> Result<Self, Box<dyn Error>> {
// 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<u32>,
}

46
src/lib.rs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
pub mod command;
mod graph;
use core::fmt;
use std::io::{BufRead, BufReader};
@ -13,6 +14,7 @@ use semver::Version; @@ -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}; @@ -22,6 +24,20 @@ use crate::command::{CargoCmd, Run};
pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus, Vec<Message>) {
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<String>) -> (ExitStatus, @@ -57,6 +73,29 @@ pub fn run_cargo(input: &Input, message_format: Option<String>) -> (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<String>) -> Com @@ -70,14 +109,7 @@ pub fn make_cargo_command(input: &Input, message_format: &Option<String>) -> 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")

Loading…
Cancel
Save