Skip to content

Commit 7fd38e2

Browse files
Merge pull request #60 from rust3ds/feature/multiple-outputs-take2
2 parents fb32005 + 59000ef commit 7fd38e2

File tree

3 files changed

+230
-116
lines changed

3 files changed

+230
-116
lines changed

src/command.rs

Lines changed: 124 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use std::fs;
22
use std::io::Read;
3-
use std::process::Stdio;
3+
use std::process::{self, Stdio};
44
use std::sync::OnceLock;
55

6-
use cargo_metadata::Message;
6+
use cargo_metadata::{Message, Metadata};
77
use clap::{Args, Parser, Subcommand};
88

9-
use crate::{build_3dsx, cargo, get_metadata, link, print_command, CTRConfig};
9+
use crate::{build_3dsx, cargo, get_artifact_config, link, print_command, CTRConfig};
1010

1111
#[derive(Parser, Debug)]
1212
#[command(name = "cargo", bin_name = "cargo")]
@@ -83,6 +83,12 @@ pub struct RemainingArgs {
8383
args: Vec<String>,
8484
}
8585

86+
#[allow(unused_variables)]
87+
trait Callbacks {
88+
fn build_callback(&self, config: &CTRConfig) {}
89+
fn run_callback(&self, config: &CTRConfig) {}
90+
}
91+
8692
#[derive(Args, Debug)]
8793
pub struct Build {
8894
#[arg(from_global)]
@@ -295,23 +301,93 @@ impl CargoCmd {
295301
///
296302
/// - `cargo 3ds build` and other "build" commands will use their callbacks to build the final `.3dsx` file and link it.
297303
/// - `cargo 3ds new` and other generic commands will use their callbacks to make 3ds-specific changes to the environment.
298-
pub fn run_callback(&self, messages: &[Message]) {
299-
// Process the metadata only for commands that have it/use it
300-
let config = if self.should_build_3dsx() {
301-
eprintln!("Getting metadata");
304+
pub fn run_callbacks(&self, messages: &[Message], metadata: Option<&Metadata>) {
305+
let configs = metadata
306+
.map(|metadata| self.build_callbacks(messages, metadata))
307+
.unwrap_or_default();
308+
309+
let config = match self {
310+
// If we produced one executable, we will attempt to run that one
311+
_ if configs.len() == 1 => configs.into_iter().next().unwrap(),
312+
313+
// --no-run may produce any number of executables, and we skip the callback
314+
Self::Test(Test { no_run: true, .. }) => return,
315+
316+
// If using custom runners, they may be able to handle multiple executables,
317+
// and we also want to skip our own callback. `cargo run` also has its own
318+
// logic to disallow multiple executables.
319+
Self::Test(Test { run_args: run, .. }) | Self::Run(run) if run.use_custom_runner() => {
320+
return
321+
}
302322

303-
Some(get_metadata(messages))
304-
} else {
305-
None
323+
// Config is ignored by the New callback, using default is fine.
324+
Self::New(_) => CTRConfig::default(),
325+
326+
// Otherwise (configs.len() != 1) print an error and exit
327+
Self::Test(_) | Self::Run(_) => {
328+
let paths: Vec<_> = configs.into_iter().map(|c| c.path_3dsx()).collect();
329+
let names: Vec<_> = paths.iter().filter_map(|p| p.file_name()).collect();
330+
eprintln!(
331+
"Error: expected exactly one (1) executable to run, got {}: {names:?}",
332+
paths.len(),
333+
);
334+
process::exit(1);
335+
}
336+
337+
_ => return,
306338
};
307339

308-
// Run callback only for commands that use it
340+
self.run_callback(&config);
341+
}
342+
343+
/// Generate a .3dsx for every executable artifact within the workspace that
344+
/// was built by the cargo command.
345+
fn build_callbacks(&self, messages: &[Message], metadata: &Metadata) -> Vec<CTRConfig> {
346+
let max_artifact_count = metadata.packages.iter().map(|pkg| pkg.targets.len()).sum();
347+
let mut configs = Vec::with_capacity(max_artifact_count);
348+
349+
for message in messages {
350+
let Message::CompilerArtifact(artifact) = message else {
351+
continue;
352+
};
353+
354+
if artifact.executable.is_none()
355+
|| !metadata.workspace_members.contains(&artifact.package_id)
356+
{
357+
continue;
358+
}
359+
360+
let package = &metadata[&artifact.package_id];
361+
let config = get_artifact_config(package.clone(), artifact.clone());
362+
363+
self.build_callback(&config);
364+
365+
configs.push(config);
366+
}
367+
368+
configs
369+
}
370+
371+
fn inner_callback(&self) -> Option<&dyn Callbacks> {
309372
match self {
310-
Self::Build(cmd) => cmd.callback(&config),
311-
Self::Run(cmd) => cmd.callback(&config),
312-
Self::Test(cmd) => cmd.callback(&config),
313-
Self::New(cmd) => cmd.callback(),
314-
_ => (),
373+
Self::Build(cmd) => Some(cmd),
374+
Self::Run(cmd) => Some(cmd),
375+
Self::Test(cmd) => Some(cmd),
376+
_ => None,
377+
}
378+
}
379+
}
380+
381+
impl Callbacks for CargoCmd {
382+
fn build_callback(&self, config: &CTRConfig) {
383+
if let Some(cb) = self.inner_callback() {
384+
cb.build_callback(config);
385+
}
386+
}
387+
388+
fn run_callback(&self, config: &CTRConfig) {
389+
if let Some(cb) = self.inner_callback() {
390+
cb.run_callback(config);
315391
}
316392
}
317393
}
@@ -342,18 +418,30 @@ impl RemainingArgs {
342418
}
343419
}
344420

345-
impl Build {
421+
impl Callbacks for Build {
346422
/// Callback for `cargo 3ds build`.
347423
///
348424
/// This callback handles building the application as a `.3dsx` file.
349-
fn callback(&self, config: &Option<CTRConfig>) {
350-
if let Some(config) = config {
351-
eprintln!("Building smdh: {}", config.path_smdh());
352-
config.build_smdh(self.verbose);
425+
fn build_callback(&self, config: &CTRConfig) {
426+
eprintln!("Building smdh: {}", config.path_smdh());
427+
config.build_smdh(self.verbose);
353428

354-
eprintln!("Building 3dsx: {}", config.path_3dsx());
355-
build_3dsx(config, self.verbose);
356-
}
429+
eprintln!("Building 3dsx: {}", config.path_3dsx());
430+
build_3dsx(config, self.verbose);
431+
}
432+
}
433+
434+
impl Callbacks for Run {
435+
fn build_callback(&self, config: &CTRConfig) {
436+
self.build_args.build_callback(config);
437+
}
438+
439+
/// Callback for `cargo 3ds run`.
440+
///
441+
/// This callback handles launching the application via `3dslink`.
442+
fn run_callback(&self, config: &CTRConfig) {
443+
eprintln!("Running 3dslink");
444+
link(config, self, self.build_args.verbose);
357445
}
358446
}
359447

@@ -399,21 +487,6 @@ impl Run {
399487
args
400488
}
401489

402-
/// Callback for `cargo 3ds run`.
403-
///
404-
/// This callback handles launching the application via `3dslink`.
405-
fn callback(&self, config: &Option<CTRConfig>) {
406-
// Run the normal "build" callback
407-
self.build_args.callback(config);
408-
409-
if !self.use_custom_runner() {
410-
if let Some(cfg) = config {
411-
eprintln!("Running 3dslink");
412-
link(cfg, self, self.build_args.verbose);
413-
}
414-
}
415-
}
416-
417490
/// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner`
418491
/// configured. This will only be checked once during the lifetime of the program,
419492
/// and takes into account the usual ways Cargo looks for its
@@ -457,20 +530,22 @@ impl Run {
457530
}
458531
}
459532

460-
impl Test {
533+
impl Callbacks for Test {
534+
fn build_callback(&self, config: &CTRConfig) {
535+
self.run_args.build_callback(config);
536+
}
537+
461538
/// Callback for `cargo 3ds test`.
462539
///
463540
/// This callback handles launching the application via `3dslink`.
464-
fn callback(&self, config: &Option<CTRConfig>) {
465-
if self.no_run {
466-
// If the tests don't have to run, use the "build" callback
467-
self.run_args.build_args.callback(config);
468-
} else {
469-
// If the tests have to run, use the "run" callback
470-
self.run_args.callback(config);
541+
fn run_callback(&self, config: &CTRConfig) {
542+
if !self.no_run {
543+
self.run_args.run_callback(config);
471544
}
472545
}
546+
}
473547

548+
impl Test {
474549
fn should_run(&self) -> bool {
475550
self.run_args.use_custom_runner() && !self.no_run
476551
}
@@ -540,11 +615,11 @@ fn main() {
540615
}
541616
"#;
542617

543-
impl New {
618+
impl Callbacks for New {
544619
/// Callback for `cargo 3ds new`.
545620
///
546621
/// This callback handles the custom environment modifications when creating a new 3DS project.
547-
fn callback(&self) {
622+
fn run_callback(&self, _: &CTRConfig) {
548623
// Commmit changes to the project only if is meant to be a binary
549624
if self.cargo_args.args.contains(&"--lib".to_string()) {
550625
return;

0 commit comments

Comments
 (0)