1use camino::Utf8PathBuf;
11
12use cabin_core::{OptLevel, SourceLanguage};
13
14use crate::action::{ArchiveAction, BuildAction, CompileAction, CompileMode, LinkAction};
15use crate::dialect::Dialect;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct LoweredAction {
25 pub kind: LoweredActionKind,
27 pub inputs: Vec<Utf8PathBuf>,
29 pub implicit_inputs: Vec<Utf8PathBuf>,
32 pub outputs: Vec<Utf8PathBuf>,
34 pub depfile: Option<Utf8PathBuf>,
38 pub command: Vec<String>,
40 pub description: String,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum LoweredActionKind {
48 CompileC,
50 CompileCpp,
52 SyntaxCheckC,
55 SyntaxCheckCpp,
58 ArchiveStaticLibrary,
60 LinkExecutable,
62}
63
64#[must_use]
70pub fn lower(dialect: Dialect, action: &BuildAction) -> LoweredAction {
71 match action {
72 BuildAction::Compile(compile) => lower_compile(dialect, compile),
73 BuildAction::Archive(archive) => lower_archive(dialect, archive),
74 BuildAction::Link(link) => lower_link(dialect, link),
75 }
76}
77
78#[must_use]
83pub fn compile_argv(dialect: Dialect, compile: &CompileAction) -> Vec<String> {
84 match dialect {
85 Dialect::GnuLike => compile_argv_gnu(compile),
86 Dialect::Msvc => compile_argv_msvc(compile),
87 }
88}
89
90fn lower_compile(dialect: Dialect, compile: &CompileAction) -> LoweredAction {
91 let mut command = compile_argv(dialect, compile);
95 if let Some(wrapper) = &compile.compiler_wrapper {
96 command.insert(0, wrapper.as_str().to_owned());
97 }
98 let (kind, outputs) = match &compile.mode {
99 CompileMode::Object => {
100 let kind = match compile.language {
101 SourceLanguage::C => LoweredActionKind::CompileC,
102 SourceLanguage::Cxx => LoweredActionKind::CompileCpp,
103 };
104 (kind, vec![compile.object.clone()])
105 }
106 CompileMode::SyntaxOnly { stamp } => {
107 let kind = match compile.language {
108 SourceLanguage::C => LoweredActionKind::SyntaxCheckC,
109 SourceLanguage::Cxx => LoweredActionKind::SyntaxCheckCpp,
110 };
111 (kind, vec![stamp.clone()])
112 }
113 };
114 let depfile = match dialect {
117 Dialect::GnuLike => compile.depfile.clone(),
118 Dialect::Msvc => None,
119 };
120 LoweredAction {
121 kind,
122 inputs: vec![compile.source.clone()],
123 implicit_inputs: compile.implicit_inputs.clone(),
124 outputs,
125 depfile,
126 command,
127 description: compile.description.clone(),
128 }
129}
130
131fn gnu_std_flag(language: SourceLanguage) -> &'static str {
136 match language {
137 SourceLanguage::C => "-std=c11",
138 SourceLanguage::Cxx => "-std=c++17",
139 }
140}
141
142fn 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.language).to_owned());
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("-MMD".to_owned());
161 out.push("-MF".to_owned());
162 out.push(depfile.as_str().to_owned());
163 if let CompileMode::SyntaxOnly { stamp } = &compile.mode {
167 out.push("-MT".to_owned());
168 out.push(stamp.as_str().to_owned());
169 }
170 }
171 for define in &args.defines {
172 out.push(format!("-D{define}"));
173 }
174 for include in &args.include_dirs {
175 out.push("-I".to_owned());
176 out.push(include.as_str().to_owned());
177 }
178 out.extend(args.extra_flags.iter().cloned());
179 match &compile.mode {
180 CompileMode::Object => {
181 out.push("-c".to_owned());
182 out.push(compile.source.as_str().to_owned());
183 out.push("-o".to_owned());
184 out.push(compile.object.as_str().to_owned());
185 }
186 CompileMode::SyntaxOnly { .. } => {
187 out.push(compile.source.as_str().to_owned());
188 out.push("-fsyntax-only".to_owned());
189 }
190 }
191 out
192}
193
194fn lower_archive_gnu(archive: &ArchiveAction) -> Vec<String> {
195 let mut command = vec![
198 archive.archiver.as_str().to_owned(),
199 "crs".to_owned(),
200 archive.output.as_str().to_owned(),
201 ];
202 for input in &archive.inputs {
203 command.push(input.as_str().to_owned());
204 }
205 command
206}
207
208fn lower_link_gnu(link: &LinkAction) -> Vec<String> {
209 let mut command = vec![link.linker.as_str().to_owned()];
213 for input in &link.inputs {
214 command.push(input.as_str().to_owned());
215 }
216 command.extend(link.arguments.iter().cloned());
217 for lib in &link.link_libs {
218 command.push(format!("-l{lib}"));
219 }
220 command.push("-o".to_owned());
221 command.push(link.output.as_str().to_owned());
222 command
223}
224
225fn msvc_std_flag(language: SourceLanguage) -> &'static str {
230 match language {
231 SourceLanguage::C => "/std:c11",
232 SourceLanguage::Cxx => "/std:c++17",
233 }
234}
235
236fn msvc_source_flag(language: SourceLanguage) -> &'static str {
246 match language {
247 SourceLanguage::C => "/Tc",
248 SourceLanguage::Cxx => "/Tp",
249 }
250}
251
252fn msvc_opt_flag(opt: OptLevel) -> &'static str {
253 match opt {
254 OptLevel::O0 => "/Od",
255 OptLevel::O1 | OptLevel::S | OptLevel::Z => "/O1",
256 OptLevel::O2 | OptLevel::O3 => "/O2",
258 }
259}
260
261fn compile_argv_msvc(compile: &CompileAction) -> Vec<String> {
269 let args = &compile.arguments;
270 let mut out: Vec<String> = vec![compile.compiler.as_str().to_owned(), "/nologo".to_owned()];
271 out.push(msvc_std_flag(compile.language).to_owned());
272 if compile.language == SourceLanguage::Cxx {
273 out.push("/EHsc".to_owned());
274 }
275 out.push(msvc_opt_flag(args.opt_level).to_owned());
276 if args.debug_info {
277 out.push("/Z7".to_owned());
278 }
279 if args.define_ndebug {
280 out.push("/DNDEBUG".to_owned());
281 }
282 if compile.depfile.is_some() {
286 out.push("/showIncludes".to_owned());
287 }
288 for define in &args.defines {
289 out.push(format!("/D{define}"));
290 }
291 for include in &args.include_dirs {
292 out.push("/I".to_owned());
293 out.push(include.as_str().to_owned());
294 }
295 out.extend(args.extra_flags.iter().cloned());
296 let source = format!(
297 "{}{}",
298 msvc_source_flag(compile.language),
299 compile.source.as_str()
300 );
301 match &compile.mode {
302 CompileMode::Object => {
303 out.push("/c".to_owned());
304 out.push(source);
305 out.push(format!("/Fo{}", compile.object));
306 }
307 CompileMode::SyntaxOnly { .. } => {
308 out.push(source);
309 out.push("/Zs".to_owned());
310 }
311 }
312 out
313}
314
315fn lower_archive_msvc(archive: &ArchiveAction) -> Vec<String> {
316 let mut command = vec![
318 archive.archiver.as_str().to_owned(),
319 "/nologo".to_owned(),
320 format!("/OUT:{}", archive.output),
321 ];
322 for input in &archive.inputs {
323 command.push(input.as_str().to_owned());
324 }
325 command
326}
327
328fn lower_link_msvc(link: &LinkAction) -> Vec<String> {
329 let mut command = vec![link.linker.as_str().to_owned(), "/nologo".to_owned()];
335 for input in &link.inputs {
336 command.push(input.as_str().to_owned());
337 }
338 for lib in &link.link_libs {
339 command.push(format!("{lib}.lib"));
340 }
341 command.push(format!("/Fe{}", link.output));
342 if !link.arguments.is_empty() {
343 command.push("/link".to_owned());
344 command.extend(link.arguments.iter().cloned());
345 }
346 command
347}
348
349fn lower_archive(dialect: Dialect, archive: &ArchiveAction) -> LoweredAction {
354 let command = match dialect {
355 Dialect::GnuLike => lower_archive_gnu(archive),
356 Dialect::Msvc => lower_archive_msvc(archive),
357 };
358 LoweredAction {
359 kind: LoweredActionKind::ArchiveStaticLibrary,
360 inputs: archive.inputs.clone(),
361 implicit_inputs: Vec::new(),
362 outputs: vec![archive.output.clone()],
363 depfile: None,
364 command,
365 description: archive.description.clone(),
366 }
367}
368
369fn lower_link(dialect: Dialect, link: &LinkAction) -> LoweredAction {
370 let command = match dialect {
371 Dialect::GnuLike => lower_link_gnu(link),
372 Dialect::Msvc => lower_link_msvc(link),
373 };
374 LoweredAction {
375 kind: LoweredActionKind::LinkExecutable,
376 inputs: link.inputs.clone(),
377 implicit_inputs: link.implicit_inputs.clone(),
378 outputs: vec![link.output.clone()],
379 depfile: None,
380 command,
381 description: link.description.clone(),
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::action::CompileArguments;
389
390 fn strs(v: &[&str]) -> Vec<String> {
391 v.iter().map(|s| (*s).to_owned()).collect()
392 }
393
394 fn cxx_compile(mode: CompileMode) -> CompileAction {
398 CompileAction {
399 language: SourceLanguage::Cxx,
400 source: Utf8PathBuf::from("/abs/src/main.cc"),
401 object: Utf8PathBuf::from("/abs/build/main.o"),
402 mode,
403 implicit_inputs: vec![Utf8PathBuf::from("/abs/build/generated.h")],
404 depfile: Some(Utf8PathBuf::from("/abs/build/main.o.d")),
405 compiler: Utf8PathBuf::from("/usr/bin/g++"),
406 compiler_wrapper: None,
407 arguments: CompileArguments {
408 opt_level: OptLevel::O0,
409 debug_info: false,
410 define_ndebug: false,
411 include_dirs: vec![Utf8PathBuf::from("/abs/include")],
412 defines: strs(&["FOO=1"]),
413 extra_flags: strs(&["-Wall"]),
414 },
415 description: "CXX /abs/build/main.o".to_owned(),
416 }
417 }
418
419 #[test]
420 fn gnu_object_mode_lowers_to_historic_compile_argv() {
421 let lowered = lower(
422 Dialect::GnuLike,
423 &BuildAction::Compile(cxx_compile(CompileMode::Object)),
424 );
425 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
426 assert_eq!(
427 lowered.outputs,
428 vec![Utf8PathBuf::from("/abs/build/main.o")]
429 );
430 assert_eq!(lowered.inputs, vec![Utf8PathBuf::from("/abs/src/main.cc")]);
431 assert_eq!(
432 lowered.depfile,
433 Some(Utf8PathBuf::from("/abs/build/main.o.d"))
434 );
435 assert_eq!(
436 lowered.command,
437 strs(&[
438 "/usr/bin/g++",
439 "-std=c++17",
440 "-O0",
441 "-MMD",
442 "-MF",
443 "/abs/build/main.o.d",
444 "-DFOO=1",
445 "-I",
446 "/abs/include",
447 "-Wall",
448 "-c",
449 "/abs/src/main.cc",
450 "-o",
451 "/abs/build/main.o",
452 ])
453 );
454 }
455
456 #[test]
457 fn gnu_debug_and_ndebug_flags_follow_std_and_opt() {
458 let mut c = cxx_compile(CompileMode::Object);
459 c.arguments.opt_level = OptLevel::O3;
460 c.arguments.debug_info = true;
461 c.arguments.define_ndebug = true;
462 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c));
463 assert_eq!(
464 &lowered.command[1..5],
465 &strs(&["-std=c++17", "-O3", "-g", "-DNDEBUG"])[..]
466 );
467 }
468
469 #[test]
470 fn gnu_syntax_only_mode_lowers_to_historic_check_argv() {
471 let stamp = Utf8PathBuf::from("/abs/build/main.o.check");
472 let lowered = lower(
473 Dialect::GnuLike,
474 &BuildAction::Compile(cxx_compile(CompileMode::SyntaxOnly {
475 stamp: stamp.clone(),
476 })),
477 );
478 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
479 assert_eq!(lowered.outputs, vec![stamp]);
480 assert_eq!(
481 lowered.command,
482 strs(&[
483 "/usr/bin/g++",
484 "-std=c++17",
485 "-O0",
486 "-MMD",
487 "-MF",
488 "/abs/build/main.o.d",
489 "-MT",
490 "/abs/build/main.o.check",
491 "-DFOO=1",
492 "-I",
493 "/abs/include",
494 "-Wall",
495 "/abs/src/main.cc",
496 "-fsyntax-only",
497 ])
498 );
499 }
500
501 #[test]
502 fn c_compile_lowers_to_c_kinds_and_c_standard() {
503 let mut c = cxx_compile(CompileMode::Object);
504 c.language = SourceLanguage::C;
505 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
506 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
507 assert_eq!(lowered.command[1], "-std=c11");
508 c.mode = CompileMode::SyntaxOnly {
509 stamp: Utf8PathBuf::from("/abs/build/main.o.check"),
510 };
511 assert_eq!(
512 lower(Dialect::GnuLike, &BuildAction::Compile(c)).kind,
513 LoweredActionKind::SyntaxCheckC
514 );
515 }
516
517 #[test]
518 fn compiler_wrapper_prefixes_only_the_run_command() {
519 let mut c = cxx_compile(CompileMode::Object);
520 c.compiler_wrapper = Some(Utf8PathBuf::from("/usr/local/bin/ccache"));
521 let lowered = lower(Dialect::GnuLike, &BuildAction::Compile(c.clone()));
522 assert_eq!(lowered.command[0], "/usr/local/bin/ccache");
523 assert_eq!(lowered.command[1], "/usr/bin/g++");
524 let unwrapped = compile_argv(Dialect::GnuLike, &c);
527 assert_eq!(unwrapped[0], "/usr/bin/g++");
528 assert!(!unwrapped.iter().any(|a| a == "/usr/local/bin/ccache"));
529 }
530
531 #[test]
532 fn gnu_archive_lowers_to_ar_crs_command() {
533 let action = BuildAction::Archive(ArchiveAction {
534 archiver: Utf8PathBuf::from("/usr/bin/ar"),
535 output: Utf8PathBuf::from("/abs/build/libfoo.a"),
536 inputs: vec![
537 Utf8PathBuf::from("/abs/build/a.o"),
538 Utf8PathBuf::from("/abs/build/b.o"),
539 ],
540 description: "AR /abs/build/libfoo.a".to_owned(),
541 });
542 let lowered = lower(Dialect::GnuLike, &action);
543 assert_eq!(lowered.kind, LoweredActionKind::ArchiveStaticLibrary);
544 assert_eq!(
545 lowered.command,
546 strs(&[
547 "/usr/bin/ar",
548 "crs",
549 "/abs/build/libfoo.a",
550 "/abs/build/a.o",
551 "/abs/build/b.o",
552 ])
553 );
554 }
555
556 #[test]
557 fn gnu_link_lowers_to_driver_inputs_ldflags_output() {
558 let action = BuildAction::Link(LinkAction {
559 linker: Utf8PathBuf::from("/usr/bin/g++"),
560 output: Utf8PathBuf::from("/abs/build/app"),
561 inputs: vec![
562 Utf8PathBuf::from("/abs/build/main.o"),
563 Utf8PathBuf::from("/abs/build/libfoo.a"),
564 ],
565 implicit_inputs: vec![],
566 arguments: strs(&["-Wl,--as-needed"]),
567 link_libs: vec![],
568 description: "LINK /abs/build/app".to_owned(),
569 });
570 let lowered = lower(Dialect::GnuLike, &action);
571 assert_eq!(lowered.kind, LoweredActionKind::LinkExecutable);
572 assert_eq!(
573 lowered.command,
574 strs(&[
575 "/usr/bin/g++",
576 "/abs/build/main.o",
577 "/abs/build/libfoo.a",
578 "-Wl,--as-needed",
579 "-o",
580 "/abs/build/app",
581 ])
582 );
583 }
584
585 fn msvc_cxx_compile(mode: CompileMode) -> CompileAction {
592 CompileAction {
593 language: SourceLanguage::Cxx,
594 source: Utf8PathBuf::from("C:/src/main.cc"),
595 object: Utf8PathBuf::from("C:/build/main.obj"),
596 mode,
597 implicit_inputs: vec![],
598 depfile: Some(Utf8PathBuf::from("C:/build/main.obj.d")),
599 compiler: Utf8PathBuf::from("cl.exe"),
600 compiler_wrapper: None,
601 arguments: CompileArguments {
602 opt_level: OptLevel::O2,
603 debug_info: true,
604 define_ndebug: true,
605 include_dirs: vec![Utf8PathBuf::from("C:/include")],
606 defines: strs(&["FOO=1"]),
607 extra_flags: strs(&["/W4"]),
608 },
609 description: "CXX C:/build/main.obj".to_owned(),
610 }
611 }
612
613 #[test]
614 fn msvc_object_mode_lowers_to_cl_argv_without_depfile() {
615 let lowered = lower(
616 Dialect::Msvc,
617 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
618 );
619 assert_eq!(lowered.kind, LoweredActionKind::CompileCpp);
620 assert_eq!(lowered.depfile, None);
623 assert_eq!(
624 lowered.command,
625 strs(&[
626 "cl.exe",
627 "/nologo",
628 "/std:c++17",
629 "/EHsc",
630 "/O2",
631 "/Z7",
632 "/DNDEBUG",
633 "/showIncludes",
634 "/DFOO=1",
635 "/I",
636 "C:/include",
637 "/W4",
638 "/c",
639 "/TpC:/src/main.cc",
640 "/FoC:/build/main.obj",
641 ])
642 );
643 }
644
645 #[test]
646 fn msvc_syntax_only_uses_zs_and_no_output() {
647 let stamp = Utf8PathBuf::from("C:/build/main.obj.check");
648 let lowered = lower(
649 Dialect::Msvc,
650 &BuildAction::Compile(msvc_cxx_compile(CompileMode::SyntaxOnly {
651 stamp: stamp.clone(),
652 })),
653 );
654 assert_eq!(lowered.kind, LoweredActionKind::SyntaxCheckCpp);
655 assert_eq!(lowered.outputs, vec![stamp]);
656 let tail = &lowered.command[lowered.command.len() - 2..];
657 assert_eq!(tail, &strs(&["/TpC:/src/main.cc", "/Zs"])[..]);
658 assert!(!lowered.command.iter().any(|a| a.starts_with("/Fo")));
659 }
660
661 #[test]
662 fn msvc_c_compile_uses_c_standard_and_no_ehsc() {
663 let mut c = msvc_cxx_compile(CompileMode::Object);
664 c.language = SourceLanguage::C;
665 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
666 assert_eq!(lowered.kind, LoweredActionKind::CompileC);
667 assert!(lowered.command.iter().any(|a| a == "/std:c11"));
668 assert!(!lowered.command.iter().any(|a| a == "/EHsc"));
669 }
670
671 #[test]
672 fn msvc_forces_source_language_per_classification() {
673 let cxx = lower(
678 Dialect::Msvc,
679 &BuildAction::Compile(msvc_cxx_compile(CompileMode::Object)),
680 );
681 assert!(cxx.command.iter().any(|a| a == "/TpC:/src/main.cc"));
682 assert!(!cxx.command.iter().any(|a| a == "C:/src/main.cc"));
683
684 let mut c_action = msvc_cxx_compile(CompileMode::Object);
685 c_action.language = SourceLanguage::C;
686 let c = lower(Dialect::Msvc, &BuildAction::Compile(c_action));
687 assert!(c.command.iter().any(|a| a == "/TcC:/src/main.cc"));
688 assert!(!c.command.iter().any(|a| a == "/TpC:/src/main.cc"));
689 }
690
691 #[test]
692 fn msvc_debug_off_omits_z7_and_opt_maps_o3_to_o2() {
693 let mut c = msvc_cxx_compile(CompileMode::Object);
694 c.arguments.debug_info = false;
695 c.arguments.define_ndebug = false;
696 c.arguments.opt_level = OptLevel::O3;
697 let lowered = lower(Dialect::Msvc, &BuildAction::Compile(c));
698 assert!(!lowered.command.iter().any(|a| a == "/Z7"));
699 assert!(!lowered.command.iter().any(|a| a == "/DNDEBUG"));
700 assert!(lowered.command.iter().any(|a| a == "/O2"));
701 }
702
703 #[test]
704 fn msvc_archive_lowers_to_lib_out_command() {
705 let action = BuildAction::Archive(ArchiveAction {
706 archiver: Utf8PathBuf::from("lib.exe"),
707 output: Utf8PathBuf::from("C:/build/foo.lib"),
708 inputs: vec![
709 Utf8PathBuf::from("C:/build/a.obj"),
710 Utf8PathBuf::from("C:/build/b.obj"),
711 ],
712 description: "AR C:/build/foo.lib".to_owned(),
713 });
714 let lowered = lower(Dialect::Msvc, &action);
715 assert_eq!(
716 lowered.command,
717 strs(&[
718 "lib.exe",
719 "/nologo",
720 "/OUT:C:/build/foo.lib",
721 "C:/build/a.obj",
722 "C:/build/b.obj",
723 ])
724 );
725 }
726
727 #[test]
728 fn msvc_link_lowers_to_cl_fe_with_link_options() {
729 let action = BuildAction::Link(LinkAction {
730 linker: Utf8PathBuf::from("cl.exe"),
731 output: Utf8PathBuf::from("C:/build/app.exe"),
732 inputs: vec![
733 Utf8PathBuf::from("C:/build/main.obj"),
734 Utf8PathBuf::from("C:/build/foo.lib"),
735 ],
736 implicit_inputs: vec![],
737 arguments: strs(&["/SUBSYSTEM:CONSOLE"]),
738 link_libs: vec![],
739 description: "LINK C:/build/app.exe".to_owned(),
740 });
741 let lowered = lower(Dialect::Msvc, &action);
742 assert_eq!(
743 lowered.command,
744 strs(&[
745 "cl.exe",
746 "/nologo",
747 "C:/build/main.obj",
748 "C:/build/foo.lib",
749 "/FeC:/build/app.exe",
750 "/link",
751 "/SUBSYSTEM:CONSOLE",
752 ])
753 );
754 }
755
756 #[test]
757 fn msvc_link_without_ldflags_omits_link_separator() {
758 let action = BuildAction::Link(LinkAction {
759 linker: Utf8PathBuf::from("cl.exe"),
760 output: Utf8PathBuf::from("C:/build/app.exe"),
761 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
762 implicit_inputs: vec![],
763 arguments: vec![],
764 link_libs: vec![],
765 description: "LINK C:/build/app.exe".to_owned(),
766 });
767 let lowered = lower(Dialect::Msvc, &action);
768 assert_eq!(
769 lowered.command,
770 strs(&[
771 "cl.exe",
772 "/nologo",
773 "C:/build/main.obj",
774 "/FeC:/build/app.exe",
775 ])
776 );
777 }
778
779 #[test]
780 fn gnu_link_lowers_link_libs_after_archives() {
781 let action = BuildAction::Link(LinkAction {
785 linker: Utf8PathBuf::from("/usr/bin/cc"),
786 output: Utf8PathBuf::from("/abs/build/app"),
787 inputs: vec![
788 Utf8PathBuf::from("/abs/build/main.o"),
789 Utf8PathBuf::from("/abs/build/libsqlite3.a"),
790 ],
791 implicit_inputs: vec![],
792 arguments: vec![],
793 link_libs: strs(&["pthread", "m"]),
794 description: "LINK /abs/build/app".to_owned(),
795 });
796 let lowered = lower(Dialect::GnuLike, &action);
797 assert_eq!(
798 lowered.command,
799 strs(&[
800 "/usr/bin/cc",
801 "/abs/build/main.o",
802 "/abs/build/libsqlite3.a",
803 "-lpthread",
804 "-lm",
805 "-o",
806 "/abs/build/app",
807 ])
808 );
809 }
810
811 #[test]
812 fn msvc_link_lowers_link_libs_as_dot_lib_inputs() {
813 let action = BuildAction::Link(LinkAction {
817 linker: Utf8PathBuf::from("cl.exe"),
818 output: Utf8PathBuf::from("C:/build/app.exe"),
819 inputs: vec![Utf8PathBuf::from("C:/build/main.obj")],
820 implicit_inputs: vec![],
821 arguments: vec![],
822 link_libs: strs(&["user32"]),
823 description: "LINK C:/build/app.exe".to_owned(),
824 });
825 let lowered = lower(Dialect::Msvc, &action);
826 assert_eq!(
827 lowered.command,
828 strs(&[
829 "cl.exe",
830 "/nologo",
831 "C:/build/main.obj",
832 "user32.lib",
833 "/FeC:/build/app.exe",
834 ])
835 );
836 }
837}