diff --git a/Cargo.lock b/Cargo.lock index b844183..df9d7bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" dependencies = [ "serde", ] @@ -69,6 +69,7 @@ dependencies = [ name = "cargo-3ds" version = "0.1.3" dependencies = [ + "camino", "cargo_metadata", "clap", "rustc_version", diff --git a/Cargo.toml b/Cargo.toml index e0aae4d..2d85859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ toml = "0.5.6" clap = { version = "4.0.15", features = ["derive", "wrap_help"] } shlex = "1.3.0" serde_json = "1.0.108" +camino = "1.1" diff --git a/src/command.rs b/src/command.rs index abeeeb5..4b0dab7 100644 --- a/src/command.rs +++ b/src/command.rs @@ -6,7 +6,7 @@ use std::sync::OnceLock; use cargo_metadata::Message; use clap::{Args, Parser, Subcommand}; -use crate::{build_3dsx, build_smdh, cargo, get_metadata, link, print_command, CTRConfig}; +use crate::{build_3dsx, cargo, get_metadata, link, print_command, CTRConfig}; #[derive(Parser, Debug)] #[command(name = "cargo", bin_name = "cargo")] @@ -348,10 +348,10 @@ impl Build { /// This callback handles building the application as a `.3dsx` file. fn callback(&self, config: &Option) { if let Some(config) = config { - eprintln!("Building smdh: {}", config.path_smdh().display()); - build_smdh(config, self.verbose); + eprintln!("Building smdh: {}", config.path_smdh()); + config.build_smdh(self.verbose); - eprintln!("Building 3dsx: {}", config.path_3dsx().display()); + eprintln!("Building 3dsx: {}", config.path_3dsx()); build_3dsx(config, self.verbose); } } diff --git a/src/lib.rs b/src/lib.rs index 5b6cb5c..778b06d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,20 +1,19 @@ pub mod command; mod graph; -use core::fmt; use std::ffi::OsStr; use std::io::{BufRead, BufReader}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; -use std::{env, io, process}; +use std::{env, fmt, io, process}; -use cargo_metadata::{Message, MetadataCommand}; -use command::{Input, Test}; +use camino::{Utf8Path, Utf8PathBuf}; +use cargo_metadata::{Message, MetadataCommand, Package}; use rustc_version::Channel; use semver::Version; use tee::TeeReader; -use crate::command::{CargoCmd, Run}; +use crate::command::{CargoCmd, Input, Run, Test}; use crate::graph::UnitGraph; /// Build a command using [`make_cargo_build_command`] and execute it, @@ -281,13 +280,12 @@ pub fn get_metadata(messages: &[Message]) -> CTRConfig { let (package, artifact) = (package.unwrap(), artifact.unwrap()); - let mut icon = String::from("./icon.png"); + let mut icon_path = Utf8PathBuf::from("./icon.png"); - if !Path::new(&icon).exists() { - icon = format!( - "{}/libctru/default_icon.png", - env::var("DEVKITPRO").unwrap() - ); + if !icon_path.exists() { + icon_path = Utf8PathBuf::from(env::var("DEVKITPRO").unwrap()) + .join("libctru") + .join("default_icon.png"); } // for now assume a single "kind" since we only support one output artifact @@ -301,52 +299,28 @@ pub fn get_metadata(messages: &[Message]) -> CTRConfig { _ => artifact.target.name, }; - let author = match package.authors.as_slice() { - [name, ..] => name.clone(), - [] => String::from("Unspecified Author"), // as standard with the devkitPRO toolchain - }; + let romfs_dir = get_romfs_dir(&package); CTRConfig { name, - author, + authors: package.authors, description: package .description - .clone() .unwrap_or_else(|| String::from("Homebrew Application")), - icon, - target_path: artifact.executable.unwrap().into(), - cargo_manifest_path: package.manifest_path.into(), + icon_path, + romfs_dir, + manifest_dir: package.manifest_path.parent().unwrap().into(), + target_path: artifact.executable.unwrap(), } } -/// Builds the smdh using `smdhtool`. -/// This will fail if `smdhtool` is not within the running directory or in a directory found in $PATH -pub fn build_smdh(config: &CTRConfig, verbose: bool) { - let mut command = Command::new("smdhtool"); - command - .arg("--create") - .arg(&config.name) - .arg(&config.description) - .arg(&config.author) - .arg(&config.icon) - .arg(config.path_smdh()) - .stdin(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); - - if verbose { - print_command(&command); - } - - let mut process = command - .spawn() - .expect("smdhtool command failed, most likely due to 'smdhtool' not being in $PATH"); - - let status = process.wait().unwrap(); - - if !status.success() { - process::exit(status.code().unwrap_or(1)); - } +fn get_romfs_dir(package: &Package) -> Option { + package + .metadata + .get("cargo-3ds")? + .get("romfs_dir")? + .as_str() + .map(Utf8PathBuf::from) } /// Builds the 3dsx using `3dsxtool`. @@ -356,18 +330,14 @@ pub fn build_3dsx(config: &CTRConfig, verbose: bool) { command .arg(&config.target_path) .arg(config.path_3dsx()) - .arg(format!("--smdh={}", config.path_smdh().to_string_lossy())); - - // If romfs directory exists, automatically include it - let (romfs_path, is_default_romfs) = get_romfs_path(config); - if romfs_path.is_dir() { - eprintln!("Adding RomFS from {}", romfs_path.display()); - command.arg(format!("--romfs={}", romfs_path.to_string_lossy())); - } else if !is_default_romfs { - eprintln!( - "Could not find configured RomFS dir: {}", - romfs_path.display() - ); + .arg(format!("--smdh={}", config.path_smdh())); + + let romfs = config.romfs_dir(); + if romfs.is_dir() { + eprintln!("Adding RomFS from {romfs}"); + command.arg(format!("--romfs={romfs}")); + } else if config.romfs_dir.is_some() { + eprintln!("Could not find configured RomFS dir: {romfs}"); process::exit(1); } @@ -411,56 +381,69 @@ pub fn link(config: &CTRConfig, run_args: &Run, verbose: bool) { } } -/// Read the `RomFS` path from the Cargo manifest. If it's unset, use the default. -/// The returned boolean is true when the default is used. -pub fn get_romfs_path(config: &CTRConfig) -> (PathBuf, bool) { - let manifest_path = &config.cargo_manifest_path; - let manifest_str = std::fs::read_to_string(manifest_path) - .unwrap_or_else(|e| panic!("Could not open {}: {e}", manifest_path.display())); - let manifest_data: toml::Value = - toml::de::from_str(&manifest_str).expect("Could not parse Cargo manifest as TOML"); - - // Find the romfs setting and compute the path - let mut is_default = false; - let romfs_dir_setting = manifest_data - .as_table() - .and_then(|table| table.get("package")) - .and_then(toml::Value::as_table) - .and_then(|table| table.get("metadata")) - .and_then(toml::Value::as_table) - .and_then(|table| table.get("cargo-3ds")) - .and_then(toml::Value::as_table) - .and_then(|table| table.get("romfs_dir")) - .and_then(toml::Value::as_str) - .unwrap_or_else(|| { - is_default = true; - "romfs" - }); - let mut romfs_path = manifest_path.clone(); - romfs_path.pop(); // Pop Cargo.toml - romfs_path.push(romfs_dir_setting); - - (romfs_path, is_default) -} - -#[derive(Default)] +#[derive(Default, Debug)] pub struct CTRConfig { name: String, - author: String, + authors: Vec, description: String, - icon: String, - target_path: PathBuf, - cargo_manifest_path: PathBuf, + icon_path: Utf8PathBuf, + target_path: Utf8PathBuf, + manifest_dir: Utf8PathBuf, + romfs_dir: Option, } impl CTRConfig { - pub fn path_3dsx(&self) -> PathBuf { + /// Get the path to the output `.3dsx` file. + pub fn path_3dsx(&self) -> Utf8PathBuf { self.target_path.with_extension("3dsx") } - pub fn path_smdh(&self) -> PathBuf { + /// Get the path to the output `.smdh` file. + pub fn path_smdh(&self) -> Utf8PathBuf { self.target_path.with_extension("smdh") } + + /// Get the absolute path to the romfs directory, defaulting to `romfs` if not specified. + pub fn romfs_dir(&self) -> Utf8PathBuf { + self.manifest_dir + .join(self.romfs_dir.as_deref().unwrap_or(Utf8Path::new("romfs"))) + } + + /// Builds the smdh using `smdhtool`. + /// This will fail if `smdhtool` is not within the running directory or in a directory found in $PATH + pub fn build_smdh(&self, verbose: bool) { + let author = if self.authors.is_empty() { + String::from("Unspecified Author") // as standard with the devkitPRO toolchain + } else { + self.authors.join(", ") + }; + + let mut command = Command::new("smdhtool"); + command + .arg("--create") + .arg(&self.name) + .arg(&self.description) + .arg(author) + .arg(&self.icon_path) + .arg(self.path_smdh()) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + if verbose { + print_command(&command); + } + + let mut process = command + .spawn() + .expect("smdhtool command failed, most likely due to 'smdhtool' not being in $PATH"); + + let status = process.wait().unwrap(); + + if !status.success() { + process::exit(status.code().unwrap_or(1)); + } + } } #[derive(Ord, PartialOrd, PartialEq, Eq, Debug)]