Skip to content

Commit f6b9c6d

Browse files
Merge pull request #47 from rust3ds/feature/link-debuginfo-profile
Use `cargo --unit-graph` to figure out whether to link debuginfo
2 parents 2276df9 + 7d6ddb2 commit f6b9c6d

File tree

5 files changed

+148
-14
lines changed

5 files changed

+148
-14
lines changed

Cargo.lock

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-3ds"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
authors = ["Rust3DS Org", "Andrea Ciliberti <[email protected]>"]
55
description = "Cargo wrapper for developing Nintendo 3DS homebrew apps"
66
repository = "https://github.com/rust3ds/cargo-3ds"
@@ -19,3 +19,4 @@ tee = "0.1.0"
1919
toml = "0.5.6"
2020
clap = { version = "4.0.15", features = ["derive", "wrap_help"] }
2121
shlex = "1.1.0"
22+
serde_json = "1.0.108"

src/command.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,8 +521,6 @@ romfs_dir = "romfs"
521521
const CUSTOM_MAIN_RS: &str = r#"use ctru::prelude::*;
522522
523523
fn main() {
524-
ctru::use_panic_handler();
525-
526524
let apt = Apt::new().unwrap();
527525
let mut hid = Hid::new().unwrap();
528526
let gfx = Gfx::new().unwrap();

src/graph.rs

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use std::error::Error;
2+
use std::io::Read;
3+
use std::process::{Command, Stdio};
4+
5+
use cargo_metadata::Target;
6+
use serde::Deserialize;
7+
8+
use crate::print_command;
9+
10+
/// In lieu of <https://github.com/oli-obk/cargo_metadata/issues/107>
11+
/// and to avoid pulling in the real `cargo`
12+
/// [data structures](https://docs.rs/cargo/latest/cargo/core/compiler/unit_graph/type.UnitGraph.html)
13+
/// as a dependency, we define the subset of the build graph we care about.
14+
#[derive(Deserialize)]
15+
pub struct UnitGraph {
16+
pub version: i32,
17+
pub units: Vec<Unit>,
18+
}
19+
20+
impl UnitGraph {
21+
/// Collect the unit graph via Cargo's `--unit-graph` flag.
22+
/// This runs the same command as the actual build, except nothing is actually
23+
/// build and the graph is output instead.
24+
///
25+
/// See <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph>.
26+
pub fn from_cargo(cargo_cmd: &Command, verbose: bool) -> Result<Self, Box<dyn Error>> {
27+
// Since Command isn't Clone, copy it "by hand", by copying its args and envs
28+
let mut cmd = Command::new(cargo_cmd.get_program());
29+
30+
let mut args = cargo_cmd.get_args();
31+
cmd.args(args.next())
32+
// These options must be added before any possible `--`, so the best
33+
// place is to just stick them immediately after the first arg (subcommand)
34+
.args(["-Z", "unstable-options", "--unit-graph"])
35+
.args(args)
36+
.envs(cargo_cmd.get_envs().filter_map(|(k, v)| Some((k, v?))))
37+
.stdout(Stdio::piped())
38+
.stderr(Stdio::piped());
39+
40+
if verbose {
41+
print_command(&cmd);
42+
}
43+
44+
let mut proc = cmd.spawn()?;
45+
let stdout = proc.stdout.take().unwrap();
46+
let mut stderr = proc.stderr.take().unwrap();
47+
48+
let result: Self = serde_json::from_reader(stdout).map_err(|err| {
49+
let mut stderr_str = String::new();
50+
let _ = stderr.read_to_string(&mut stderr_str);
51+
52+
let _ = proc.wait();
53+
format!("unable to parse `--unit-graph` json: {err}\nstderr: `{stderr_str}`")
54+
})?;
55+
56+
let _status = proc.wait()?;
57+
// TODO: with cargo 1.74.0-nightly (b4ddf95ad 2023-09-18),
58+
// `cargo run --unit-graph` panics at src/cargo/ops/cargo_run.rs:83:5
59+
// It seems to have been fixed as of cargo 1.76.0-nightly (71cd3a926 2023-11-20)
60+
// so maybe we can stop ignoring it once we bump the minimum toolchain version,
61+
// and certainly we should once `--unit-graph` is ever stabilized.
62+
//
63+
// if !status.success() {
64+
// return Err(format!("`cargo --unit-graph` exited with status {status:?}").into());
65+
// }
66+
67+
if result.version == 1 {
68+
Ok(result)
69+
} else {
70+
Err(format!(
71+
"unknown `cargo --unit-graph` output version {}",
72+
result.version
73+
))?
74+
}
75+
}
76+
}
77+
78+
#[derive(Deserialize)]
79+
pub struct Unit {
80+
pub target: Target,
81+
pub profile: Profile,
82+
}
83+
84+
/// This struct is very similar to [`cargo_metadata::ArtifactProfile`], but seems
85+
/// to have some slight differences so we define a different version. We only
86+
/// really care about `debuginfo` anyway.
87+
#[derive(Deserialize)]
88+
pub struct Profile {
89+
pub debuginfo: Option<u32>,
90+
}

src/lib.rs

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
pub mod command;
2+
mod graph;
23

34
use core::fmt;
5+
use std::ffi::OsStr;
46
use std::io::{BufRead, BufReader};
57
use std::path::{Path, PathBuf};
68
use std::process::{Command, ExitStatus, Stdio};
@@ -13,6 +15,7 @@ use semver::Version;
1315
use tee::TeeReader;
1416

1517
use crate::command::{CargoCmd, Run};
18+
use crate::graph::UnitGraph;
1619

1720
/// Build a command using [`make_cargo_build_command`] and execute it,
1821
/// parsing and returning the messages from the spawned process.
@@ -22,6 +25,23 @@ use crate::command::{CargoCmd, Run};
2225
pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus, Vec<Message>) {
2326
let mut command = make_cargo_command(input, &message_format);
2427

28+
let libctru = if should_use_ctru_debuginfo(&command, input.verbose) {
29+
"ctrud"
30+
} else {
31+
"ctru"
32+
};
33+
34+
let rustflags = command
35+
.get_envs()
36+
.find(|(var, _)| var == &OsStr::new("RUSTFLAGS"))
37+
.and_then(|(_, flags)| flags)
38+
.unwrap_or_default()
39+
.to_string_lossy();
40+
41+
let rustflags = format!("{rustflags} -l{libctru}");
42+
43+
command.env("RUSTFLAGS", rustflags);
44+
2545
if input.verbose {
2646
print_command(&command);
2747
}
@@ -57,27 +77,51 @@ pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus,
5777
(process.wait().unwrap(), messages)
5878
}
5979

80+
/// Ensure that we use the same `-lctru[d]` flag that `ctru-sys` is using in its build.
81+
fn should_use_ctru_debuginfo(cargo_cmd: &Command, verbose: bool) -> bool {
82+
match UnitGraph::from_cargo(cargo_cmd, verbose) {
83+
Ok(unit_graph) => {
84+
let Some(unit) = unit_graph
85+
.units
86+
.iter()
87+
.find(|unit| unit.target.name == "ctru-sys")
88+
else {
89+
eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: `ctru-sys` not found");
90+
return false;
91+
};
92+
93+
let debuginfo = unit.profile.debuginfo.unwrap_or(0);
94+
debuginfo > 0
95+
}
96+
Err(err) => {
97+
eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: {err}");
98+
false
99+
}
100+
}
101+
}
102+
60103
/// Create a cargo command based on the context.
61104
///
62105
/// For "build" commands (which compile code, such as `cargo 3ds build` or `cargo 3ds clippy`),
63106
/// if there is no pre-built std detected in the sysroot, `build-std` will be used instead.
64107
pub fn make_cargo_command(input: &Input, message_format: &Option<String>) -> Command {
108+
let devkitpro =
109+
env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable");
110+
// TODO: should we actually prepend the user's RUSTFLAGS for linking order? not sure
111+
let rustflags =
112+
env::var("RUSTFLAGS").unwrap_or_default() + &format!(" -L{devkitpro}/libctru/lib");
113+
65114
let cargo_cmd = &input.cmd;
66115

67116
let mut command = cargo(&input.config);
68-
command.arg(cargo_cmd.subcommand_name());
117+
command
118+
.arg(cargo_cmd.subcommand_name())
119+
.env("RUSTFLAGS", rustflags);
69120

70121
// Any command that needs to compile code will run under this environment.
71122
// Even `clippy` and `check` need this kind of context, so we'll just assume any other `Passthrough` command uses it too.
72123
if cargo_cmd.should_compile() {
73-
let rust_flags = env::var("RUSTFLAGS").unwrap_or_default()
74-
+ &format!(
75-
" -L{}/libctru/lib -lctru",
76-
env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable")
77-
);
78-
79124
command
80-
.env("RUSTFLAGS", rust_flags)
81125
.arg("--target")
82126
.arg("armv6k-nintendo-3ds")
83127
.arg("--message-format")

0 commit comments

Comments
 (0)