Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: use camino utf8-path types where applicable #59

Merged
merged 3 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
8 changes: 4 additions & 4 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -348,10 +348,10 @@ impl Build {
/// This callback handles building the application as a `.3dsx` file.
fn callback(&self, config: &Option<CTRConfig>) {
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);
}
}
Expand Down
183 changes: 83 additions & 100 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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<Utf8PathBuf> {
package
.metadata
.get("cargo-3ds")?
.get("romfs_dir")?
.as_str()
.map(Utf8PathBuf::from)
}

/// Builds the 3dsx using `3dsxtool`.
Expand All @@ -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);
}

Expand Down Expand Up @@ -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<String>,
description: String,
icon: String,
target_path: PathBuf,
cargo_manifest_path: PathBuf,
icon_path: Utf8PathBuf,
target_path: Utf8PathBuf,
manifest_dir: Utf8PathBuf,
romfs_dir: Option<Utf8PathBuf>,
}

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(", ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally, I believe I made the choice of including only the first author because there was the slight risk that the combined name of all authors might exceed the SMDH limitations (which is a lengthy 128 bytes in UTF-16 https://www.3dbrew.org/wiki/SMDH#Format, but will never be shown in its entirety if it gets this long).

I guess it doesn't really matter either way, since the authors field is more-or-less getting abandoned by the Rust community (it's not even shown on crates.io anymore). I'm fine with the change.

Copy link
Member Author

@ian-h-chamberlain ian-h-chamberlain Jun 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, hmm... from looking at the smdhtool implementation, it seems like anything longer will just get truncated to 0x40 characters anyway: https://github.com/devkitPro/3dstools/blob/master/src/smdhtool.cpp#L423-L429 — manually testing it seems to work as expected too.

We could add a length check of our own and panic if the joined string is too long, but it would require calculating the UTF-16 length of the input UTF-8 string, so I think just letting it get truncated seems fine. Once #58 is implemented users will also be able to override the author string to their liking without needing to modify Cargo.toml's package.authors field.

};

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)]
Expand Down
Loading