1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
use clap::Parser as ClapParser;
use rand::distributions::{Alphanumeric, DistString};
use std::path::PathBuf;
use std::process::Command;
/// Bridge between 'nasm' and 'ca65'.
#[derive(ClapParser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Assemble the instructions given on this file.
file: String,
/// Linker configuration to be used. This configuration can be an identifier
/// for the configurations already baked in into this application, or it can
/// be a file path to a configuration of your choosing. See the
/// documentation for more information on this format. Defaults to 'nrom'.
#[arg(short = 'c', long)]
config: Option<String>,
/// Used for compatibility with 'ca65'.
#[arg(long)]
target: Option<String>,
/// Place the output into the given <OUT> file.
#[arg(short = 'o', long)]
out: String,
}
// Print the given `message` and exit(1).
fn die(message: String) {
eprintln!("error: {}", message);
std::process::exit(1);
}
// Find the binary by `name` in "PATH". Implementation taken from:
// https://stackoverflow.com/a/37499032.
fn find_binary(name: &str) -> Option<PathBuf> {
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.filter_map(|dir| {
let full_path = dir.join(name);
if full_path.is_file() {
Some(full_path)
} else {
None
}
})
.next()
})
}
// Returns the path for the binaries for 'nasm' and 'cl65'.
fn get_binaries() -> Result<(PathBuf, PathBuf), String> {
let nasm = match find_binary("nasm") {
Some(nasm) => nasm,
None => return Err("could not find 'nasm'".to_string()),
};
let cl65 = match find_binary("cl65") {
Some(cl65) => cl65,
None => return Err("could not find 'cl65'".to_string()),
};
Ok((nasm, cl65))
}
fn main() {
// Make sure that the binaries are there.
let (nasm, cl65) = match get_binaries() {
Ok((nasm, cl65)) => (nasm, cl65),
Err(e) => {
die(e);
return;
}
};
let args = Args::parse();
// Sanity check on the target flag from 'ca65'.
if let Some(target) = args.target {
if target != "nes" {
die(format!(
"the target has to be 'nes', but '{}' was provided instead",
target
));
return;
}
}
// Generate a temporary directory in which both binary files will be placed
// as an intermediate step.
let random_string = &Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
let dir = std::env::temp_dir().join(random_string);
if let Err(e) = std::fs::create_dir(&dir) {
die(e.to_string());
return;
}
// Run 'nasm' with the given arguments. Note that we don't care whether
// 'nasm' itself errors out.
if let Err(e) = Command::new(nasm)
.arg(&args.file)
.arg("-o")
.arg(dir.join("nasm.nes"))
.arg("-c")
.arg(args.config.clone().unwrap_or("nrom65".to_string()))
.status()
{
die(e.to_string());
return;
}
// Run 'cl65' with the given arguments.
let mut cl65_command = Command::new(cl65);
cl65_command
.arg("--target")
.arg("nes")
.arg(&args.file)
.arg("-o")
.arg(dir.join("cl65.nes"));
if let Some(config) = &args.config {
cl65_command.arg("-c").arg(config);
}
// Here, and in contrast with the 'nasm' execution, we do care about the
// exit code of 'cl65'.
match cl65_command.status() {
Ok(cmd) => {
if !cmd.success() {
std::process::exit(1);
}
}
Err(e) => {
die(e.to_string());
return;
}
}
// Everything went fine, we should have both binaries available to be
// compared. For 'diff' actually capture the output so it does not pollute
// the shell.
match Command::new("diff")
.arg(dir.join("nasm.nes"))
.arg(dir.join("cl65.nes"))
.output()
{
Ok(diff) => {
// If 'diff' failed, show it but don't error out.
if !diff.status.success() {
println!(
"xa65 (error): 'nasm' and 'ca65' have a mismatch. Check the results at {}",
dir.display()
);
}
}
Err(e) => {
die(e.to_string());
return;
}
}
// And just copy one of the binaries to where it was originally requested.
// Note that the binary is the one from 'cl65' just in case 'diff' failed
// (we take 'cl65' as the source of truth).
if let Err(e) = std::fs::copy(dir.join("cl65.nes"), args.out) {
die(format!("could not copy the resulting binary: {}", e));
}
}
|