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.to_string());
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, gnu_extensions: bool) -> String {
138 if gnu_extensions {
139 format!("-std=gnu{}", &standard.as_str()[1..])
144 } else {
145 format!("-std={standard}")
146 }
147}
148
149fn compile_argv_gnu(compile: &CompileAction) -> Vec<String> {
156 let args = &compile.arguments;
157 let mut out: Vec<String> = Vec::new();
158 out.push(compile.compiler.to_string());
159 out.push(gnu_std_flag(compile.standard, compile.gnu_extensions));
160 out.push(args.opt_level.as_flag().to_owned());
161 if args.debug_info {
162 out.push("-g".to_owned());
163 }
164 if args.define_ndebug {
165 out.push("-DNDEBUG".to_owned());
166 }
167 if let Some(depfile) = &compile.depfile {
168 out.push("-MD".to_owned());
173 out.push("-MF".to_owned());
174 out.push(depfile.to_string());
175 if let CompileMode::SyntaxOnly { stamp } = &compile.mode {
179 out.push("-MT".to_owned());
180 out.push(stamp.to_string());
181 }
182 }
183 for define in &args.defines {
184 out.push(format!("-D{define}"));
185 }
186 for include in &args.include_dirs {
187 out.push("-I".to_owned());
188 out.push(include.to_string());
189 }
190 for include in &args.system_include_dirs {
194 out.push("-isystem".to_owned());
195 out.push(include.to_string());
196 }
197 out.extend(args.extra_flags.iter().cloned());
198 match &compile.mode {
199 CompileMode::Object => {
200 out.push("-c".to_owned());
201 out.push(compile.source.to_string());
202 out.push("-o".to_owned());
203 out.push(compile.object.to_string());
204 }
205 CompileMode::SyntaxOnly { .. } => {
206 out.push(compile.source.to_string());
207 out.push("-fsyntax-only".to_owned());
208 }
209 }
210 out
211}
212
213fn lower_archive_gnu(archive: &ArchiveAction) -> Vec<String> {
214 let mut command = vec![
217 archive.archiver.to_string(),
218 "crs".to_owned(),
219 archive.output.to_string(),
220 ];
221 for input in &archive.inputs {
222 command.push(input.to_string());
223 }
224 command
225}
226
227fn lower_link_gnu(link: &LinkAction) -> Vec<String> {
228 let mut command = vec![link.linker.to_string()];
232 for input in &link.inputs {
233 command.push(input.to_string());
234 }
235 command.extend(link.arguments.iter().cloned());
236 for lib in &link.link_libs {
237 command.push(format!("-l{lib}"));
238 }
239 command.push("-o".to_owned());
240 command.push(link.output.to_string());
241 command
242}
243
244fn msvc_std_flag(standard: LanguageStandard) -> &'static str {
249 standard.msvc_spelling().unwrap_or_else(|| {
250 unreachable!(
251 "the planner validates MSVC-dialect standards before lowering; `{standard}` has no stable /std: flag"
252 )
253 })
254}
255
256fn msvc_source_flag(language: SourceLanguage) -> &'static str {
266 match language {
267 SourceLanguage::C => "/Tc",
268 SourceLanguage::Cxx => "/Tp",
269 }
270}
271
272fn msvc_opt_flag(opt: OptLevel) -> &'static str {
273 match opt {
274 OptLevel::O0 => "/Od",
275 OptLevel::O1 | OptLevel::S | OptLevel::Z => "/O1",
276 OptLevel::O2 | OptLevel::O3 => "/O2",
278 }
279}
280
281fn compile_argv_msvc(compile: &CompileAction) -> Vec<String> {
290 debug_assert!(
291 !compile.gnu_extensions,
292 "the planner rejects `gnu-extensions` on the MSVC dialect before lowering"
293 );
294 let args = &compile.arguments;
295 let mut out: Vec<String> = vec![compile.compiler.to_string(), "/nologo".to_owned()];
296 out.push("/utf-8".to_owned());
304 out.push(msvc_std_flag(compile.standard).to_owned());
305 if compile.standard.language() == SourceLanguage::Cxx {
306 out.push("/EHsc".to_owned());
307 }
308 out.push(msvc_opt_flag(args.opt_level).to_owned());
309 if args.debug_info {
310 out.push("/Z7".to_owned());
311 }
312 if args.define_ndebug {
313 out.push("/DNDEBUG".to_owned());
314 }
315 if compile.depfile.is_some() {
319 out.push("/showIncludes".to_owned());
320 }
321 for define in &args.defines {
322 out.push(format!("/D{define}"));
323 }
324 for include in &args.include_dirs {
325 out.push("/I".to_owned());
326 out.push(include.to_string());
327 }
328 if !args.system_include_dirs.is_empty() {
334 out.push("/external:W0".to_owned());
335 }
336 for include in &args.system_include_dirs {
337 out.push("/external:I".to_owned());
338 out.push(include.to_string());
339 }
340 out.extend(args.extra_flags.iter().cloned());
341 let source = format!(
342 "{}{}",
343 msvc_source_flag(compile.standard.language()),
344 compile.source.as_str()
345 );
346 match &compile.mode {
347 CompileMode::Object => {
348 out.push("/c".to_owned());
349 out.push(source);
350 out.push(format!("/Fo{}", compile.object));
351 }
352 CompileMode::SyntaxOnly { .. } => {
353 out.push(source);
354 out.push("/Zs".to_owned());
355 }
356 }
357 out
358}
359
360fn lower_archive_msvc(archive: &ArchiveAction) -> Vec<String> {
361 let mut command = vec![
363 archive.archiver.to_string(),
364 "/nologo".to_owned(),
365 format!("/OUT:{}", archive.output),
366 ];
367 for input in &archive.inputs {
368 command.push(input.to_string());
369 }
370 command
371}
372
373fn lower_link_msvc(link: &LinkAction) -> Vec<String> {
374 let mut command = vec![link.linker.to_string(), "/nologo".to_owned()];
380 for input in &link.inputs {
381 command.push(input.to_string());
382 }
383 for lib in &link.link_libs {
384 command.push(format!("{lib}.lib"));
385 }
386 command.push(format!("/Fe{}", link.output));
387 if !link.arguments.is_empty() {
388 command.push("/link".to_owned());
389 command.extend(link.arguments.iter().cloned());
390 }
391 command
392}
393
394fn lower_archive(dialect: Dialect, archive: &ArchiveAction) -> LoweredAction {
399 let command = match dialect {
400 Dialect::GnuLike => lower_archive_gnu(archive),
401 Dialect::Msvc => lower_archive_msvc(archive),
402 };
403 LoweredAction {
404 kind: LoweredActionKind::ArchiveStaticLibrary,
405 inputs: archive.inputs.clone(),
406 implicit_inputs: Vec::new(),
407 outputs: vec![archive.output.clone()],
408 depfile: None,
409 command,
410 description: archive.description.clone(),
411 }
412}
413
414fn lower_link(dialect: Dialect, link: &LinkAction) -> LoweredAction {
415 let command = match dialect {
416 Dialect::GnuLike => lower_link_gnu(link),
417 Dialect::Msvc => lower_link_msvc(link),
418 };
419 LoweredAction {
420 kind: LoweredActionKind::LinkExecutable,
421 inputs: link.inputs.clone(),
422 implicit_inputs: link.implicit_inputs.clone(),
423 outputs: vec![link.output.clone()],
424 depfile: None,
425 command,
426 description: link.description.clone(),
427 }
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use crate::action::CompileArguments;
434
435 fn strs(v: &[&str]) -> Vec<String> {
436 v.iter().map(|&s| s.to_string()).collect()
437 }
438
439 fn cxx_compile(mode: CompileMode) -> CompileAction {
443 CompileAction {
444 standard: LanguageStandard::Cxx(CxxStandard::Cxx17),
445 gnu_extensions: false,
446 source: Utf8PathBuf::from("/abs/src/main.cc"),
447 object: Utf8PathBuf::from("/abs/build/main.o"),
448 mode,
449 implicit_inputs: vec![Utf8PathBuf::from("/abs/build/generated.h")],
450 depfile: Some(Utf8PathBuf::from("/abs/build/main.o.d")),
451 compiler: Utf8PathBuf::from("/usr/bin/g++"),
452 compiler_wrapper: None,
453 arguments: CompileArguments {
454 opt_level: OptLevel::O0,
455 debug_info: false,
456 define_ndebug: false,
457 include_dirs: vec![Utf8PathBuf::from("/abs/include")],
458 system_include_dirs: vec![Utf8PathBuf::from("/abs/dep/include")],
459 defines: strs(&["FOO=1"]),
460 extra_flags: strs(&["-Wall"]),
461 },
462 description: "CXX /abs/build/main.o".to_owned(),
463 }
464 }
465
466 #[test]
467 fn gnu_object_mode_lowers_to_historic_compile_argv() {
468 let lowered = lower(
469 Dialect::GnuLike,
470 &BuildAction::Compile(cxx_compile(CompileMode::Object)),
471 );
472 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
473 assert_eq!(
474 lowered.outputs,
475 vec![Utf8PathBuf::from("/abs/build/main.o")]
476 );
477 assert_eq!(lowered.inputs, vec![Utf8PathBuf::from("/abs/src/main.cc")]);
478 assert_eq!(
479 lowered.depfile,
480 Some(Utf8PathBuf::from("/abs/build/main.o.d"))
481 );
482 assert_eq!(
483 lowered.command,
484 strs(&[
485 "/usr/bin/g++",
486 "-std=c++17",
487 "-O0",
488 "-MD",
489 "-MF",
490 "/abs/build/main.o.d",
491 "-DFOO=1",
492 "-I",
493 "/abs/include",
494 "-isystem",
495 "/abs/dep/include",
496 "-Wall",
497 "-c",
498 "/abs/src/main.cc",
499 "-o",
500 "/abs/build/main.o",
501 ])
502 );
503 }
504
505 #[test]
506 fn gnu_dialect_spells_declared_standards() {
507 let mut compile = cxx_compile(CompileMode::Object);
508 compile.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
509 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
510 assert_eq!(lowered.command[1], "-std=c++20");
511
512 let mut compile = cxx_compile(CompileMode::Object);
513 compile.standard = LanguageStandard::C(CStandard::C99);
514 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
515 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
516 assert_eq!(lowered.command[1], "-std=c99");
517 }
518
519 #[test]
520 fn gnu_dialect_spells_gnu_extensions_from_the_iso_level() {
521 let mut compile = cxx_compile(CompileMode::Object);
524 compile.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
525 compile.gnu_extensions = true;
526 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
527 assert_eq!(lowered.command[1], "-std=gnu++20");
528
529 let mut compile = cxx_compile(CompileMode::Object);
530 compile.standard = LanguageStandard::C(CStandard::C17);
531 compile.gnu_extensions = true;
532 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
533 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
534 assert_eq!(lowered.command[1], "-std=gnu17");
535
536 for (standard, flag) in [
538 (LanguageStandard::C(CStandard::C89), "-std=gnu89"),
539 (LanguageStandard::C(CStandard::C23), "-std=gnu23"),
540 (LanguageStandard::Cxx(CxxStandard::Cxx98), "-std=gnu++98"),
541 (LanguageStandard::Cxx(CxxStandard::Cxx26), "-std=gnu++26"),
542 ] {
543 let mut compile = cxx_compile(CompileMode::Object);
544 compile.standard = standard;
545 compile.gnu_extensions = true;
546 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(compile));
547 assert_eq!(lowered.command[1], flag);
548 }
549 }
550
551 #[test]
552 fn gnu_extensions_is_strictly_per_compile() {
553 let mut with_extensions = cxx_compile(CompileMode::Object);
556 with_extensions.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
557 with_extensions.gnu_extensions = true;
558 let mut without = cxx_compile(CompileMode::Object);
559 without.standard = LanguageStandard::Cxx(CxxStandard::Cxx17);
560 let lowered_with = lower(Dialect::GnuLike, &BuildAction::Compile(with_extensions));
561 let lowered_without = lower(Dialect::GnuLike, &BuildAction::Compile(without));
562 assert_eq!(lowered_with.command[1], "-std=gnu++20");
563 assert_eq!(lowered_without.command[1], "-std=c++17");
564 }
565
566 #[test]
567 fn msvc_dialect_spells_declared_standards() {
568 let mut compile = cxx_compile(CompileMode::Object);
569 compile.standard = LanguageStandard::Cxx(CxxStandard::Cxx20);
570 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(compile));
571 assert_eq!(lowered.command[3], "/std:c++20");
573
574 let mut compile = cxx_compile(CompileMode::Object);
575 compile.standard = LanguageStandard::C(CStandard::C17);
576 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(compile));
577 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
578 assert_eq!(lowered.command[3], "/std:c17");
579 assert!(!lowered.command.iter().any(|a| a == "/EHsc"));
581 assert!(lowered.command.iter().any(|a| a.starts_with("/Tc")));
582 }
583
584 #[test]
585 fn gnu_debug_and_ndebug_flags_follow_std_and_opt() {
586 let mut c = cxx_compile(CompileMode::Object);
587 c.arguments.opt_level = OptLevel::O3;
588 c.arguments.debug_info = true;
589 c.arguments.define_ndebug = true;
590 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c));
591 assert_eq!(
592 &lowered.command[1..5],
593 &strs(&["-std=c++17", "-O3", "-g", "-DNDEBUG"])[..]
594 );
595 }
596
597 #[test]
598 fn gnu_syntax_only_mode_lowers_to_historic_check_argv() {
599 let stamp = Utf8PathBuf::from("/abs/build/main.o.check");
600 let lowered = lower(
601 Dialect::GnuLike,
602 &BuildAction::Compile(cxx_compile(CompileMode::SyntaxOnly {
603 stamp: stamp.clone(),
604 })),
605 );
606 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
607 assert_eq!(lowered.outputs, vec![stamp]);
608 assert_eq!(
609 lowered.command,
610 strs(&[
611 "/usr/bin/g++",
612 "-std=c++17",
613 "-O0",
614 "-MD",
615 "-MF",
616 "/abs/build/main.o.d",
617 "-MT",
618 "/abs/build/main.o.check",
619 "-DFOO=1",
620 "-I",
621 "/abs/include",
622 "-isystem",
623 "/abs/dep/include",
624 "-Wall",
625 "/abs/src/main.cc",
626 "-fsyntax-only",
627 ])
628 );
629 }
630
631 #[test]
632 fn c_compile_lowers_to_c_kinds_and_c_standard() {
633 let mut c = cxx_compile(CompileMode::Object);
634 c.standard = LanguageStandard::C(CStandard::C11);
635 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
636 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
637 assert_eq!(lowered.command[1], "-std=c11");
638 c.mode = CompileMode::SyntaxOnly {
639 stamp: Utf8PathBuf::from("/abs/build/main.o.check"),
640 };
641 assert_eq!(
642 lower(Dialect::GnuLike, &BuildAction::Compile(c)).kind,
643 LoweredActionKind::SyntaxCheckC
644 );
645 }
646
647 #[test]
648 fn compiler_wrapper_prefixes_only_the_run_command() {
649 let mut c = cxx_compile(CompileMode::Object);
650 c.compiler_wrapper = Some(Utf8PathBuf::from("/usr/local/bin/ccache"));
651 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
652 assert_eq!(lowered.command[0], "/usr/local/bin/ccache");
653 assert_eq!(lowered.command[1], "/usr/bin/g++");
654 let unwrapped = compile_argv(Dialect::GnuLike, &c);
657 assert_eq!(unwrapped[0], "/usr/bin/g++");
658 assert!(!unwrapped.iter().any(|a| a == "/usr/local/bin/ccache"));
659 }
660
661 #[test]
662 fn gnu_archive_lowers_to_ar_crs_command() {
663 let action = BuildAction::Archive(ArchiveAction {
664 archiver: Utf8PathBuf::from("/usr/bin/ar"),
665 output: Utf8PathBuf::from("/abs/build/libfoo.a"),
666 inputs: vec![
667 Utf8PathBuf::from("/abs/build/a.o"),
668 Utf8PathBuf::from("/abs/build/b.o"),
669 ],
670 description: "AR /abs/build/libfoo.a".to_owned(),
671 });
672 let lowered = lower(Dialect::GnuLike, &action);
673 assert_eq!(lowered.kind, LoweredActionKind::ArchiveStaticLibrary);
674 assert_eq!(
675 lowered.command,
676 strs(&[
677 "/usr/bin/ar",
678 "crs",
679 "/abs/build/libfoo.a",
680 "/abs/build/a.o",
681 "/abs/build/b.o",
682 ])
683 );
684 }
685
686 #[test]
687 fn gnu_link_lowers_to_driver_inputs_ldflags_output() {
688 let action = BuildAction::Link(LinkAction {
689 linker: Utf8PathBuf::from("/usr/bin/g++"),
690 output: Utf8PathBuf::from("/abs/build/app"),
691 inputs: vec![
692 Utf8PathBuf::from("/abs/build/main.o"),
693 Utf8PathBuf::from("/abs/build/libfoo.a"),
694 ],
695 implicit_inputs: vec![],
696 arguments: strs(&["-Wl,--as-needed"]),
697 link_libs: vec![],
698 description: "LINK /abs/build/app".to_owned(),
699 });
700 let lowered = lower(Dialect::GnuLike, &action);
701 assert_eq!(lowered.kind, LoweredActionKind::LinkExecutable);
702 assert_eq!(
703 lowered.command,
704 strs(&[
705 "/usr/bin/g++",
706 "/abs/build/main.o",
707 "/abs/build/libfoo.a",
708 "-Wl,--as-needed",
709 "-o",
710 "/abs/build/app",
711 ])
712 );
713 }
714
715 fn msvc_cxx_compile(mode: CompileMode) -> CompileAction {
722 CompileAction {
723 standard: LanguageStandard::Cxx(CxxStandard::Cxx17),
724 gnu_extensions: false,
725 source: Utf8PathBuf::from("C:/src/main.cc"),
726 object: Utf8PathBuf::from("C:/build/main.obj"),
727 mode,
728 implicit_inputs: vec![],
729 depfile: Some(Utf8PathBuf::from("C:/build/main.obj.d")),
730 compiler: Utf8PathBuf::from("cl.exe"),
731 compiler_wrapper: None,
732 arguments: CompileArguments {
733 opt_level: OptLevel::O2,
734 debug_info: true,
735 define_ndebug: true,
736 include_dirs: vec![Utf8PathBuf::from("C:/include")],
737 system_include_dirs: vec![Utf8PathBuf::from("C:/dep/include")],
738 defines: strs(&["FOO=1"]),
739 extra_flags: strs(&["/W4"]),
740 },
741 description: "CXX C:/build/main.obj".to_owned(),
742 }
743 }
744
745 #[test]
746 fn msvc_object_mode_lowers_to_cl_argv_without_depfile() {
747 let lowered = lower(
748 Dialect::Msvc,
749 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
750 );
751 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
752 assert_eq!(lowered.depfile, None);
755 assert_eq!(
756 lowered.command,
757 strs(&[
758 "cl.exe",
759 "/nologo",
760 "/utf-8",
761 "/std:c++17",
762 "/EHsc",
763 "/O2",
764 "/Z7",
765 "/DNDEBUG",
766 "/showIncludes",
767 "/DFOO=1",
768 "/I",
769 "C:/include",
770 "/external:W0",
771 "/external:I",
772 "C:/dep/include",
773 "/W4",
774 "/c",
775 "/TpC:/src/main.cc",
776 "/FoC:/build/main.obj",
777 ])
778 );
779 }
780
781 #[test]
782 fn system_include_markers_are_omitted_when_no_system_dirs_exist() {
783 let mut gnu = cxx_compile(CompileMode::Object);
787 gnu.arguments.system_include_dirs = Vec::new();
788 let gnu_argv = compile_argv(Dialect::GnuLike, &gnu);
789 assert!(!gnu_argv.iter().any(|a| a == "-isystem"));
790
791 let mut msvc = msvc_cxx_compile(CompileMode::Object);
792 msvc.arguments.system_include_dirs = Vec::new();
793 let msvc_argv = compile_argv(Dialect::Msvc, &msvc);
794 assert!(!msvc_argv.iter().any(|a| a.starts_with("/external:")));
795 }
796
797 #[test]
798 fn msvc_syntax_only_uses_zs_and_no_output() {
799 let stamp = Utf8PathBuf::from("C:/build/main.obj.check");
800 let lowered = lower(
801 Dialect::Msvc,
802 &BuildAction::Compile(msvc_cxx_compile(CompileMode::SyntaxOnly {
803 stamp: stamp.clone(),
804 })),
805 );
806 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
807 assert_eq!(lowered.outputs, vec![stamp]);
808 let tail = &lowered.command[lowered.command.len() - 2..];
809 assert_eq!(tail, &strs(&["/TpC:/src/main.cc", "/Zs"])[..]);
810 assert!(!lowered.command.iter().any(|a| a.starts_with("/Fo")));
811 }
812
813 #[test]
814 fn msvc_c_compile_uses_c_standard_and_no_ehsc() {
815 let mut c = msvc_cxx_compile(CompileMode::Object);
816 c.standard = LanguageStandard::C(CStandard::C11);
817 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
818 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
819 assert!(lowered.command.iter().any(|a| a == "/std:c11"));
820 assert!(!lowered.command.iter().any(|a| a == "/EHsc"));
821 assert!(lowered.command.iter().any(|a| a == "/utf-8"));
823 }
824
825 #[test]
826 fn msvc_forces_source_language_per_classification() {
827 let cxx = lower(
832 Dialect::Msvc,
833 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
834 );
835 assert!(cxx.command.iter().any(|a| a == "/TpC:/src/main.cc"));
836 assert!(!cxx.command.iter().any(|a| a == "C:/src/main.cc"));
837
838 let mut c_action = msvc_cxx_compile(CompileMode::Object);
839 c_action.standard = LanguageStandard::C(CStandard::C11);
840 let c = lower(Dialect::Msvc, &BuildAction::Compile(c_action));
841 assert!(c.command.iter().any(|a| a == "/TcC:/src/main.cc"));
842 assert!(!c.command.iter().any(|a| a == "/TpC:/src/main.cc"));
843 }
844
845 #[test]
846 fn msvc_debug_off_omits_z7_and_opt_maps_o3_to_o2() {
847 let mut c = msvc_cxx_compile(CompileMode::Object);
848 c.arguments.debug_info = false;
849 c.arguments.define_ndebug = false;
850 c.arguments.opt_level = OptLevel::O3;
851 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
852 assert!(!lowered.command.iter().any(|a| a == "/Z7"));
853 assert!(!lowered.command.iter().any(|a| a == "/DNDEBUG"));
854 assert!(lowered.command.iter().any(|a| a == "/O2"));
855 }
856
857 #[test]
858 fn msvc_archive_lowers_to_lib_out_command() {
859 let action = BuildAction::Archive(ArchiveAction {
860 archiver: Utf8PathBuf::from("lib.exe"),
861 output: Utf8PathBuf::from("C:/build/foo.lib"),
862 inputs: vec![
863 Utf8PathBuf::from("C:/build/a.obj"),
864 Utf8PathBuf::from("C:/build/b.obj"),
865 ],
866 description: "AR C:/build/foo.lib".to_owned(),
867 });
868 let lowered = lower(Dialect::Msvc, &action);
869 assert_eq!(
870 lowered.command,
871 strs(&[
872 "lib.exe",
873 "/nologo",
874 "/OUT:C:/build/foo.lib",
875 "C:/build/a.obj",
876 "C:/build/b.obj",
877 ])
878 );
879 }
880
881 #[test]
882 fn msvc_link_lowers_to_cl_fe_with_link_options() {
883 let action = BuildAction::Link(LinkAction {
884 linker: Utf8PathBuf::from("cl.exe"),
885 output: Utf8PathBuf::from("C:/build/app.exe"),
886 inputs: vec![
887 Utf8PathBuf::from("C:/build/main.obj"),
888 Utf8PathBuf::from("C:/build/foo.lib"),
889 ],
890 implicit_inputs: vec![],
891 arguments: strs(&["/SUBSYSTEM:CONSOLE"]),
892 link_libs: vec![],
893 description: "LINK C:/build/app.exe".to_owned(),
894 });
895 let lowered = lower(Dialect::Msvc, &action);
896 assert_eq!(
897 lowered.command,
898 strs(&[
899 "cl.exe",
900 "/nologo",
901 "C:/build/main.obj",
902 "C:/build/foo.lib",
903 "/FeC:/build/app.exe",
904 "/link",
905 "/SUBSYSTEM:CONSOLE",
906 ])
907 );
908 }
909
910 #[test]
911 fn msvc_link_without_ldflags_omits_link_separator() {
912 let action = BuildAction::Link(LinkAction {
913 linker: Utf8PathBuf::from("cl.exe"),
914 output: Utf8PathBuf::from("C:/build/app.exe"),
915 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
916 implicit_inputs: vec![],
917 arguments: vec![],
918 link_libs: vec![],
919 description: "LINK C:/build/app.exe".to_owned(),
920 });
921 let lowered = lower(Dialect::Msvc, &action);
922 assert_eq!(
923 lowered.command,
924 strs(&[
925 "cl.exe",
926 "/nologo",
927 "C:/build/main.obj",
928 "/FeC:/build/app.exe",
929 ])
930 );
931 }
932
933 #[test]
934 fn gnu_link_lowers_link_libs_after_archives() {
935 let action = BuildAction::Link(LinkAction {
939 linker: Utf8PathBuf::from("/usr/bin/cc"),
940 output: Utf8PathBuf::from("/abs/build/app"),
941 inputs: vec![
942 Utf8PathBuf::from("/abs/build/main.o"),
943 Utf8PathBuf::from("/abs/build/libsqlite3.a"),
944 ],
945 implicit_inputs: vec![],
946 arguments: vec![],
947 link_libs: strs(&["pthread", "m"]),
948 description: "LINK /abs/build/app".to_owned(),
949 });
950 let lowered = lower(Dialect::GnuLike, &action);
951 assert_eq!(
952 lowered.command,
953 strs(&[
954 "/usr/bin/cc",
955 "/abs/build/main.o",
956 "/abs/build/libsqlite3.a",
957 "-lpthread",
958 "-lm",
959 "-o",
960 "/abs/build/app",
961 ])
962 );
963 }
964
965 #[test]
966 fn msvc_link_lowers_link_libs_as_dot_lib_inputs() {
967 let action = BuildAction::Link(LinkAction {
971 linker: Utf8PathBuf::from("cl.exe"),
972 output: Utf8PathBuf::from("C:/build/app.exe"),
973 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
974 implicit_inputs: vec![],
975 arguments: vec![],
976 link_libs: strs(&["user32"]),
977 description: "LINK C:/build/app.exe".to_owned(),
978 });
979 let lowered = lower(Dialect::Msvc, &action);
980 assert_eq!(
981 lowered.command,
982 strs(&[
983 "cl.exe",
984 "/nologo",
985 "C:/build/main.obj",
986 "user32.lib",
987 "/FeC:/build/app.exe",
988 ])
989 );
990 }
991}