1#![doc(html_root_url = "https://docs.rs/rucc-driver/0.1.0")]
22
23pub mod phase;
24pub mod schedule;
25
26use std::fmt::Write as _;
27use std::io::Write as _;
28
29use rucc_session::{EmitKind, Options, Session};
30use rucc_target::Triple;
31
32pub use crate::phase::{Input, InputKind, Job, LinkJob, Output, Phase, Plan};
33pub use crate::schedule::Jobs;
34
35pub const VERSION: &str = env!("CARGO_PKG_VERSION");
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum Action {
41 Help,
43 Version,
45 PrintConfig(Box<Options>),
47 PrintPlan(Box<Plan>),
49 Compile {
51 opts: Box<Options>,
53 plan: Box<Plan>,
55 jobs: Jobs,
57 verbose: bool,
59 },
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct CliError {
65 pub message: String,
68}
69
70impl std::fmt::Display for CliError {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.write_str(&self.message)
73 }
74}
75
76impl std::error::Error for CliError {}
77
78fn err(message: impl Into<String>) -> CliError {
79 CliError { message: message.into() }
80}
81
82pub const USAGE: &str = "\
87rucc, an optimizing C compiler
88
89usage: rucc [options] file...
90
91options:
92 -c compile and assemble, do not link
93 -S compile only, emit assembly
94 -E preprocess only
95 -o <file> write output to <file>
96 -x <lang> treat later inputs as <lang>, or none to stop
97 -O<level> optimize: 0, 1, 2, 3, s, z
98 -g emit debug information
99 -Werror treat warnings as errors
100 -j[n] compile n translation units at once, default all
101 -v print each phase as it runs
102 -### print the phases without running any of them
103 --target=<triple> generate code for <triple>
104 --emit=<kind> exe, obj, asm, preprocessed, tast, ir, mir-final
105 --print-config print the resolved configuration and exit
106 --version print the version and exit
107 -h, --help print this message and exit
108
109See spec/04-driver-and-cli.md for the full flag reference.
110";
111
112pub fn parse_args(args: &[String]) -> Result<Action, CliError> {
119 let host = Triple::host()
120 .ok_or_else(|| err("this host is not a supported target and no --target was given"))?;
121 let mut opts = Options::new(host);
122 let mut inputs: Vec<Input> = Vec::new();
123 let mut print_config = false;
124 let mut print_plan = false;
125 let mut verbose = false;
126 let mut jobs = Jobs::default();
127 let mut output = None;
128 let mut forced: Option<InputKind> = None;
131
132 let mut i = 0;
133 while i < args.len() {
134 let arg = args[i].as_str();
135 i += 1;
136 match arg {
137 "-h" | "--help" => return Ok(Action::Help),
138 "--version" => return Ok(Action::Version),
139 "--print-config" => print_config = true,
140 "-###" => print_plan = true,
141 "-v" => verbose = true,
142 "-c" => opts.emit = EmitKind::Object,
143 "-S" => opts.emit = EmitKind::Asm,
144 "-E" => opts.emit = EmitKind::Preprocessed,
145 "-g" => opts.debug_info = true,
146 "-Werror" => opts.warnings_are_errors = true,
147 "-o" => {
148 output = Some(args.get(i).ok_or_else(|| err("-o requires an argument"))?.clone());
149 i += 1;
150 }
151 "-x" => {
152 let lang = args.get(i).ok_or_else(|| err("-x requires an argument"))?;
153 i += 1;
154 forced = if lang == "none" {
155 None
156 } else {
157 Some(InputKind::from_x_arg(lang).map_err(|e| err(format!("{e}")))?)
158 };
159 }
160 _ if arg.starts_with("-j") => {
165 jobs = Jobs::parse(&arg[2..]).map_err(err)?;
166 }
167 _ if arg.starts_with("--target=") => {
168 let t = &arg["--target=".len()..];
169 opts.target = t.parse().map_err(|e| err(format!("{e}")))?;
170 }
171 _ if arg.starts_with("--emit=") => {
172 let k = &arg["--emit=".len()..];
173 opts.emit = k
174 .parse()
175 .map_err(|()| err(format!("unknown --emit kind `{k}`, see --help")))?;
176 }
177 _ if arg.starts_with("-O") => {
178 opts.opt_level = arg[2..]
179 .parse()
180 .map_err(|()| err(format!("unknown optimization level `{arg}`")))?;
181 }
182 _ if arg.starts_with('-') && arg.len() > 1 => {
183 return Err(err(format!("unknown option `{arg}`")));
188 }
189 _ => inputs.push(Input { path: arg.to_owned(), forced }),
190 }
191 }
192
193 if print_config {
196 return Ok(Action::PrintConfig(Box::new(opts)));
197 }
198 let plan = Plan::new(&opts, &inputs, output.as_deref()).map_err(|e| err(e.message))?;
199 if print_plan {
200 return Ok(Action::PrintPlan(Box::new(plan)));
201 }
202 Ok(Action::Compile { opts: Box::new(opts), plan: Box::new(plan), jobs, verbose })
203}
204
205#[must_use]
210pub fn print_config(opts: &Options) -> String {
211 let sess = Session::new(opts.clone());
212 let t = &sess.target;
213 let mut out = String::new();
214 let _ = writeln!(out, "version: {VERSION}");
215 let _ = writeln!(out, "target: {}", t.triple);
216 let _ = writeln!(out, "arch: {}", t.triple.arch.as_str());
217 let _ = writeln!(out, "os: {}", t.triple.os.as_str());
218 let _ = writeln!(out, "env: {}", t.triple.env.as_str());
219 let _ = writeln!(out, "object-format: {}", t.object_format.as_str());
220 let _ = writeln!(out, "pointer-width: {}", t.pointer_width);
221 let _ = writeln!(out, "long-width: {}", t.long_width);
222 let _ = writeln!(out, "long-double-width: {}", t.long_double_width);
223 let _ = writeln!(out, "endian: {}", if t.little_endian { "little" } else { "big" });
224 let _ = writeln!(out, "char-signed: {}", t.char_is_signed);
225 let _ = writeln!(out, "opt-level: {}", sess.opts.opt_level);
226 let _ = writeln!(out, "emit: {}", sess.opts.emit.as_str());
227 let _ = writeln!(out, "debug-info: {}", sess.opts.debug_info);
228 out
229}
230
231pub fn run(args: &[String]) -> i32 {
236 match parse_args(args) {
237 Ok(Action::Help) => {
238 print!("{USAGE}");
239 0
240 }
241 Ok(Action::Version) => {
242 println!("rucc {VERSION}");
243 0
244 }
245 Ok(Action::PrintConfig(opts)) => {
246 print!("{}", print_config(&opts));
247 0
248 }
249 Ok(Action::PrintPlan(plan)) => {
250 print!("{}", plan.render());
251 0
252 }
253 Ok(Action::Compile { plan, jobs, verbose, .. }) => {
254 let mut stderr = std::io::stderr().lock();
255 if verbose {
256 let _ = write!(stderr, "{}", plan.render());
257 let _ = writeln!(stderr, "workers: {}", jobs.count());
258 }
259 let _ = writeln!(
263 stderr,
264 "rucc: error: running the phases is not implemented yet; \
265 use -### to see the plan, and see spec/17-milestones.md for when it runs"
266 );
267 1
268 }
269 Err(e) => {
270 let mut stderr = std::io::stderr().lock();
271 let _ = writeln!(stderr, "rucc: error: {e}");
272 let _ = writeln!(stderr, "rucc: note: run `rucc --help` for usage");
273 1
274 }
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use rucc_session::OptLevel;
281
282 use super::*;
283
284 fn args(s: &[&str]) -> Vec<String> {
285 s.iter().map(|x| (*x).to_owned()).collect()
286 }
287
288 #[test]
289 fn help_and_version_win_over_everything_else() {
290 assert_eq!(parse_args(&args(&["-c", "--help", "x.c"])).unwrap(), Action::Help);
291 assert_eq!(parse_args(&args(&["--version"])).unwrap(), Action::Version);
292 }
293
294 fn compile(s: &[&str]) -> (Box<Options>, Box<Plan>) {
295 match parse_args(&args(s)).expect("expected a compilation") {
296 Action::Compile { opts, plan, .. } => (opts, plan),
297 other => panic!("expected a compilation, got {other:?}"),
298 }
299 }
300
301 #[test]
302 fn collects_inputs_and_flags() {
303 let (opts, plan) = compile(&["-c", "-O2", "-g", "a.c", "b.c"]);
304 let paths: Vec<&str> = plan.jobs.iter().map(|j| j.input.as_str()).collect();
305 assert_eq!(paths, vec!["a.c", "b.c"]);
306 assert_eq!(opts.opt_level, OptLevel::O2);
307 assert_eq!(opts.emit, EmitKind::Object);
308 assert!(opts.debug_info);
309 }
310
311 #[test]
312 fn a_bare_dash_o_means_o1_the_way_gcc_reads_it() {
313 let (opts, _) = compile(&["-O", "a.c"]);
314 assert_eq!(opts.opt_level, OptLevel::O1);
315 }
316
317 #[test]
318 fn dash_x_applies_to_later_inputs_only_and_none_stops_it() {
319 let (_, plan) = compile(&["a.o", "-x", "c", "b.txt", "-x", "none", "c.o"]);
320 assert_eq!(plan.jobs[0].kind, InputKind::LinkerInput);
321 assert_eq!(plan.jobs[1].kind, InputKind::C);
322 assert_eq!(plan.jobs[2].kind, InputKind::LinkerInput);
323 }
324
325 #[test]
326 fn dash_j_reaches_the_scheduler_and_defaults_to_the_machine() {
327 let (_, _, jobs) = match parse_args(&args(&["-j4", "a.c"])).unwrap() {
328 Action::Compile { opts, plan, jobs, .. } => (opts, plan, jobs),
329 other => panic!("expected a compilation, got {other:?}"),
330 };
331 assert_eq!(jobs.count(), 4);
332
333 let default = match parse_args(&args(&["a.c"])).unwrap() {
334 Action::Compile { jobs, .. } => jobs,
335 other => panic!("expected a compilation, got {other:?}"),
336 };
337 assert_eq!(default, Jobs::available());
338 assert!(parse_args(&args(&["-j0", "a.c"])).is_err());
339 }
340
341 #[test]
342 fn triple_hash_prints_the_plan_and_runs_nothing() {
343 let a = parse_args(&args(&["-###", "-c", "a.c"])).unwrap();
344 let Action::PrintPlan(plan) = a else { panic!("expected a plan dump") };
345 assert!(plan.render().contains("a.c: preprocess, compile, assemble -> a.o"));
346 }
347
348 #[test]
349 fn dash_x_names_what_it_accepts_when_it_does_not_know_a_language() {
350 let e = parse_args(&args(&["-x", "fortran", "a.c"])).unwrap_err();
351 assert!(e.message.contains("assembler-with-cpp"), "{}", e.message);
352 }
353
354 #[test]
355 fn an_unknown_flag_is_an_error_rather_than_a_shrug() {
356 let e = parse_args(&args(&["-fno-such-thing", "a.c"])).unwrap_err();
357 assert!(e.message.contains("unknown option"), "{}", e.message);
358 }
359
360 #[test]
361 fn an_unsupported_target_names_itself() {
362 let e = parse_args(&args(&["--target=sparc64-linux-gnu", "a.c"])).unwrap_err();
363 assert!(e.message.contains("sparc64"), "{}", e.message);
364 }
365
366 #[test]
367 fn no_inputs_is_an_error_but_print_config_needs_none() {
368 assert!(parse_args(&args(&[])).is_err());
369 assert!(matches!(parse_args(&args(&["--print-config"])), Ok(Action::PrintConfig(_))));
370 }
371
372 #[test]
373 fn print_config_reports_the_target_it_was_given_not_the_host() {
374 let a = parse_args(&args(&["--print-config", "--target=riscv64-linux-musl"])).unwrap();
375 let Action::PrintConfig(opts) = a else { panic!("expected a configuration dump") };
376 let text = print_config(&opts);
377 assert!(text.contains("target: riscv64-unknown-linux-musl"), "{text}");
378 assert!(text.contains("char-signed: false"), "{text}");
379 assert!(text.contains("object-format: elf"), "{text}");
380 }
381
382 #[test]
383 fn print_config_has_one_key_per_line_and_a_fixed_order() {
384 let opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
385 let text = print_config(&opts);
386 let keys: Vec<&str> =
387 text.lines().map(|l| l.split(':').next().unwrap_or_default()).collect();
388 assert_eq!(keys[0], "version");
389 assert_eq!(keys[1], "target");
390 assert_eq!(keys.len(), 14);
391 assert!(text.ends_with('\n'));
392 }
393
394 #[test]
395 fn dash_o_needs_an_argument() {
396 let e = parse_args(&args(&["a.c", "-o"])).unwrap_err();
397 assert_eq!(e.message, "-o requires an argument");
398 }
399
400 #[test]
401 fn usage_fits_on_a_screen() {
402 assert!(USAGE.lines().count() < 30, "usage text has grown past one screen");
405 }
406}