Browse Source

Parse message-format up front and use TeeReader

We can still parse the JSON output, even if

Also:
- remove some unused deps.
- make sure we use eprintln so it doesn't interfere with any JSON output
  the user might want
pull/15/head
Ian Chamberlain 3 years ago
parent
commit
8023bfb28c
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 6
      Cargo.toml
  2. 98
      src/main.rs

6
Cargo.toml

@ -4,15 +4,13 @@ version = "0.1.0"
description = "Cargo wrapper for developing Rust-based Nintendo 3DS homebrew apps" description = "Cargo wrapper for developing Rust-based Nintendo 3DS homebrew apps"
repository = "https://github.com/Meziu/cargo-3ds" repository = "https://github.com/Meziu/cargo-3ds"
license = "MIT" license = "MIT"
authors = [ "Andrea Ciliberti <meziu210@icloud.com>" ] authors = ["Andrea Ciliberti <meziu210@icloud.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
clap = "2.33.1"
goblin = "0.4.3"
scroll = "0.10.1"
cargo_metadata = "0.14.0" cargo_metadata = "0.14.0"
rustc_version = "0.4.0" rustc_version = "0.4.0"
serde = "1.0.111" serde = "1.0.111"
serde_derive = "1.0.111" serde_derive = "1.0.111"
tee = "0.1.0"
toml = "0.5.6" toml = "0.5.6"

98
src/main.rs

@ -1,11 +1,13 @@
use cargo_metadata::{Message, MetadataCommand}; use cargo_metadata::{Message, MetadataCommand};
use rustc_version::{Channel, Version}; use rustc_version::{Channel, Version};
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::ExitStatus; use std::process::ExitStatus;
use std::{ use std::{
env, fmt, io, env, fmt, io,
process::{self, Command, Stdio}, process::{self, Command, Stdio},
}; };
use tee::TeeReader;
#[derive(serde_derive::Deserialize, Default)] #[derive(serde_derive::Deserialize, Default)]
struct CTRConfig { struct CTRConfig {
@ -102,9 +104,12 @@ struct CargoCommand {
command: String, command: String,
should_link: bool, should_link: bool,
args: Vec<String>, args: Vec<String>,
message_format: String,
} }
impl CargoCommand { impl CargoCommand {
const DEFAULT_MESSAGE_FORMAT: &'static str = "json-render-diagnostics";
fn from_args() -> Option<Self> { fn from_args() -> Option<Self> {
// Skip `cargo 3ds`. `cargo-3ds` isn't supported for now // Skip `cargo 3ds`. `cargo-3ds` isn't supported for now
let mut args = env::args().skip(2); let mut args = env::args().skip(2);
@ -127,62 +132,85 @@ impl CargoCommand {
_ => (command, false), _ => (command, false),
}; };
let message_format = match Self::extract_message_format(&mut remaining_args) {
Some(format) => {
if !format.starts_with("json") {
eprintln!("error: non-JSON `message-format` is not supported");
std::process::exit(1);
}
format
}
None => Self::DEFAULT_MESSAGE_FORMAT.to_string(),
};
Some(Self { Some(Self {
command, command,
should_link, should_link,
args: remaining_args, args: remaining_args,
message_format,
}) })
} }
fn extract_message_format(args: &mut Vec<String>) -> Option<String> {
for (i, arg) in args.iter().enumerate() {
if arg.starts_with("--message-format") {
return {
let arg = args.remove(i);
if let Some((_, format)) = arg.split_once('=') {
Some(format.to_string())
} else {
Some(args.remove(i))
}
};
}
}
None
}
fn build_elf(&self) -> (ExitStatus, Vec<Message>) { fn build_elf(&self) -> (ExitStatus, Vec<Message>) {
let rustflags = env::var("RUSTFLAGS").unwrap_or_default() let rustflags = env::var("RUSTFLAGS").unwrap_or_default()
+ &format!(" -L{}/libctru/lib -lctru", env::var("DEVKITPRO").unwrap()); + &format!(" -L{}/libctru/lib -lctru", env::var("DEVKITPRO").unwrap());
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string());
let has_message_format = self let mut command = Command::new(cargo)
.args .env("RUSTFLAGS", rustflags)
.iter() .arg(&self.command)
.any(|arg| arg.starts_with("--message-format"));
let mut command = Command::new(cargo);
command.arg(&self.command);
if has_message_format {
// The user presumably cares about the message format, so we should
// print to stdout like usual.
command.stdout(Stdio::inherit())
} else {
command
.stdout(Stdio::piped())
.arg("--message-format=json-render-diagnostics")
};
let mut command = command
.arg("-Z") .arg("-Z")
.arg("build-std") .arg("build-std")
.arg("--target") .arg("--target")
.arg("armv6k-nintendo-3ds") .arg("armv6k-nintendo-3ds")
.arg("--message-format")
.arg(&self.message_format)
.args(&self.args) .args(&self.args)
.env("RUSTFLAGS", rustflags) .stdout(Stdio::piped())
.stdin(Stdio::inherit()) .stdin(Stdio::inherit())
.stderr(Stdio::inherit()) .stderr(Stdio::inherit())
.spawn() .spawn()
.unwrap(); .unwrap();
let messages = if has_message_format { let command_stdout = command.stdout.take().unwrap();
// if the user specified message format, we can't parse the messages
// for anything since we wrote them all to stdout. let mut tee_reader;
// TODO: should we exit early in this case? We can't get the let mut stdout_reader;
// metadata about the built artifacts or anything in this case.
Vec::new() let buf_reader: &mut dyn BufRead = if self.message_format == Self::DEFAULT_MESSAGE_FORMAT {
stdout_reader = BufReader::new(command_stdout);
&mut stdout_reader
} else { } else {
let stdout_reader = std::io::BufReader::new(command.stdout.take().unwrap()); // The user presumably cares about the message format, so we should
Message::parse_stream(stdout_reader) // copy stuff to stdout like they expect. We can still extract the executable
.collect::<io::Result<Vec<Message>>>() // information out of it that we need for 3dsxtool etc.
.unwrap() tee_reader = BufReader::new(TeeReader::new(command_stdout, io::stdout()));
&mut tee_reader
}; };
let messages = Message::parse_stream(buf_reader)
.collect::<io::Result<_>>()
.unwrap();
(command.wait().unwrap(), messages) (command.wait().unwrap(), messages)
} }
@ -242,8 +270,8 @@ fn check_rust_version() {
let rustc_version = rustc_version::version_meta().unwrap(); let rustc_version = rustc_version::version_meta().unwrap();
if rustc_version.channel > Channel::Nightly { if rustc_version.channel > Channel::Nightly {
println!("cargo-3ds requires a nightly rustc version."); eprintln!("cargo-3ds requires a nightly rustc version.");
println!( eprintln!(
"Please run `rustup override set nightly` to use nightly in the \ "Please run `rustup override set nightly` to use nightly in the \
current directory." current directory."
); );
@ -261,11 +289,11 @@ fn check_rust_version() {
}; };
if old_version || old_commit { if old_version || old_commit {
println!( eprintln!(
"cargo-3ds requires rustc nightly version >= {}", "cargo-3ds requires rustc nightly version >= {}",
MINIMUM_COMMIT_DATE, MINIMUM_COMMIT_DATE,
); );
println!("Please run `rustup update nightly` to upgrade your nightly version"); eprintln!("Please run `rustup update nightly` to upgrade your nightly version");
process::exit(1); process::exit(1);
} }
@ -362,7 +390,7 @@ fn build_3dsx(config: &CTRConfig) {
// If romfs directory exists, automatically include it // If romfs directory exists, automatically include it
let (romfs_path, is_default_romfs) = get_romfs_path(config); let (romfs_path, is_default_romfs) = get_romfs_path(config);
if romfs_path.is_dir() { if romfs_path.is_dir() {
println!("Adding RomFS from {}", romfs_path.display()); eprintln!("Adding RomFS from {}", romfs_path.display());
process = process.arg(format!("--romfs={}", romfs_path.to_string_lossy())); process = process.arg(format!("--romfs={}", romfs_path.to_string_lossy()));
} else if !is_default_romfs { } else if !is_default_romfs {
eprintln!( eprintln!(

Loading…
Cancel
Save