Cargo command to work with Nintendo 3DS project binaries.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

273 lines
7.3 KiB

use cargo_metadata::{MetadataCommand, Package};
3 years ago
use rustc_version::{Channel, Version};
use std::path::Path;
3 years ago
use std::{
env, fmt,
3 years ago
process::{self, Command, Stdio},
};
#[derive(serde_derive::Deserialize, Default)]
struct CTRConfig {
name: String,
author: String,
description: String,
icon: String,
target_path: String,
3 years ago
}
#[derive(Ord, PartialOrd, PartialEq, Eq, Debug)]
struct CommitDate {
year: i32,
month: i32,
day: i32,
}
impl CommitDate {
fn parse(date: &str) -> Option<Self> {
let mut iter = date.split('-');
3 years ago
let year = iter.next()?.parse().ok()?;
let month = iter.next()?.parse().ok()?;
let day = iter.next()?.parse().ok()?;
Some(Self { year, month, day })
}
}
impl fmt::Display for CommitDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
}
}
3 years ago
const MINIMUM_COMMIT_DATE: CommitDate = CommitDate {
year: 2021,
month: 10,
day: 1,
3 years ago
};
3 years ago
const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 56, 0);
fn main() {
check_rust_version();
let optimization_level = match env::args().any(|arg| arg == "--release") {
3 years ago
true => String::from("release"),
false => String::from("debug"),
};
// Skip `cargo 3ds`
let mut args = env::args().skip(2);
// Get the command and collect the remaining arguments
3 years ago
let command = args.next();
let args: Vec<String> = args.collect();
let args: Vec<&str> = args.iter().map(String::as_str).collect();
3 years ago
let must_link = match command {
None => panic!("No command specified, try with \"build\" or \"link\""),
3 years ago
Some(s) => match s.as_str() {
"build" => false,
"link" => true,
_ => panic!("Invalid command, try with \"build\" or \"link\""),
},
3 years ago
};
eprintln!("Building ELF");
build_elf(&args);
eprintln!("Getting metadata");
let app_conf = get_metadata(&args, &optimization_level);
3 years ago
eprintln!("Building smdh");
build_smdh(&app_conf);
eprintln!("Building 3dsx");
build_3dsx(&app_conf);
3 years ago
if must_link {
eprintln!("Running 3dslink");
link(&app_conf);
3 years ago
}
}
fn check_rust_version() {
let rustc_version = rustc_version::version_meta().unwrap();
if rustc_version.channel > Channel::Nightly {
println!("cargo-3ds requires a nightly rustc version.");
println!(
"Please run `rustup override set nightly` to use nightly in the \
current directory."
);
process::exit(1);
}
let old_version: bool = MINIMUM_RUSTC_VERSION > rustc_version.semver;
3 years ago
let old_commit = match rustc_version.commit_date {
None => false,
3 years ago
Some(date) => {
MINIMUM_COMMIT_DATE
> CommitDate::parse(&date).expect("could not parse `rustc --version` commit date")
}
3 years ago
};
if old_version || old_commit {
println!(
"cargo-3ds requires rustc nightly version >= {}",
MINIMUM_COMMIT_DATE,
);
3 years ago
println!("Please run `rustup update nightly` to upgrade your nightly version");
3 years ago
process::exit(1);
}
}
fn build_elf(args: &[&str]) {
let rustflags = format!("-L{}/libctru/lib -lctru ", env::var("DEVKITPRO").unwrap());
3 years ago
let mut process = Command::new("cargo")
.arg("build")
.arg("-Z")
.arg("build-std")
.arg("--target")
.arg("armv6k-nintendo-3ds")
.args(args)
.env("RUSTFLAGS", rustflags)
3 years ago
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let status = process.wait().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
3 years ago
}
}
fn get_metadata(args: &[&str], opt_level: &str) -> CTRConfig {
3 years ago
let metadata = MetadataCommand::new()
3 years ago
.exec()
.expect("Failed to get cargo metadata");
let target_dir = &metadata.target_directory;
let package: &Package;
let binary_name: String;
let target_path: String;
// Check if we compiled the crate or an example
if let Some(example_pos) = args.iter().position(|arg| *arg == "--example") {
let example_name = *args.get(example_pos + 1).expect("No example given");
// Find the example's package
package = metadata
.packages
.iter()
.find(|pkg| {
pkg.targets.iter().any(|target| {
target.name == example_name && target.kind.iter().any(|kind| kind == "example")
})
})
.expect("Could not find package for example");
binary_name = format!("{} - {} example", example_name, package.name);
target_path = format!(
"{}/armv6k-nintendo-3ds/{}/examples/{}",
target_dir, opt_level, example_name
);
} else {
// Otherwise get the current/root crate
package = metadata.root_package().expect("No root crate found");
binary_name = package.name.clone();
target_path = format!(
"{}/armv6k-nintendo-3ds/{}/{}",
target_dir, opt_level, package.name
);
}
3 years ago
let mut icon = String::from("./icon.png");
3 years ago
if !Path::new(&icon).exists() {
icon = format!(
3 years ago
"{}/libctru/default_icon.png",
env::var("DEVKITPRO").unwrap()
)
}
3 years ago
CTRConfig {
name: binary_name,
author: package.authors[0].clone(),
description: package
3 years ago
.description
.clone()
.unwrap_or_else(|| String::from("Homebrew Application")),
icon,
target_path,
3 years ago
}
}
fn build_smdh(config: &CTRConfig) {
3 years ago
let mut process = Command::new("smdhtool")
.arg("--create")
.arg(&config.name)
.arg(&config.description)
.arg(&config.author)
.arg(&config.icon)
.arg(format!("{}.smdh", config.target_path))
3 years ago
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let status = process.wait().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
3 years ago
}
}
3 years ago
fn build_3dsx(config: &CTRConfig) {
3 years ago
let mut command = Command::new("3dsxtool");
let mut process = command
.arg(format!("{}.elf", config.target_path))
.arg(format!("{}.3dsx", config.target_path))
.arg(format!("--smdh={}.smdh", config.target_path));
3 years ago
// If romfs directory exists, automatically include it
if Path::new("./romfs").is_dir() {
3 years ago
process = process.arg("--romfs=\"./romfs\"");
}
3 years ago
let mut process = process
.stdin(Stdio::inherit())
3 years ago
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let status = process.wait().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
3 years ago
}
}
fn link(config: &CTRConfig) {
3 years ago
let mut process = Command::new("3dslink")
.arg(format!("{}.3dsx", config.target_path))
3 years ago
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let status = process.wait().unwrap();
if !status.success() {
process::exit(status.code().unwrap_or(1));
3 years ago
}
}