|
1 | 1 | use std::fs; |
2 | 2 | use std::io::Read; |
3 | | -use std::process::Stdio; |
| 3 | +use std::process::{self, Stdio}; |
4 | 4 | use std::sync::OnceLock; |
5 | 5 |
|
6 | | -use cargo_metadata::Message; |
| 6 | +use cargo_metadata::{Message, Metadata}; |
7 | 7 | use clap::{Args, Parser, Subcommand}; |
8 | 8 |
|
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}; |
10 | 10 |
|
11 | 11 | #[derive(Parser, Debug)] |
12 | 12 | #[command(name = "cargo", bin_name = "cargo")] |
@@ -83,6 +83,12 @@ pub struct RemainingArgs { |
83 | 83 | args: Vec<String>, |
84 | 84 | } |
85 | 85 |
|
| 86 | +#[allow(unused_variables)] |
| 87 | +trait Callbacks { |
| 88 | + fn build_callback(&self, config: &CTRConfig) {} |
| 89 | + fn run_callback(&self, config: &CTRConfig) {} |
| 90 | +} |
| 91 | + |
86 | 92 | #[derive(Args, Debug)] |
87 | 93 | pub struct Build { |
88 | 94 | #[arg(from_global)] |
@@ -295,23 +301,93 @@ impl CargoCmd { |
295 | 301 | /// |
296 | 302 | /// - `cargo 3ds build` and other "build" commands will use their callbacks to build the final `.3dsx` file and link it. |
297 | 303 | /// - `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 | + } |
302 | 322 |
|
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, |
306 | 338 | }; |
307 | 339 |
|
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> { |
309 | 372 | 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); |
315 | 391 | } |
316 | 392 | } |
317 | 393 | } |
@@ -342,18 +418,30 @@ impl RemainingArgs { |
342 | 418 | } |
343 | 419 | } |
344 | 420 |
|
345 | | -impl Build { |
| 421 | +impl Callbacks for Build { |
346 | 422 | /// Callback for `cargo 3ds build`. |
347 | 423 | /// |
348 | 424 | /// 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); |
353 | 428 |
|
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); |
357 | 445 | } |
358 | 446 | } |
359 | 447 |
|
@@ -399,21 +487,6 @@ impl Run { |
399 | 487 | args |
400 | 488 | } |
401 | 489 |
|
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 | | - |
417 | 490 | /// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner` |
418 | 491 | /// configured. This will only be checked once during the lifetime of the program, |
419 | 492 | /// and takes into account the usual ways Cargo looks for its |
@@ -457,20 +530,22 @@ impl Run { |
457 | 530 | } |
458 | 531 | } |
459 | 532 |
|
460 | | -impl Test { |
| 533 | +impl Callbacks for Test { |
| 534 | + fn build_callback(&self, config: &CTRConfig) { |
| 535 | + self.run_args.build_callback(config); |
| 536 | + } |
| 537 | + |
461 | 538 | /// Callback for `cargo 3ds test`. |
462 | 539 | /// |
463 | 540 | /// 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); |
471 | 544 | } |
472 | 545 | } |
| 546 | +} |
473 | 547 |
|
| 548 | +impl Test { |
474 | 549 | fn should_run(&self) -> bool { |
475 | 550 | self.run_args.use_custom_runner() && !self.no_run |
476 | 551 | } |
@@ -540,11 +615,11 @@ fn main() { |
540 | 615 | } |
541 | 616 | "#; |
542 | 617 |
|
543 | | -impl New { |
| 618 | +impl Callbacks for New { |
544 | 619 | /// Callback for `cargo 3ds new`. |
545 | 620 | /// |
546 | 621 | /// This callback handles the custom environment modifications when creating a new 3DS project. |
547 | | - fn callback(&self) { |
| 622 | + fn run_callback(&self, _: &CTRConfig) { |
548 | 623 | // Commmit changes to the project only if is meant to be a binary |
549 | 624 | if self.cargo_args.args.contains(&"--lib".to_string()) { |
550 | 625 | return; |
|
0 commit comments