Browse Source

Add plumbing for new commands to running cargo

pull/26/head
Ian Chamberlain 2 years ago
parent
commit
dbf1595def
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 170
      src/command.rs
  2. 112
      src/lib.rs
  3. 67
      src/main.rs

170
src/command.rs

@ -23,7 +23,7 @@ pub struct Input {
#[command(allow_external_subcommands = true)] #[command(allow_external_subcommands = true)]
pub enum CargoCmd { pub enum CargoCmd {
/// Builds an executable suitable to run on a 3DS (3dsx). /// Builds an executable suitable to run on a 3DS (3dsx).
Build(CargoArgs), Build(RemainingArgs),
/// Builds an executable and sends it to a device with `3dslink`. /// Builds an executable and sends it to a device with `3dslink`.
Run(Run), Run(Run),
@ -43,7 +43,7 @@ pub enum CargoCmd {
} }
#[derive(Args, Debug)] #[derive(Args, Debug)]
pub struct CargoArgs { pub struct RemainingArgs {
/// Pass additional options through to the `cargo` command. /// Pass additional options through to the `cargo` command.
/// ///
/// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds`
@ -101,12 +101,74 @@ pub struct Run {
// Passthrough cargo options. // Passthrough cargo options.
#[command(flatten)] #[command(flatten)]
pub cargo_args: CargoArgs, pub cargo_args: RemainingArgs,
} }
impl CargoArgs { impl CargoCmd {
/// Whether or not this command should build a 3DSX executable file.
pub fn should_build_3dsx(&self) -> bool {
matches!(self, Self::Build(_) | Self::Run(_) | Self::Test(_))
}
/// Whether or not the resulting executable should be sent to the 3DS with
/// `3dslink`.
pub fn should_link_to_device(&self) -> bool {
match self {
CargoCmd::Test(test) => !test.no_run,
CargoCmd::Run(_) => true,
_ => false,
}
}
pub const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics";
pub fn extract_message_format(&mut self) -> Result<Option<String>, String> {
Self::extract_message_format_from_args(match self {
CargoCmd::Build(args) => &mut args.args,
CargoCmd::Run(run) => &mut run.cargo_args.args,
CargoCmd::Test(test) => &mut test.run_args.cargo_args.args,
CargoCmd::Passthrough(args) => args,
})
}
fn extract_message_format_from_args(
cargo_args: &mut Vec<String>,
) -> Result<Option<String>, String> {
// Checks for a position within the args where '--message-format' is located
if let Some(pos) = cargo_args
.iter()
.position(|s| s.starts_with("--message-format"))
{
// Remove the arg from list so we don't pass anything twice by accident
let arg = cargo_args.remove(pos);
// Allows for usage of '--message-format=<format>' and also using space separation.
// Check for a '=' delimiter and use the second half of the split as the format,
// otherwise remove next arg which is now at the same position as the original flag.
let format = if let Some((_, format)) = arg.split_once('=') {
format.to_string()
} else {
// Also need to remove the argument to the --message-format option
cargo_args.remove(pos)
};
// Non-json formats are not supported so the executable exits.
if format.starts_with("json") {
Ok(Some(format))
} else {
Err(String::from(
"error: non-JSON `message-format` is not supported",
))
}
} else {
Ok(None)
}
}
}
impl RemainingArgs {
/// Get the args to be passed to the executable itself (not `cargo`). /// Get the args to be passed to the executable itself (not `cargo`).
pub fn cargo_opts(&self) -> &[String] { pub fn cargo_args(&self) -> &[String] {
self.split_args().0 self.split_args().0
} }
@ -116,17 +178,8 @@ impl CargoArgs {
} }
fn split_args(&self) -> (&[String], &[String]) { fn split_args(&self) -> (&[String], &[String]) {
if let Some(split) = self if let Some(split) = self.args.iter().position(|s| s == "--") {
.args self.args.split_at(split + 1)
.iter()
.position(|s| s == "--" || !s.starts_with('-'))
{
let split = if self.args[split] == "--" {
split + 1
} else {
split
};
self.args.split_at(split)
} else { } else {
(&self.args[..], &[]) (&self.args[..], &[])
} }
@ -143,4 +196,89 @@ mod tests {
fn verify_app() { fn verify_app() {
Cargo::command().debug_assert(); Cargo::command().debug_assert();
} }
#[test]
fn extract_format() {
for (args, expected) in [
(&["--foo", "--message-format=json", "bar"][..], Some("json")),
(&["--foo", "--message-format", "json", "bar"], Some("json")),
(
&[
"--foo",
"--message-format",
"json-render-diagnostics",
"bar",
],
Some("json-render-diagnostics"),
),
(
&["--foo", "--message-format=json-render-diagnostics", "bar"],
Some("json-render-diagnostics"),
),
] {
let mut cmd = CargoCmd::Build(RemainingArgs {
args: args.iter().map(ToString::to_string).collect(),
});
assert_eq!(
cmd.extract_message_format().unwrap(),
expected.map(ToString::to_string)
);
if let CargoCmd::Build(args) = cmd {
assert_eq!(args.args, vec!["--foo", "bar"]);
} else {
unreachable!();
}
}
}
#[test]
fn extract_format_err() {
for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] {
let mut cmd = CargoCmd::Build(RemainingArgs {
args: args.iter().map(ToString::to_string).collect(),
});
assert!(cmd.extract_message_format().is_err());
}
}
#[test]
fn split_run_args() {
struct TestParam {
input: &'static [&'static str],
expected_cargo: &'static [&'static str],
expected_exe: &'static [&'static str],
}
for param in [
TestParam {
input: &["--example", "hello-world", "--no-default-features"],
expected_cargo: &["--example", "hello-world", "--no-default-features"],
expected_exe: &[],
},
TestParam {
input: &["--example", "hello-world", "--", "--do-stuff", "foo"],
expected_cargo: &["--example", "hello-world", "--"],
expected_exe: &["--do-stuff", "foo"],
},
TestParam {
input: &["--lib", "--", "foo"],
expected_cargo: &["--lib", "--"],
expected_exe: &["foo"],
},
TestParam {
input: &["foo", "--", "bar"],
expected_cargo: &["foo", "--"],
expected_exe: &["bar"],
},
] {
let Run { cargo_args, .. } =
Run::parse_from(std::iter::once(&"run").chain(param.input));
assert_eq!(cargo_args.cargo_args(), param.expected_cargo);
assert_eq!(cargo_args.exe_args(), param.expected_exe);
}
}
} }

112
src/lib.rs

@ -1,84 +1,32 @@
pub mod command; pub mod command;
use crate::command::{CargoCmd, Input}; use crate::command::CargoCmd;
use cargo_metadata::{Message, MetadataCommand}; use cargo_metadata::{Message, MetadataCommand};
use core::fmt;
use rustc_version::Channel; use rustc_version::Channel;
use semver::Version; use semver::Version;
use serde::Deserialize; use serde::Deserialize;
use tee::TeeReader;
use core::fmt;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus, Stdio}; use std::process::{Command, ExitStatus, Stdio};
use std::{env, io, process}; use std::{env, io, process};
use tee::TeeReader;
const DEFAULT_MESSAGE_FORMAT: &str = "json-render-diagnostics"; /// Built a command using [`make_cargo_build_command`] and execute it,
/// parsing and returning the messages from the spawned process.
/// Gets whether the executable should link the generated files to a 3ds ///
/// based on the parsed input. /// For commands that produce an executable output, this function will build the
pub fn get_should_link(input: &mut Input) -> bool { /// `.elf` binary that can be used to create other 3ds files.
// When running compile only commands, don't link the executable to the 3ds. pub fn run_cargo(cmd: &CargoCmd, message_format: Option<String>) -> (ExitStatus, Vec<Message>) {
// Otherwise, link and run on the 3ds but do not run locally. let mut command = make_cargo_build_command(cmd, &message_format);
match input.cmd {
CargoCmd::Run(_) => true,
// CargoCmd::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => {
// // input.cargo_opts().push("--no-run".to_string());
// true
// }
_ => false,
}
}
/// Extracts the user-defined message format and if there is none,
/// default to `json-render-diagnostics`.
pub fn get_message_format(input: &mut Input) -> String {
// Checks for a position within the args where '--message-format' is located
todo!();
// if let Some(pos) = input
// .cargo_opts()
// .iter()
// .position(|s| s.starts_with("--message-format"))
// {
// // Remove the arg from list
// let arg = input.cargo_opts()[pos].clone(); // TODO
// // Allows for usage of '--message-format=<format>' and also using space separation.
// // Check for a '=' delimiter and use the second half of the split as the format,
// // otherwise remove next arg which is now at the same position as the original flag.
// let format = if let Some((_, format)) = arg.split_once('=') {
// format.to_string()
// } else {
// input.cargo_opts()[pos].clone() // TODO
// };
// // Non-json formats are not supported so the executable exits.
// if format.starts_with("json") {
// format
// } else {
// eprintln!("error: non-JSON `message-format` is not supported");
// process::exit(1);
// }
// } else {
// // Default to 'json-render-diagnostics'
// DEFAULT_MESSAGE_FORMAT.to_string()
// }
}
/// Build the elf that will be used to create other 3ds files.
/// The command built from [`make_cargo_build_command`] is executed
/// and the messages from the spawned process are parsed and returned.
pub fn build_elf(
cmd: CargoCmd,
message_format: &str,
args: &Vec<String>,
) -> (ExitStatus, Vec<Message>) {
let mut command = make_cargo_build_command(cmd, message_format, args);
let mut process = command.spawn().unwrap(); let mut process = command.spawn().unwrap();
let command_stdout = process.stdout.take().unwrap(); let command_stdout = process.stdout.take().unwrap();
let mut tee_reader; let mut tee_reader;
let mut stdout_reader; let mut stdout_reader;
let buf_reader: &mut dyn BufRead = if message_format == DEFAULT_MESSAGE_FORMAT { let buf_reader: &mut dyn BufRead = if message_format.is_none() {
stdout_reader = BufReader::new(command_stdout); stdout_reader = BufReader::new(command_stdout);
&mut stdout_reader &mut stdout_reader
} else { } else {
@ -98,11 +46,7 @@ pub fn build_elf(
/// Create the cargo build command, but don't execute it. /// Create the cargo build command, but don't execute it.
/// If there is no pre-built std detected in the sysroot, `build-std` is used. /// If there is no pre-built std detected in the sysroot, `build-std` is used.
pub fn make_cargo_build_command( pub fn make_cargo_build_command(cmd: &CargoCmd, message_format: &Option<String>) -> Command {
cmd: CargoCmd,
message_format: &str,
args: &Vec<String>,
) -> Command {
let rust_flags = env::var("RUSTFLAGS").unwrap_or_default() let rust_flags = env::var("RUSTFLAGS").unwrap_or_default()
+ &format!( + &format!(
" -L{}/libctru/lib -lctru", " -L{}/libctru/lib -lctru",
@ -112,32 +56,48 @@ pub fn make_cargo_build_command(
let sysroot = find_sysroot(); let sysroot = find_sysroot();
let mut command = Command::new(cargo); let mut command = Command::new(cargo);
let cmd = match cmd { let cmd_str = match cmd {
CargoCmd::Build(_) | CargoCmd::Run(_) => "build", CargoCmd::Build(_) | CargoCmd::Run(_) => "build",
CargoCmd::Test(_) => "test", CargoCmd::Test(_) => "test",
CargoCmd::Passthrough(_) => todo!(), CargoCmd::Passthrough(cmd) => &cmd[0],
}; };
command command
.env("RUSTFLAGS", rust_flags) .env("RUSTFLAGS", rust_flags)
.arg(cmd) .arg(cmd_str)
.arg("--target") .arg("--target")
.arg("armv6k-nintendo-3ds") .arg("armv6k-nintendo-3ds")
.arg("--message-format") .arg("--message-format")
.arg(message_format); .arg(
message_format
.as_deref()
.unwrap_or(CargoCmd::DEFAULT_MESSAGE_FORMAT),
);
if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() {
eprintln!("No pre-build std found, using build-std"); eprintln!("No pre-build std found, using build-std");
command.arg("-Z").arg("build-std"); command.arg("-Z").arg("build-std");
} }
let cargo_args = match cmd {
CargoCmd::Build(cargo_args) => cargo_args.cargo_args(),
CargoCmd::Run(run) => run.cargo_args.cargo_args(),
CargoCmd::Test(test) => {
// We can't run 3DS executables on the host, so pass --no-run here and
// send the executable with 3dslink later, if the user wants
command.arg("--no-run");
test.run_args.cargo_args.cargo_args()
}
CargoCmd::Passthrough(other) => &other[1..],
};
command command
.args(args) .args(cargo_args)
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stderr(Stdio::inherit()); .stderr(Stdio::inherit());
command dbg!(command)
} }
/// Finds the sysroot path of the current toolchain /// Finds the sysroot path of the current toolchain

67
src/main.rs

@ -1,9 +1,8 @@
use cargo_3ds::command::{Cargo, CargoCmd, Input, Run, Test}; use cargo_3ds::command::Cargo;
use cargo_3ds::{ use cargo_3ds::{build_3dsx, build_smdh, check_rust_version, get_metadata, link, run_cargo};
build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata,
get_should_link, link, use clap::Parser;
};
use clap::{CommandFactory, FromArgMatches, Parser};
use std::process; use std::process;
fn main() { fn main() {
@ -11,45 +10,37 @@ fn main() {
let Cargo::Input(mut input) = Cargo::parse(); let Cargo::Input(mut input) = Cargo::parse();
dbg!(&input); let message_format = match input.cmd.extract_message_format() {
Ok(fmt) => fmt,
let cargo_args = match &input.cmd { Err(msg) => {
CargoCmd::Build(cargo_args) eprintln!("{msg}");
| CargoCmd::Run(Run { cargo_args, .. }) process::exit(1)
| CargoCmd::Test(Test { }
run_args: Run { cargo_args, .. },
..
}) => cargo_args,
CargoCmd::Passthrough(other) => todo!(),
}; };
dbg!(cargo_args.cargo_opts()); let (status, messages) = run_cargo(&input.cmd, message_format);
dbg!(cargo_args.exe_args());
// let if !status.success() {
// let message_format = get_message_format(&mut input); process::exit(status.code().unwrap_or(1));
}
// let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts);
// if !status.success() { if !input.cmd.should_build_3dsx() {
// process::exit(status.code().unwrap_or(1)); return;
// } }
// if !input.cmd.should_build_3dsx() { eprintln!("Getting metadata");
// return; let app_conf = get_metadata(&messages);
// }
// eprintln!("Getting metadata"); eprintln!("Building smdh:{}", app_conf.path_smdh().display());
// let app_conf = get_metadata(&messages); build_smdh(&app_conf);
// eprintln!("Building smdh:{}", app_conf.path_smdh().display()); eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display());
// build_smdh(&app_conf); build_3dsx(&app_conf);
// eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); if input.cmd.should_link_to_device() {
// build_3dsx(&app_conf); // TODO plumb in exe_args and various 3dslink args
// if should_link { eprintln!("Running 3dslink");
// eprintln!("Running 3dslink"); link(&app_conf);
// link(&app_conf); }
// }
} }

Loading…
Cancel
Save