1use camino::Utf8PathBuf;
11
12#[cfg(test)]
13use cabin_core::{CStandard, CxxStandard};
14use cabin_core::{LanguageStandard, OptLevel, SourceLanguage};
15
16use crate::action::{ArchiveAction, BuildAction, CompileAction, CompileMode, LinkAction};
17use crate::dialect::Dialect;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct LoweredAction {
27 pub kind: LoweredActionKind,
29 pub inputs: Vec<Utf8PathBuf>,
31 pub implicit_inputs: Vec<Utf8PathBuf>,
34 pub outputs: Vec<Utf8PathBuf>,
36 pub depfile: Option<Utf8PathBuf>,
40 pub command: Vec<String>,
42 pub description: String,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum LoweredActionKind {
50 CompileC,
52 CompileCpp,
54 SyntaxCheckC,
57 SyntaxCheckCpp,
60 ArchiveStaticLibrary,
62 LinkExecutable,
64}
65
66#[must_use]
72pub fn lower(dialect: Dialect, action: &BuildAction) -> LoweredAction {
73 match action {
74 BuildAction::Compile(compile) => lower_compile(dialect, compile),
75 BuildAction::Archive(archive) => lower_archive(dialect, archive),
76 BuildAction::Link(link) => lower_link(dialect, link),
77 }
78}
79
80#[must_use]
85pub fn compile_argv(dialect: Dialect, compile: &CompileAction) -> Vec<String> {
86 match dialect {
87 Dialect::GnuLike => compile_argv_gnu(compile),
88 Dialect::Msvc => compile_argv_msvc(compile),
89 }
90}
91
92fn lower_compile(dialect: Dialect, compile: &CompileAction) -> LoweredAction {
93 let mut command = compile_argv(dialect, compile);
97 if let Some(wrapper) = &compile.compiler_wrapper {
98 command.insert(0, wrapper.as_str().to_owned());
99 }
100 let (kind, outputs) = match &compile.mode {
101 CompileMode::Object => {
102 let kind = match compile.standard.language() {
103 SourceLanguage::C => LoweredActionKind::CompileC,
104 SourceLanguage::Cxx => LoweredActionKind::CompileCpp,
105 };
106 (kind, vec![compile.object.clone()])
107 }
108 CompileMode::SyntaxOnly { stamp } => {
109 let kind = match compile.standard.language() {
110 SourceLanguage::C => LoweredActionKind::SyntaxCheckC,
111 SourceLanguage::Cxx => LoweredActionKind::SyntaxCheckCpp,
112 };
113 (kind, vec![stamp.clone()])
114 }
115 };
116 let depfile = match dialect {
119 Dialect::GnuLike => compile.depfile.clone(),
120 Dialect::Msvc => None,
121 };
122 LoweredAction {
123 kind,
124 inputs: vec![compile.source.clone()],
125 implicit_inputs: compile.implicit_inputs.clone(),
126 outputs,
127 depfile,
128 command,
129 description: compile.description.clone(),
130 }
131}
132
133fn gnu_std_flag(standard: LanguageStandard) -> String {
138 format!("-std={standard}")
139}
140
141fn compile_argv_gnu(compile: &CompileAction) -> Vec<String> {
148 let args = &compile.arguments;
149 let mut out: Vec<String> = Vec::new();
150 out.push(compile.compiler.as_str().to_owned());
151 out.push(gnu_std_flag(compile.standard));
152 out.push(args.opt_level.as_flag().to_owned());
153 if args.debug_info {
154 out.push("-g".to_owned());
155 }
156 if args.define_ndebug {
157 out.push("-DNDEBUG".to_owned());
158 }
159 if let Some(depfile) = &compile.depfile {
160 out.push("-MD".to_owned());
165 out.push("-MF".to_owned());
166 out.push(depfile.as_str().to_owned());
167 if let CompileMode::SyntaxOnly { stamp } = &compile.mode {
171 out.push("-MT".to_owned());
172 out.push(stamp.as_str().to_owned());
173 }
174 }
175 for define in &args.defines {
176 out.push(format!("-D{define}"));
177 }
178 for include in &args.include_dirs {
179 out.push("-I".to_owned());
180 out.push(include.as_str().to_owned());
181 }
182 for include in &args.system_include_dirs {
186 out.push("-isystem".to_owned());
187 out.push(include.as_str().to_owned());
188 }
189 out.extend(args.extra_flags.iter().cloned());
190 match &compile.mode {
191 CompileMode::Object => {
192 out.push("-c".to_owned());
193 out.push(compile.source.as_str().to_owned());
194 out.push("-o".to_owned());
195 out.push(compile.object.as_str().to_owned());
196 }
197 CompileMode::SyntaxOnly { .. } => {
198 out.push(compile.source.as_str().to_owned());
199 out.push("-fsyntax-only".to_owned());
200 }
201 }
202 out
203}
204
205fn lower_archive_gnu(archive: &ArchiveAction) -> Vec<String> {
206 let mut command = vec![
209 archive.archiver.as_str().to_owned(),
210 "crs".to_owned(),
211 archive.output.as_str().to_owned(),
212 ];
213 for input in &archive.inputs {
214 command.push(input.as_str().to_owned());
215 }
216 command
217}
218
219fn lower_link_gnu(link: &LinkAction) -> Vec<String> {
220 let mut command = vec![link.linker.as_str().to_owned()];
224 for input in &link.inputs {
225 command.push(input.as_str().to_owned());
226 }
227 command.extend(link.arguments.iter().cloned());
228 for lib in &link.link_libs {
229 command.push(format!("-l{lib}"));
230 }
231 command.push("-o".to_owned());
232 command.push(link.output.as_str().to_owned());
233 command
234}
235
236fn msvc_std_flag(standard: LanguageStandard) -> &'static str {
241 standard.msvc_spelling().unwrap_or_else(|| {
242 unreachable!(
243 "the planner validates MSVC-dialect standards before lowering; `{standard}` has no stable /std: flag"
244 )
245 })
246}
247
248fn msvc_source_flag(language: SourceLanguage) -> &'static str {
258 match language {
259 SourceLanguage::C => "/Tc",
260 SourceLanguage::Cxx => "/Tp",
261 }
262}
263
264fn msvc_opt_flag(opt: OptLevel) -> &'static str {
265 match opt {
266 OptLevel::O0 => "/Od",
267 OptLevel::O1 | OptLevel::S | OptLevel::Z => "/O1",
268 OptLevel::O2 | OptLevel::O3 => "/O2",
270 }
271}
272
273fn compile_argv_msvc(compile: &CompileAction) -> Vec<String> {
281 let args = &compile.arguments;
282 let mut out: Vec<String> = vec![compile.compiler.as_str().to_owned(), "/nologo".to_owned()];
283 out.push(msvc_std_flag(compile.standard).to_owned());
284 if compile.standard.language() == SourceLanguage::Cxx {
285 out.push("/EHsc".to_owned());
286 }
287 out.push(msvc_opt_flag(args.opt_level).to_owned());
288 if args.debug_info {
289 out.push("/Z7".to_owned());
290 }
291 if args.define_ndebug {
292 out.push("/DNDEBUG".to_owned());
293 }
294 if compile.depfile.is_some() {
298 out.push("/showIncludes".to_owned());
299 }
300 for define in &args.defines {
301 out.push(format!("/D{define}"));
302 }
303 for include in &args.include_dirs {
304 out.push("/I".to_owned());
305 out.push(include.as_str().to_owned());
306 }
307 if !args.system_include_dirs.is_empty() {
313 out.push("/external:W0".to_owned());
314 }
315 for include in &args.system_include_dirs {
316 out.push("/external:I".to_owned());
317 out.push(include.as_str().to_owned());
318 }
319 out.extend(args.extra_flags.iter().cloned());
320 let source = format!(
321 "{}{}",
322 msvc_source_flag(compile.standard.language()),
323 compile.source.as_str()
324 );
325 match &compile.mode {
326 CompileMode::Object => {
327 out.push("/c".to_owned());
328 out.push(source);
329 out.push(format!("/Fo{}", compile.object));
330 }
331 CompileMode::SyntaxOnly { .. } => {
332 out.push(source);
333 out.push("/Zs".to_owned());
334 }
335 }
336 out
337}
338
339fn lower_archive_msvc(archive: &ArchiveAction) -> Vec<String> {
340 let mut command = vec![
342 archive.archiver.as_str().to_owned(),
343 "/nologo".to_owned(),
344 format!("/OUT:{}", archive.output),
345 ];
346 for input in &archive.inputs {
347 command.push(input.as_str().to_owned());
348 }
349 command
350}
351
352fn lower_link_msvc(link: &LinkAction) -> Vec<String> {
353 let mut command = vec![link.linker.as_str().to_owned(), "/nologo".to_owned()];
359 for input in &link.inputs {
360 command.push(input.as_str().to_owned());
361 }
362 for lib in &link.link_libs {
363 command.push(format!("{lib}.lib"));
364 }
365 command.push(format!("/Fe{}", link.output));
366 if !link.arguments.is_empty() {
367 command.push("/link".to_owned());
368 command.extend(link.arguments.iter().cloned());
369 }
370 command
371}
372
373fn lower_archive(dialect: Dialect, archive: &ArchiveAction) -> LoweredAction {
378 let command = match dialect {
379 Dialect::GnuLike => lower_archive_gnu(archive),
380 Dialect::Msvc => lower_archive_msvc(archive),
381 };
382 LoweredAction {
383 kind: LoweredActionKind::ArchiveStaticLibrary,
384 inputs: archive.inputs.clone(),
385 implicit_inputs: Vec::new(),
386 outputs: vec![archive.output.clone()],
387 depfile: None,
388 command,
389 description: archive.description.clone(),
390 }
391}
392
393fn lower_link(dialect: Dialect, link: &LinkAction) -> LoweredAction {
394 let command = match dialect {
395 Dialect::GnuLike => lower_link_gnu(link),
396 Dialect::Msvc => lower_link_msvc(link),
397 };
398 LoweredAction {
399 kind: LoweredActionKind::LinkExecutable,
400 inputs: link.inputs.clone(),
401 implicit_inputs: link.implicit_inputs.clone(),
402 outputs: vec![link.output.clone()],
403 depfile: None,
404 command,
405 description: link.description.clone(),
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::action::CompileArguments;
413
414 fn strs(v: &[&str]) -> Vec<String> {
415 v.iter().map(|s| (*s).to_owned()).collect()
416 }
417
418 fn cxx_compile(mode: CompileMode) -> CompileAction {
422 CompileAction {
423 standard: LanguageStandard::Cxx(CxxStandard::Cxx17),
424 source: Utf8PathBuf::from("/abs/src/main.cc"),
425 object: Utf8PathBuf::from("/abs/build/main.o"),
426 mode,
427 implicit_inputs: vec![Utf8PathBuf::from("/abs/build/generated.h")],
428 depfile: Some(Utf8PathBuf::from("/abs/build/main.o.d")),
429 compiler: Utf8PathBuf::from("/usr/bin/g++"),
430 compiler_wrapper: None,
431 arguments: CompileArguments {
432 opt_level: OptLevel::O0,
433 debug_info: false,
434 define_ndebug: false,
435 include_dirs: vec![Utf8PathBuf::from("/abs/include")],
436 system_include_dirs: vec![Utf8PathBuf::from("/abs/dep/include")],
437 defines: strs(&["FOO=1"]),
438 extra_flags: strs(&["-Wall"]),
439 },
440 description: "CXX /abs/build/main.o".to_owned(),
441 }
442 }
443
444 #[test]
445 fn gnu_object_mode_lowers_to_historic_compile_argv() {
446 let lowered = lower(
447 Dialect::GnuLike,
448 &BuildAction::Compile(cxx_compile(CompileMode::Object)),
449 );
450 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
451 assert_eq!(
452 lowered.outputs,
453 vec![Utf8PathBuf::from("/abs/build/main.o")]
454 );
455 assert_eq!(lowered.inputs, vec![Utf8PathBuf::from("/abs/src/main.cc")]);
456 assert_eq!(
457 lowered.depfile,
458 Some(Utf8PathBuf::from("/abs/build/main.o.d"))
459 );
460 assert_eq!(
461 lowered.command,
462 strs(&[
463 "/usr/bin/g++",
464 "-std=c++17",
465 "-O0",
466 "-MD",
467 "-MF",
468 "/abs/build/main.o.d",
469 "-DFOO=1",
470 "-I",
471 "/abs/include",
472 "-isystem",
473 "/abs/dep/include",
474 "-Wall",
475 "-c",
476 "/abs/src/main.cc",
477 "-o",
478 "/abs/build/main.o",
479 ])
480 );
481 }
482
483 #[test]
484 fn gnu_dialect_spells_declared_standards() {
485 let mut compile = cxx_compile(CompileMode::Object);
486 compile.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
487 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
488 assert_eq!(lowered.command[1], "-std=c++20");
489
490 let mut compile = cxx_compile(CompileMode::Object);
491 compile.standard = LanguageStandard::C(CStandard::C99);
492 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
493 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
494 assert_eq!(lowered.command[1], "-std=c99");
495 }
496
497 #[test]
498 fn msvc_dialect_spells_declared_standards() {
499 let mut compile = cxx_compile(CompileMode::Object);
500 compile.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
501 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(compile));
502 assert_eq!(lowered.command[2], "/std:c++20");
503
504 let mut compile = cxx_compile(CompileMode::Object);
505 compile.standard = LanguageStandard::C(CStandard::C17);
506 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(compile));
507 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
508 assert_eq!(lowered.command[2], "/std:c17");
509 assert!(!lowered.command.iter().any(|a| a == "/EHsc"));
511 assert!(lowered.command.iter().any(|a| a.starts_with("/Tc")));
512 }
513
514 #[test]
515 fn gnu_debug_and_ndebug_flags_follow_std_and_opt() {
516 let mut c = cxx_compile(CompileMode::Object);
517 c.arguments.opt_level = OptLevel::O3;
518 c.arguments.debug_info = true;
519 c.arguments.define_ndebug = true;
520 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c));
521 assert_eq!(
522 &lowered.command[1..5],
523 &strs(&["-std=c++17", "-O3", "-g", "-DNDEBUG"])[..]
524 );
525 }
526
527 #[test]
528 fn gnu_syntax_only_mode_lowers_to_historic_check_argv() {
529 let stamp = Utf8PathBuf::from("/abs/build/main.o.check");
530 let lowered = lower(
531 Dialect::GnuLike,
532 &BuildAction::Compile(cxx_compile(CompileMode::SyntaxOnly {
533 stamp: stamp.clone(),
534 })),
535 );
536 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
537 assert_eq!(lowered.outputs, vec![stamp]);
538 assert_eq!(
539 lowered.command,
540 strs(&[
541 "/usr/bin/g++",
542 "-std=c++17",
543 "-O0",
544 "-MD",
545 "-MF",
546 "/abs/build/main.o.d",
547 "-MT",
548 "/abs/build/main.o.check",
549 "-DFOO=1",
550 "-I",
551 "/abs/include",
552 "-isystem",
553 "/abs/dep/include",
554 "-Wall",
555 "/abs/src/main.cc",
556 "-fsyntax-only",
557 ])
558 );
559 }
560
561 #[test]
562 fn c_compile_lowers_to_c_kinds_and_c_standard() {
563 let mut c = cxx_compile(CompileMode::Object);
564 c.standard = LanguageStandard::C(CStandard::C11);
565 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
566 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
567 assert_eq!(lowered.command[1], "-std=c11");
568 c.mode = CompileMode::SyntaxOnly {
569 stamp: Utf8PathBuf::from("/abs/build/main.o.check"),
570 };
571 assert_eq!(
572 lower(Dialect::GnuLike, &BuildAction::Compile(c)).kind,
573 LoweredActionKind::SyntaxCheckC
574 );
575 }
576
577 #[test]
578 fn compiler_wrapper_prefixes_only_the_run_command() {
579 let mut c = cxx_compile(CompileMode::Object);
580 c.compiler_wrapper = Some(Utf8PathBuf::from("/usr/local/bin/ccache"));
581 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
582 assert_eq!(lowered.command[0], "/usr/local/bin/ccache");
583 assert_eq!(lowered.command[1], "/usr/bin/g++");
584 let unwrapped = compile_argv(Dialect::GnuLike, &c);
587 assert_eq!(unwrapped[0], "/usr/bin/g++");
588 assert!(!unwrapped.iter().any(|a| a == "/usr/local/bin/ccache"));
589 }
590
591 #[test]
592 fn gnu_archive_lowers_to_ar_crs_command() {
593 let action = BuildAction::Archive(ArchiveAction {
594 archiver: Utf8PathBuf::from("/usr/bin/ar"),
595 output: Utf8PathBuf::from("/abs/build/libfoo.a"),
596 inputs: vec![
597 Utf8PathBuf::from("/abs/build/a.o"),
598 Utf8PathBuf::from("/abs/build/b.o"),
599 ],
600 description: "AR /abs/build/libfoo.a".to_owned(),
601 });
602 let lowered = lower(Dialect::GnuLike, &action);
603 assert_eq!(lowered.kind, LoweredActionKind::ArchiveStaticLibrary);
604 assert_eq!(
605 lowered.command,
606 strs(&[
607 "/usr/bin/ar",
608 "crs",
609 "/abs/build/libfoo.a",
610 "/abs/build/a.o",
611 "/abs/build/b.o",
612 ])
613 );
614 }
615
616 #[test]
617 fn gnu_link_lowers_to_driver_inputs_ldflags_output() {
618 let action = BuildAction::Link(LinkAction {
619 linker: Utf8PathBuf::from("/usr/bin/g++"),
620 output: Utf8PathBuf::from("/abs/build/app"),
621 inputs: vec![
622 Utf8PathBuf::from("/abs/build/main.o"),
623 Utf8PathBuf::from("/abs/build/libfoo.a"),
624 ],
625 implicit_inputs: vec![],
626 arguments: strs(&["-Wl,--as-needed"]),
627 link_libs: vec![],
628 description: "LINK /abs/build/app".to_owned(),
629 });
630 let lowered = lower(Dialect::GnuLike, &action);
631 assert_eq!(lowered.kind, LoweredActionKind::LinkExecutable);
632 assert_eq!(
633 lowered.command,
634 strs(&[
635 "/usr/bin/g++",
636 "/abs/build/main.o",
637 "/abs/build/libfoo.a",
638 "-Wl,--as-needed",
639 "-o",
640 "/abs/build/app",
641 ])
642 );
643 }
644
645 fn msvc_cxx_compile(mode: CompileMode) -> CompileAction {
652 CompileAction {
653 standard: LanguageStandard::Cxx(CxxStandard::Cxx17),
654 source: Utf8PathBuf::from("C:/src/main.cc"),
655 object: Utf8PathBuf::from("C:/build/main.obj"),
656 mode,
657 implicit_inputs: vec![],
658 depfile: Some(Utf8PathBuf::from("C:/build/main.obj.d")),
659 compiler: Utf8PathBuf::from("cl.exe"),
660 compiler_wrapper: None,
661 arguments: CompileArguments {
662 opt_level: OptLevel::O2,
663 debug_info: true,
664 define_ndebug: true,
665 include_dirs: vec![Utf8PathBuf::from("C:/include")],
666 system_include_dirs: vec![Utf8PathBuf::from("C:/dep/include")],
667 defines: strs(&["FOO=1"]),
668 extra_flags: strs(&["/W4"]),
669 },
670 description: "CXX C:/build/main.obj".to_owned(),
671 }
672 }
673
674 #[test]
675 fn msvc_object_mode_lowers_to_cl_argv_without_depfile() {
676 let lowered = lower(
677 Dialect::Msvc,
678 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
679 );
680 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
681 assert_eq!(lowered.depfile, None);
684 assert_eq!(
685 lowered.command,
686 strs(&[
687 "cl.exe",
688 "/nologo",
689 "/std:c++17",
690 "/EHsc",
691 "/O2",
692 "/Z7",
693 "/DNDEBUG",
694 "/showIncludes",
695 "/DFOO=1",
696 "/I",
697 "C:/include",
698 "/external:W0",
699 "/external:I",
700 "C:/dep/include",
701 "/W4",
702 "/c",
703 "/TpC:/src/main.cc",
704 "/FoC:/build/main.obj",
705 ])
706 );
707 }
708
709 #[test]
710 fn system_include_markers_are_omitted_when_no_system_dirs_exist() {
711 let mut gnu = cxx_compile(CompileMode::Object);
715 gnu.arguments.system_include_dirs = Vec::new();
716 let gnu_argv = compile_argv(Dialect::GnuLike, &gnu);
717 assert!(!gnu_argv.iter().any(|a| a == "-isystem"));
718
719 let mut msvc = msvc_cxx_compile(CompileMode::Object);
720 msvc.arguments.system_include_dirs = Vec::new();
721 let msvc_argv = compile_argv(Dialect::Msvc, &msvc);
722 assert!(!msvc_argv.iter().any(|a| a.starts_with("/external:")));
723 }
724
725 #[test]
726 fn msvc_syntax_only_uses_zs_and_no_output() {
727 let stamp = Utf8PathBuf::from("C:/build/main.obj.check");
728 let lowered = lower(
729 Dialect::Msvc,
730 &BuildAction::Compile(msvc_cxx_compile(CompileMode::SyntaxOnly {
731 stamp: stamp.clone(),
732 })),
733 );
734 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
735 assert_eq!(lowered.outputs, vec![stamp]);
736 let tail = &lowered.command[lowered.command.len() - 2..];
737 assert_eq!(tail, &strs(&["/TpC:/src/main.cc", "/Zs"])[..]);
738 assert!(!lowered.command.iter().any(|a| a.starts_with("/Fo")));
739 }
740
741 #[test]
742 fn msvc_c_compile_uses_c_standard_and_no_ehsc() {
743 let mut c = msvc_cxx_compile(CompileMode::Object);
744 c.standard = LanguageStandard::C(CStandard::C11);
745 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
746 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
747 assert!(lowered.command.iter().any(|a| a == "/std:c11"));
748 assert!(!lowered.command.iter().any(|a| a == "/EHsc"));
749 }
750
751 #[test]
752 fn msvc_forces_source_language_per_classification() {
753 let cxx = lower(
758 Dialect::Msvc,
759 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
760 );
761 assert!(cxx.command.iter().any(|a| a == "/TpC:/src/main.cc"));
762 assert!(!cxx.command.iter().any(|a| a == "C:/src/main.cc"));
763
764 let mut c_action = msvc_cxx_compile(CompileMode::Object);
765 c_action.standard = LanguageStandard::C(CStandard::C11);
766 let c = lower(Dialect::Msvc, &BuildAction::Compile(c_action));
767 assert!(c.command.iter().any(|a| a == "/TcC:/src/main.cc"));
768 assert!(!c.command.iter().any(|a| a == "/TpC:/src/main.cc"));
769 }
770
771 #[test]
772 fn msvc_debug_off_omits_z7_and_opt_maps_o3_to_o2() {
773 let mut c = msvc_cxx_compile(CompileMode::Object);
774 c.arguments.debug_info = false;
775 c.arguments.define_ndebug = false;
776 c.arguments.opt_level = OptLevel::O3;
777 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
778 assert!(!lowered.command.iter().any(|a| a == "/Z7"));
779 assert!(!lowered.command.iter().any(|a| a == "/DNDEBUG"));
780 assert!(lowered.command.iter().any(|a| a == "/O2"));
781 }
782
783 #[test]
784 fn msvc_archive_lowers_to_lib_out_command() {
785 let action = BuildAction::Archive(ArchiveAction {
786 archiver: Utf8PathBuf::from("lib.exe"),
787 output: Utf8PathBuf::from("C:/build/foo.lib"),
788 inputs: vec![
789 Utf8PathBuf::from("C:/build/a.obj"),
790 Utf8PathBuf::from("C:/build/b.obj"),
791 ],
792 description: "AR C:/build/foo.lib".to_owned(),
793 });
794 let lowered = lower(Dialect::Msvc, &action);
795 assert_eq!(
796 lowered.command,
797 strs(&[
798 "lib.exe",
799 "/nologo",
800 "/OUT:C:/build/foo.lib",
801 "C:/build/a.obj",
802 "C:/build/b.obj",
803 ])
804 );
805 }
806
807 #[test]
808 fn msvc_link_lowers_to_cl_fe_with_link_options() {
809 let action = BuildAction::Link(LinkAction {
810 linker: Utf8PathBuf::from("cl.exe"),
811 output: Utf8PathBuf::from("C:/build/app.exe"),
812 inputs: vec![
813 Utf8PathBuf::from("C:/build/main.obj"),
814 Utf8PathBuf::from("C:/build/foo.lib"),
815 ],
816 implicit_inputs: vec![],
817 arguments: strs(&["/SUBSYSTEM:CONSOLE"]),
818 link_libs: vec![],
819 description: "LINK C:/build/app.exe".to_owned(),
820 });
821 let lowered = lower(Dialect::Msvc, &action);
822 assert_eq!(
823 lowered.command,
824 strs(&[
825 "cl.exe",
826 "/nologo",
827 "C:/build/main.obj",
828 "C:/build/foo.lib",
829 "/FeC:/build/app.exe",
830 "/link",
831 "/SUBSYSTEM:CONSOLE",
832 ])
833 );
834 }
835
836 #[test]
837 fn msvc_link_without_ldflags_omits_link_separator() {
838 let action = BuildAction::Link(LinkAction {
839 linker: Utf8PathBuf::from("cl.exe"),
840 output: Utf8PathBuf::from("C:/build/app.exe"),
841 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
842 implicit_inputs: vec![],
843 arguments: vec![],
844 link_libs: vec![],
845 description: "LINK C:/build/app.exe".to_owned(),
846 });
847 let lowered = lower(Dialect::Msvc, &action);
848 assert_eq!(
849 lowered.command,
850 strs(&[
851 "cl.exe",
852 "/nologo",
853 "C:/build/main.obj",
854 "/FeC:/build/app.exe",
855 ])
856 );
857 }
858
859 #[test]
860 fn gnu_link_lowers_link_libs_after_archives() {
861 let action = BuildAction::Link(LinkAction {
865 linker: Utf8PathBuf::from("/usr/bin/cc"),
866 output: Utf8PathBuf::from("/abs/build/app"),
867 inputs: vec![
868 Utf8PathBuf::from("/abs/build/main.o"),
869 Utf8PathBuf::from("/abs/build/libsqlite3.a"),
870 ],
871 implicit_inputs: vec![],
872 arguments: vec![],
873 link_libs: strs(&["pthread", "m"]),
874 description: "LINK /abs/build/app".to_owned(),
875 });
876 let lowered = lower(Dialect::GnuLike, &action);
877 assert_eq!(
878 lowered.command,
879 strs(&[
880 "/usr/bin/cc",
881 "/abs/build/main.o",
882 "/abs/build/libsqlite3.a",
883 "-lpthread",
884 "-lm",
885 "-o",
886 "/abs/build/app",
887 ])
888 );
889 }
890
891 #[test]
892 fn msvc_link_lowers_link_libs_as_dot_lib_inputs() {
893 let action = BuildAction::Link(LinkAction {
897 linker: Utf8PathBuf::from("cl.exe"),
898 output: Utf8PathBuf::from("C:/build/app.exe"),
899 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
900 implicit_inputs: vec![],
901 arguments: vec![],
902 link_libs: strs(&["user32"]),
903 description: "LINK C:/build/app.exe".to_owned(),
904 });
905 let lowered = lower(Dialect::Msvc, &action);
906 assert_eq!(
907 lowered.command,
908 strs(&[
909 "cl.exe",
910 "/nologo",
911 "C:/build/main.obj",
912 "user32.lib",
913 "/FeC:/build/app.exe",
914 ])
915 );
916 }
917}