Skip to content

Commit

Permalink
Run build callbacks once per executable
Browse files Browse the repository at this point in the history
For each executable artifact generated by the build command, run the
build callback on it. Then, if there is only one artifact, run the
run/test callback on it.
  • Loading branch information
ian-h-chamberlain committed Jun 14, 2024
1 parent b7858c7 commit fbf77c1
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 55 deletions.
81 changes: 58 additions & 23 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use std::fs;
use std::io::Read;
use std::process;
use std::process::Stdio;
use std::sync::OnceLock;

use cargo_metadata::Message;
use cargo_metadata::{Message, Metadata};
use clap::{Args, Parser, Subcommand};

use crate::{build_3dsx, cargo, get_metadata, link, print_command, CTRConfig};
use crate::{build_3dsx, cargo, get_artifact_config, link, print_command, CTRConfig};

#[derive(Parser, Debug)]
#[command(name = "cargo", bin_name = "cargo")]
Expand Down Expand Up @@ -295,23 +296,64 @@ impl CargoCmd {
///
/// - `cargo 3ds build` and other "build" commands will use their callbacks to build the final `.3dsx` file and link it.
/// - `cargo 3ds new` and other generic commands will use their callbacks to make 3ds-specific changes to the environment.
pub fn run_callback(&self, messages: &[Message]) {
// Process the metadata only for commands that have it/use it
let config = if self.should_build_3dsx() {
eprintln!("Getting metadata");
pub fn run_callback(&self, messages: &[Message], metadata: &Metadata) {
let mut configs = Vec::new();

Some(get_metadata(messages))
} else {
None
};
// Process the metadata only for commands that have it/use it
if self.should_build_3dsx() {
configs = self.run_build_callbacks(messages, metadata);
}

// Run callback only for commands that use it
// For run + test, we can only run one of the targets. Error if more or less
// than one executable was built, otherwise run the callback for the first target.
match self {
Self::Build(cmd) => cmd.callback(&config),
Self::Run(cmd) => cmd.callback(&config),
Self::Test(cmd) => cmd.callback(&config),
Self::Run(cmd) if configs.len() == 1 => cmd.callback(&configs.into_iter().next()),
Self::Test(cmd) if configs.len() == 1 => cmd.callback(&configs.into_iter().next()),
Self::Run(_) | Self::Test(_) => {
let names: Vec<_> = configs.into_iter().map(|c| c.name).collect();
eprintln!("Error: expected exactly one executable to run, got {names:?}");
process::exit(1);
}
// New is a special case where we always want to run its callback
Self::New(cmd) => cmd.callback(),
_ => (),
_ => {}
}
}

/// Generate a .3dsx for every executable artifact within the workspace that
/// was built by the cargo command.
fn run_build_callbacks(&self, messages: &[Message], metadata: &Metadata) -> Vec<CTRConfig> {
let mut configs = Vec::new();

for message in messages {
let Message::CompilerArtifact(artifact) = message else {
continue;
};

if artifact.executable.is_none()
|| !metadata.workspace_members.contains(&artifact.package_id)
{
continue;
}

let package = &metadata[&artifact.package_id];
let config = Some(get_artifact_config(package.clone(), artifact.clone()));

self.run_artifact_callback(&config);

configs.push(config.unwrap());

Check warning on line 344 in src/command.rs

View workflow job for this annotation

GitHub Actions / lint (stable)

used `unwrap()` on `Some` value
}

configs
}

// TODO: can we just swap all signatures to &CTRConfig? Seems more sensible now
fn run_artifact_callback(&self, config: &Option<CTRConfig>) {
match self {
Self::Build(cmd) => cmd.callback(config),
Self::Run(cmd) => cmd.build_args.callback(config),
Self::Test(cmd) => cmd.run_args.build_args.callback(config),
_ => {}
}
}
}
Expand Down Expand Up @@ -403,9 +445,6 @@ impl Run {
///
/// This callback handles launching the application via `3dslink`.
fn callback(&self, config: &Option<CTRConfig>) {
// Run the normal "build" callback
self.build_args.callback(config);

if !self.use_custom_runner() {
if let Some(cfg) = config {
eprintln!("Running 3dslink");
Expand Down Expand Up @@ -462,11 +501,7 @@ impl Test {
///
/// This callback handles launching the application via `3dslink`.
fn callback(&self, config: &Option<CTRConfig>) {
if self.no_run {
// If the tests don't have to run, use the "build" callback
self.run_args.build_args.callback(config);
} else {
// If the tests have to run, use the "run" callback
if !self.no_run {
self.run_args.callback(config);
}
}
Expand Down
39 changes: 8 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::process::{Command, ExitStatus, Stdio};
use std::{env, fmt, io, process};

use camino::{Utf8Path, Utf8PathBuf};
use cargo_metadata::{Message, MetadataCommand};
use cargo_metadata::{Artifact, Message, Package};
use rustc_version::Channel;
use semver::Version;
use serde::Deserialize;
Expand Down Expand Up @@ -252,36 +252,10 @@ pub fn check_rust_version(input: &Input) {

/// Parses messages returned by "build" cargo commands (such as `cargo 3ds build` or `cargo 3ds run`).
/// The returned [`CTRConfig`] is then used for further building in and execution
/// in [`build_smdh`], [`build_3dsx`], and [`link`].
pub fn get_metadata(messages: &[Message]) -> CTRConfig {
let metadata = MetadataCommand::new()
.no_deps()
.exec()
.expect("Failed to get cargo metadata");

let mut package = None;
let mut artifact = None;

// Extract the final built executable. We may want to fail in cases where
// multiple executables, or none, were built?
for message in messages.iter().rev() {
if let Message::CompilerArtifact(art) = message {
if art.executable.is_some() {
package = Some(metadata[&art.package_id].clone());
artifact = Some(art.clone());

break;
}
}
}
if package.is_none() || artifact.is_none() {
eprintln!("No executable found from build command output!");
process::exit(1);
}

let (package, artifact) = (package.unwrap(), artifact.unwrap());

// for now assume a single "kind" since we only support one output artifact
/// in [`CTRConfig::build_smdh`], [`build_3dsx`], and [`link`].
pub fn get_artifact_config(package: Package, artifact: Artifact) -> CTRConfig {
// For now, assume a single "kind" per artifact. It seems to be the case
// when a single executable is built anyway but maybe not in all cases.
let name = match artifact.target.kind[0].as_ref() {
"bin" | "lib" | "rlib" | "dylib" if artifact.target.test => {
format!("{} tests", artifact.target.name)
Expand All @@ -292,6 +266,9 @@ pub fn get_metadata(messages: &[Message]) -> CTRConfig {
_ => artifact.target.name,
};

// TODO: need to break down by target kind and name, e.g.
// [package.metadata.cargo-3ds.example.hello-world]
// Probably fall back to top level as well.
let config = package
.metadata
.get("cargo-3ds")
Expand Down
7 changes: 6 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ fn main() {
}
};

let metadata = cargo_metadata::MetadataCommand::new()
.no_deps()
.exec()
.unwrap();

let (status, messages) = run_cargo(&input, message_format);

if !status.success() {
process::exit(status.code().unwrap_or(1));
}

input.cmd.run_callback(&messages);
input.cmd.run_callback(&messages, &metadata);
}

0 comments on commit fbf77c1

Please sign in to comment.