1use std::fmt;
16
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "kebab-case")]
23pub enum CompilerKind {
24 Clang,
26 AppleClang,
30 Gcc,
32 Msvc,
36 Unknown,
39}
40
41impl CompilerKind {
42 pub fn as_key(self) -> &'static str {
44 match self {
45 CompilerKind::Clang => "clang",
46 CompilerKind::AppleClang => "apple-clang",
47 CompilerKind::Gcc => "gcc",
48 CompilerKind::Msvc => "msvc",
49 CompilerKind::Unknown => "unknown",
50 }
51 }
52
53 pub fn is_clang_like(self) -> bool {
55 matches!(self, CompilerKind::Clang | CompilerKind::AppleClang)
56 }
57
58 pub fn supports_gcc_style_command_line(self) -> bool {
62 matches!(
63 self,
64 CompilerKind::Clang | CompilerKind::AppleClang | CompilerKind::Gcc
65 )
66 }
67}
68
69impl fmt::Display for CompilerKind {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.write_str(self.as_key())
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "kebab-case")]
78pub enum ArchiverKind {
79 Ar,
82 LlvmAr,
84 Lib,
87 Unknown,
89}
90
91impl ArchiverKind {
92 pub fn as_key(self) -> &'static str {
93 match self {
94 ArchiverKind::Ar => "ar",
95 ArchiverKind::LlvmAr => "llvm-ar",
96 ArchiverKind::Lib => "lib",
97 ArchiverKind::Unknown => "unknown",
98 }
99 }
100
101 pub fn supports_ar_crs(self) -> bool {
104 matches!(self, ArchiverKind::Ar | ArchiverKind::LlvmAr)
105 }
106}
107
108impl fmt::Display for ArchiverKind {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(self.as_key())
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct CompilerVersion {
121 pub major: u32,
122 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub minor: Option<u32>,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub patch: Option<u32>,
126 pub raw: String,
127}
128
129impl CompilerVersion {
130 pub fn parse(raw: &str) -> Option<Self> {
134 let mut parts = raw.split('.');
135 let major: u32 = parts.next()?.parse().ok()?;
136 let minor = parts.next().and_then(|s| s.parse().ok());
137 let patch = parts.next().and_then(|s| s.parse().ok());
138 Some(Self {
139 major,
140 minor,
141 patch,
142 raw: raw.to_owned(),
143 })
144 }
145
146 pub fn to_display_string(&self) -> String {
149 match (self.minor, self.patch) {
150 (Some(min), Some(pat)) => format!("{}.{}.{}", self.major, min, pat),
151 (Some(min), None) => format!("{}.{}", self.major, min),
152 _ => self.major.to_string(),
153 }
154 }
155}
156
157impl fmt::Display for CompilerVersion {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 f.write_str(&self.to_display_string())
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165pub struct CompilerIdentity {
166 pub kind: CompilerKind,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub version: Option<CompilerVersion>,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
175 pub target: Option<String>,
176 pub raw_version_line: String,
179}
180
181impl CompilerIdentity {
182 pub fn unknown(raw_version_line: impl Into<String>) -> Self {
184 Self {
185 kind: CompilerKind::Unknown,
186 version: None,
187 target: None,
188 raw_version_line: raw_version_line.into(),
189 }
190 }
191
192 pub fn as_json(&self) -> serde_json::Value {
194 let mut obj = serde_json::Map::new();
195 obj.insert(
196 "kind".to_owned(),
197 serde_json::Value::String(self.kind.as_key().to_owned()),
198 );
199 if let Some(v) = &self.version {
200 obj.insert(
201 "version".to_owned(),
202 serde_json::Value::String(v.to_display_string()),
203 );
204 }
205 if let Some(t) = &self.target {
206 obj.insert("target".to_owned(), serde_json::Value::String(t.clone()));
207 }
208 obj.insert(
209 "raw_version_line".to_owned(),
210 serde_json::Value::String(self.raw_version_line.clone()),
211 );
212 serde_json::Value::Object(obj)
213 }
214}
215
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
218pub struct ArchiverIdentity {
219 pub kind: ArchiverKind,
220 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub version: Option<CompilerVersion>,
222 pub raw_version_line: String,
223}
224
225impl ArchiverIdentity {
226 pub fn unknown(raw_version_line: impl Into<String>) -> Self {
227 Self {
228 kind: ArchiverKind::Unknown,
229 version: None,
230 raw_version_line: raw_version_line.into(),
231 }
232 }
233
234 pub fn as_json(&self) -> serde_json::Value {
235 let mut obj = serde_json::Map::new();
236 obj.insert(
237 "kind".to_owned(),
238 serde_json::Value::String(self.kind.as_key().to_owned()),
239 );
240 if let Some(v) = &self.version {
241 obj.insert(
242 "version".to_owned(),
243 serde_json::Value::String(v.to_display_string()),
244 );
245 }
246 obj.insert(
247 "raw_version_line".to_owned(),
248 serde_json::Value::String(self.raw_version_line.clone()),
249 );
250 serde_json::Value::Object(obj)
251 }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
258#[serde(rename_all = "kebab-case")]
259pub enum CapabilitySource {
260 Version,
262 Probe,
266 AssumedDefault,
269 Unsupported,
272}
273
274impl CapabilitySource {
275 pub fn as_key(self) -> &'static str {
276 match self {
277 CapabilitySource::Version => "version",
278 CapabilitySource::Probe => "probe",
279 CapabilitySource::AssumedDefault => "assumed-default",
280 CapabilitySource::Unsupported => "unsupported",
281 }
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
288pub struct Capability {
289 pub supported: bool,
290 pub source: CapabilitySource,
291}
292
293impl Capability {
294 pub fn supported_from(source: CapabilitySource) -> Self {
295 Self {
296 supported: true,
297 source,
298 }
299 }
300 pub fn unsupported_from(source: CapabilitySource) -> Self {
301 Self {
302 supported: false,
303 source,
304 }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
312pub struct CompilerCapabilities {
313 pub gcc_style_flags: Capability,
315 pub msvc_style_flags: Capability,
318 pub depfile_mmd_mf: Capability,
320 pub std_flag: Capability,
322 pub cxx_standard_17: Capability,
325 pub color_diagnostics_flag: Capability,
328 pub response_files: Capability,
330 pub json_diagnostics: Capability,
334 pub sarif_diagnostics: Capability,
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
340pub struct ArchiverCapabilities {
341 pub ar_crs: Capability,
343 pub static_library_output: Capability,
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
351pub struct ToolchainDetectionReport {
352 pub cxx: ToolDetection<CompilerIdentity, CompilerCapabilities>,
353 #[serde(default, skip_serializing_if = "Option::is_none")]
355 pub cc: Option<ToolDetection<CompilerIdentity, CompilerCapabilities>>,
356 pub ar: ToolDetection<ArchiverIdentity, ArchiverCapabilities>,
357}
358
359impl ToolchainDetectionReport {
360 pub fn as_json(&self) -> serde_json::Value {
367 let mut obj = serde_json::Map::new();
368 obj.insert(
369 "cxx".to_owned(),
370 serde_json::json!({
371 "path": self.cxx.path.display().to_string(),
372 "identity": self.cxx.identity.as_json(),
373 "capabilities": cxx_capabilities_as_json(&self.cxx.capabilities),
374 }),
375 );
376 if let Some(cc) = &self.cc {
377 obj.insert(
378 "cc".to_owned(),
379 serde_json::json!({
380 "path": cc.path.display().to_string(),
381 "identity": cc.identity.as_json(),
382 "capabilities": cxx_capabilities_as_json(&cc.capabilities),
383 }),
384 );
385 }
386 obj.insert(
387 "ar".to_owned(),
388 serde_json::json!({
389 "path": self.ar.path.display().to_string(),
390 "identity": self.ar.identity.as_json(),
391 "capabilities": ar_capabilities_as_json(&self.ar.capabilities),
392 }),
393 );
394 serde_json::Value::Object(obj)
395 }
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
403pub struct ToolDetection<I, C> {
404 pub path: std::path::PathBuf,
405 pub identity: I,
406 pub capabilities: C,
407}
408
409pub fn parse_cxx_version_output(text: &str) -> CompilerIdentity {
423 let lines: Vec<&str> = text
424 .lines()
425 .map(str::trim_end)
426 .filter(|l| !l.is_empty())
427 .collect();
428 let first_line = lines.first().copied().unwrap_or("").to_owned();
429
430 let kind = detect_cxx_kind(&lines);
431 let version = match kind {
432 CompilerKind::Clang | CompilerKind::AppleClang => parse_clang_version(&lines),
433 CompilerKind::Gcc => parse_gcc_version(&lines),
434 CompilerKind::Msvc => parse_msvc_version(&lines),
435 CompilerKind::Unknown => None,
436 };
437 let target = parse_target_line(&lines);
438
439 CompilerIdentity {
440 kind,
441 version,
442 target,
443 raw_version_line: truncate(&first_line, 256),
444 }
445}
446
447fn detect_cxx_kind(lines: &[&str]) -> CompilerKind {
448 let joined = lines.join("\n");
449 let lower = joined.to_ascii_lowercase();
450 if lower.contains("apple clang") {
451 return CompilerKind::AppleClang;
452 }
453 if lower.contains("clang version")
454 || lower.contains("clang++")
455 || lower.contains("openbsd clang")
456 {
457 return CompilerKind::Clang;
458 }
459 if lower.contains("microsoft (r)") || lower.contains("microsoft c/c++") {
460 return CompilerKind::Msvc;
461 }
462 if lower.contains("free software foundation")
463 || lower.starts_with("g++")
464 || lower.starts_with("gcc")
465 || lower.contains("gnu c++")
466 {
467 return CompilerKind::Gcc;
468 }
469 CompilerKind::Unknown
470}
471
472fn parse_clang_version(lines: &[&str]) -> Option<CompilerVersion> {
473 let first = lines.first()?;
474 let lower = first.to_ascii_lowercase();
475 let needle = if lower.starts_with("apple clang") {
476 "apple clang version "
477 } else {
478 "clang version "
479 };
480 let idx = lower.find(needle)?;
481 let after = &first[idx + needle.len()..];
482 let token = after
483 .split_whitespace()
484 .next()
485 .unwrap_or("")
486 .trim_end_matches(',');
487 CompilerVersion::parse(token)
488}
489
490fn parse_gcc_version(lines: &[&str]) -> Option<CompilerVersion> {
491 let first = lines.first()?;
497 first
498 .split_whitespace()
499 .filter_map(|tok| {
500 let trimmed = tok.trim_end_matches(',');
501 CompilerVersion::parse(trimmed)
502 })
503 .next_back()
504}
505
506fn parse_msvc_version(lines: &[&str]) -> Option<CompilerVersion> {
507 let joined = lines.join(" ");
508 let lower = joined.to_ascii_lowercase();
509 let idx = lower.find("version ")?;
510 let after = &joined[idx + "version ".len()..];
511 let token = after.split_whitespace().next().unwrap_or("");
512 CompilerVersion::parse(token)
513}
514
515fn parse_target_line(lines: &[&str]) -> Option<String> {
516 for line in lines {
517 let trimmed = line.trim();
518 if let Some(rest) = trimmed.strip_prefix("Target:") {
519 let v = rest.trim();
520 if !v.is_empty() {
521 return Some(v.to_owned());
522 }
523 }
524 }
525 None
526}
527
528pub fn parse_ar_version_output(text: &str) -> ArchiverIdentity {
535 let lines: Vec<&str> = text
536 .lines()
537 .map(str::trim_end)
538 .filter(|l| !l.is_empty())
539 .collect();
540 let first_line = lines.first().copied().unwrap_or("").to_owned();
541 let lower = lines.join("\n").to_ascii_lowercase();
542
543 let kind = if lower.contains("llvm version") || lower.contains("llvm-ar") {
544 ArchiverKind::LlvmAr
545 } else if lower.contains("gnu ar") || lower.contains("gnu binutils") || lower.starts_with("ar ")
546 {
547 ArchiverKind::Ar
548 } else if lower.contains("microsoft (r) library manager") || lower.contains("lib.exe") {
549 ArchiverKind::Lib
550 } else {
551 ArchiverKind::Unknown
552 };
553
554 let version = match kind {
555 ArchiverKind::LlvmAr => parse_llvm_ar_version(&lines),
556 ArchiverKind::Ar => parse_gnu_ar_version(&lines),
557 ArchiverKind::Lib => parse_msvc_version(&lines),
558 ArchiverKind::Unknown => None,
559 };
560
561 ArchiverIdentity {
562 kind,
563 version,
564 raw_version_line: truncate(&first_line, 256),
565 }
566}
567
568fn parse_gnu_ar_version(lines: &[&str]) -> Option<CompilerVersion> {
569 let first = lines.first()?;
572 first
573 .split_whitespace()
574 .filter_map(|tok| CompilerVersion::parse(tok.trim_end_matches(',')))
575 .next_back()
576}
577
578fn parse_llvm_ar_version(lines: &[&str]) -> Option<CompilerVersion> {
579 for line in lines {
582 let lower = line.to_ascii_lowercase();
583 if let Some(idx) = lower.find("llvm version ") {
584 let after = &line[idx + "llvm version ".len()..];
585 if let Some(token) = after.split_whitespace().next()
586 && let Some(v) = CompilerVersion::parse(token)
587 {
588 return Some(v);
589 }
590 }
591 }
592 None
593}
594
595fn truncate(s: &str, max: usize) -> String {
596 if s.len() <= max {
597 return s.to_owned();
598 }
599 let mut end = max;
600 while !s.is_char_boundary(end) && end > 0 {
601 end -= 1;
602 }
603 s[..end].to_owned()
604}
605
606pub fn derive_cxx_capabilities(identity: &CompilerIdentity) -> CompilerCapabilities {
612 let gcc_style = if identity.kind.supports_gcc_style_command_line() {
613 Capability::supported_from(CapabilitySource::Version)
614 } else if identity.kind == CompilerKind::Msvc {
615 Capability::unsupported_from(CapabilitySource::Unsupported)
616 } else {
617 Capability::unsupported_from(CapabilitySource::AssumedDefault)
618 };
619 let msvc_style = if identity.kind == CompilerKind::Msvc {
620 Capability::supported_from(CapabilitySource::Version)
621 } else {
622 Capability::unsupported_from(CapabilitySource::AssumedDefault)
623 };
624 let depfile_mmd_mf = if identity.kind.supports_gcc_style_command_line() {
625 Capability::supported_from(CapabilitySource::Version)
626 } else {
627 Capability::unsupported_from(match identity.kind {
628 CompilerKind::Msvc => CapabilitySource::Unsupported,
629 _ => CapabilitySource::AssumedDefault,
630 })
631 };
632 let std_flag = if identity.kind.supports_gcc_style_command_line() {
633 Capability::supported_from(CapabilitySource::Version)
634 } else {
635 Capability::unsupported_from(match identity.kind {
636 CompilerKind::Msvc => CapabilitySource::Unsupported,
637 _ => CapabilitySource::AssumedDefault,
638 })
639 };
640 let cxx_standard_17 = match identity.kind {
645 CompilerKind::Clang | CompilerKind::AppleClang => {
646 Capability::supported_from(CapabilitySource::Version)
647 }
648 CompilerKind::Gcc => match identity.version.as_ref().map(|v| v.major) {
649 Some(m) if m >= 5 => Capability::supported_from(CapabilitySource::Version),
650 Some(_) => Capability::unsupported_from(CapabilitySource::Version),
651 None => Capability::supported_from(CapabilitySource::AssumedDefault),
652 },
653 CompilerKind::Msvc => Capability::unsupported_from(CapabilitySource::Unsupported),
654 CompilerKind::Unknown => Capability::unsupported_from(CapabilitySource::AssumedDefault),
655 };
656 let color = if identity.kind.is_clang_like() || identity.kind == CompilerKind::Gcc {
657 Capability::supported_from(CapabilitySource::Version)
658 } else {
659 Capability::unsupported_from(CapabilitySource::AssumedDefault)
660 };
661 let response_files = if identity.kind.is_clang_like() || identity.kind == CompilerKind::Gcc {
662 Capability::supported_from(CapabilitySource::Version)
663 } else {
664 Capability::unsupported_from(CapabilitySource::AssumedDefault)
665 };
666 let json_diagnostics = if identity.kind.is_clang_like() {
667 Capability::supported_from(CapabilitySource::Version)
668 } else {
669 Capability::unsupported_from(CapabilitySource::AssumedDefault)
670 };
671 let sarif_diagnostics = Capability::unsupported_from(CapabilitySource::AssumedDefault);
675
676 CompilerCapabilities {
677 gcc_style_flags: gcc_style,
678 msvc_style_flags: msvc_style,
679 depfile_mmd_mf,
680 std_flag,
681 cxx_standard_17,
682 color_diagnostics_flag: color,
683 response_files,
684 json_diagnostics,
685 sarif_diagnostics,
686 }
687}
688
689pub fn derive_ar_capabilities(identity: &ArchiverIdentity) -> ArchiverCapabilities {
692 let ar_crs = if identity.kind.supports_ar_crs() {
693 Capability::supported_from(CapabilitySource::Version)
694 } else if identity.kind == ArchiverKind::Lib {
695 Capability::unsupported_from(CapabilitySource::Unsupported)
696 } else {
697 Capability::unsupported_from(CapabilitySource::AssumedDefault)
698 };
699 let static_library_output = if identity.kind.supports_ar_crs() {
700 Capability::supported_from(CapabilitySource::Version)
701 } else if identity.kind == ArchiverKind::Lib {
702 Capability::unsupported_from(CapabilitySource::Unsupported)
705 } else {
706 Capability::unsupported_from(CapabilitySource::AssumedDefault)
707 };
708 ArchiverCapabilities {
709 ar_crs,
710 static_library_output,
711 }
712}
713
714#[derive(Debug, Error, Clone, PartialEq, Eq)]
717pub enum ToolDetectionError {
718 #[error(
719 "selected C++ compiler `{spec}` is MSVC, but the current C++ backend requires a GCC- or Clang-like compiler"
720 )]
721 UnsupportedCxxBackend { spec: String },
722
723 #[error(
724 "selected C++ compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
725 )]
726 UnknownCxxRequiresGccStyle { spec: String },
727
728 #[error(
729 "selected C++ compiler `{spec}` ({kind}) does not support the required C++17 standard flag"
730 )]
731 CxxLacksStdCxx17 { spec: String, kind: CompilerKind },
732
733 #[error(
734 "selected C++ compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
735 )]
736 CxxLacksDepfile { spec: String, kind: CompilerKind },
737
738 #[error(
739 "selected C compiler `{spec}` is MSVC, but the current C backend requires a GCC- or Clang-like compiler"
740 )]
741 UnsupportedCBackend { spec: String },
742
743 #[error(
744 "selected C compiler `{spec}` could not be identified and the current backend requires GCC-style flags"
745 )]
746 UnknownCRequiresGccStyle { spec: String },
747
748 #[error(
749 "selected C compiler `{spec}` ({kind}) does not support the depfile flags required by the Ninja backend"
750 )]
751 CLacksDepfile { spec: String, kind: CompilerKind },
752
753 #[error(
754 "selected archiver `{spec}` is not supported by the current static-library backend; use an ar-compatible archiver"
755 )]
756 UnsupportedArchiver { spec: String },
757
758 #[error(
759 "selected archiver `{spec}` could not be identified and the current backend requires `ar crs`-compatible behavior"
760 )]
761 UnknownArchiverRequiresArCompatible { spec: String },
762}
763
764pub fn validate_cxx_for_backend(
777 spec_display: &str,
778 identity: &CompilerIdentity,
779 capabilities: &CompilerCapabilities,
780) -> Result<(), ToolDetectionError> {
781 if identity.kind == CompilerKind::Msvc {
782 return Err(ToolDetectionError::UnsupportedCxxBackend {
783 spec: spec_display.to_owned(),
784 });
785 }
786 if !capabilities.gcc_style_flags.supported {
787 if identity.kind == CompilerKind::Unknown {
788 return Err(ToolDetectionError::UnknownCxxRequiresGccStyle {
789 spec: spec_display.to_owned(),
790 });
791 }
792 return Err(ToolDetectionError::UnsupportedCxxBackend {
793 spec: spec_display.to_owned(),
794 });
795 }
796 if !capabilities.depfile_mmd_mf.supported {
797 return Err(ToolDetectionError::CxxLacksDepfile {
798 spec: spec_display.to_owned(),
799 kind: identity.kind,
800 });
801 }
802 if !capabilities.cxx_standard_17.supported {
803 return Err(ToolDetectionError::CxxLacksStdCxx17 {
804 spec: spec_display.to_owned(),
805 kind: identity.kind,
806 });
807 }
808 Ok(())
809}
810
811pub fn validate_cc_for_backend(
825 spec_display: &str,
826 identity: &CompilerIdentity,
827 capabilities: &CompilerCapabilities,
828) -> Result<(), ToolDetectionError> {
829 if identity.kind == CompilerKind::Msvc {
830 return Err(ToolDetectionError::UnsupportedCBackend {
831 spec: spec_display.to_owned(),
832 });
833 }
834 if !capabilities.gcc_style_flags.supported {
835 if identity.kind == CompilerKind::Unknown {
836 return Err(ToolDetectionError::UnknownCRequiresGccStyle {
837 spec: spec_display.to_owned(),
838 });
839 }
840 return Err(ToolDetectionError::UnsupportedCBackend {
841 spec: spec_display.to_owned(),
842 });
843 }
844 if !capabilities.depfile_mmd_mf.supported {
845 return Err(ToolDetectionError::CLacksDepfile {
846 spec: spec_display.to_owned(),
847 kind: identity.kind,
848 });
849 }
850 Ok(())
851}
852
853pub fn validate_ar_for_backend(
862 spec_display: &str,
863 identity: &ArchiverIdentity,
864 capabilities: &ArchiverCapabilities,
865) -> Result<(), ToolDetectionError> {
866 if identity.kind == ArchiverKind::Lib {
867 return Err(ToolDetectionError::UnsupportedArchiver {
868 spec: spec_display.to_owned(),
869 });
870 }
871 if !capabilities.ar_crs.supported {
872 if identity.kind == ArchiverKind::Unknown {
873 return Err(ToolDetectionError::UnknownArchiverRequiresArCompatible {
874 spec: spec_display.to_owned(),
875 });
876 }
877 return Err(ToolDetectionError::UnsupportedArchiver {
878 spec: spec_display.to_owned(),
879 });
880 }
881 Ok(())
882}
883
884pub(crate) fn cxx_capabilities_as_json(caps: &CompilerCapabilities) -> serde_json::Value {
887 let CompilerCapabilities {
891 gcc_style_flags,
892 msvc_style_flags,
893 depfile_mmd_mf,
894 std_flag,
895 cxx_standard_17,
896 color_diagnostics_flag,
897 response_files,
898 json_diagnostics,
899 sarif_diagnostics,
900 } = caps;
901 let mut entries: [(&'static str, &Capability); 9] = [
902 ("gcc_style_flags", gcc_style_flags),
903 ("msvc_style_flags", msvc_style_flags),
904 ("depfile_mmd_mf", depfile_mmd_mf),
905 ("std_flag", std_flag),
906 ("cxx_standard_17", cxx_standard_17),
907 ("color_diagnostics_flag", color_diagnostics_flag),
908 ("response_files", response_files),
909 ("json_diagnostics", json_diagnostics),
910 ("sarif_diagnostics", sarif_diagnostics),
911 ];
912 capabilities_to_json(&mut entries)
913}
914
915pub(crate) fn ar_capabilities_as_json(caps: &ArchiverCapabilities) -> serde_json::Value {
916 let ArchiverCapabilities {
917 ar_crs,
918 static_library_output,
919 } = caps;
920 let mut entries: [(&'static str, &Capability); 2] = [
921 ("ar_crs", ar_crs),
922 ("static_library_output", static_library_output),
923 ];
924 capabilities_to_json(&mut entries)
925}
926
927fn capabilities_to_json(entries: &mut [(&'static str, &Capability)]) -> serde_json::Value {
932 entries.sort_by_key(|(key, _)| *key);
933 let mut obj = serde_json::Map::new();
934 for (key, cap) in entries {
935 obj.insert(
936 (*key).to_owned(),
937 serde_json::json!({
938 "supported": cap.supported,
939 "source": cap.source.as_key(),
940 }),
941 );
942 }
943 serde_json::Value::Object(obj)
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949
950 #[test]
951 fn parses_clang_first_line() {
952 let id = parse_cxx_version_output(
953 "clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\nThread model: posix\n",
954 );
955 assert_eq!(id.kind, CompilerKind::Clang);
956 let v = id.version.expect("version parsed");
957 assert_eq!(v.major, 17);
958 assert_eq!(v.minor, Some(0));
959 assert_eq!(v.patch, Some(6));
960 assert_eq!(id.target.as_deref(), Some("x86_64-unknown-linux-gnu"));
961 }
962
963 #[test]
964 fn parses_apple_clang() {
965 let id = parse_cxx_version_output(
966 "Apple clang version 14.0.3 (clang-1403.0.22.14.1)\nTarget: arm64-apple-darwin22.5.0\nThread model: posix\n",
967 );
968 assert_eq!(id.kind, CompilerKind::AppleClang);
969 let v = id.version.unwrap();
970 assert_eq!((v.major, v.minor, v.patch), (14, Some(0), Some(3)));
971 }
972
973 #[test]
974 fn parses_gcc_with_distro_prefix() {
975 let id = parse_cxx_version_output(
976 "g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
977 );
978 assert_eq!(id.kind, CompilerKind::Gcc);
979 let v = id.version.unwrap();
980 assert_eq!((v.major, v.minor, v.patch), (11, Some(4), Some(0)));
981 }
982
983 #[test]
984 fn parses_msvc_first_line() {
985 let id = parse_cxx_version_output(
986 "Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64\n",
987 );
988 assert_eq!(id.kind, CompilerKind::Msvc);
989 let v = id.version.unwrap();
990 assert_eq!(v.major, 19);
991 }
992
993 #[test]
994 fn unknown_when_unrecognized() {
995 let id = parse_cxx_version_output("My funky compiler 0.0\n");
996 assert_eq!(id.kind, CompilerKind::Unknown);
997 assert!(id.version.is_none());
998 }
999
1000 #[test]
1001 fn empty_output_is_unknown() {
1002 let id = parse_cxx_version_output("");
1003 assert_eq!(id.kind, CompilerKind::Unknown);
1004 assert!(id.raw_version_line.is_empty());
1005 }
1006
1007 #[test]
1008 fn parses_gnu_ar() {
1009 let id = parse_ar_version_output(
1010 "GNU ar (GNU Binutils for Debian) 2.40\nCopyright (C) 2023 Free Software Foundation, Inc.\n",
1011 );
1012 assert_eq!(id.kind, ArchiverKind::Ar);
1013 let v = id.version.unwrap();
1014 assert_eq!(v.major, 2);
1015 }
1016
1017 #[test]
1018 fn parses_llvm_ar_version() {
1019 let id = parse_ar_version_output(
1020 "LLVM (http://llvm.org/):\n LLVM version 17.0.6\n Optimized build.\n",
1021 );
1022 assert_eq!(id.kind, ArchiverKind::LlvmAr);
1023 let v = id.version.unwrap();
1024 assert_eq!(v.major, 17);
1025 }
1026
1027 #[test]
1028 fn detects_lib_exe_as_unsupported() {
1029 let id = parse_ar_version_output(
1030 "Microsoft (R) Library Manager Version 14.39.33523.0\nCopyright (C) Microsoft Corporation.\n",
1031 );
1032 assert_eq!(id.kind, ArchiverKind::Lib);
1033 }
1034
1035 #[test]
1036 fn unknown_archiver_classification() {
1037 let id = parse_ar_version_output("just-some-archiver 0.1\n");
1038 assert_eq!(id.kind, ArchiverKind::Unknown);
1039 assert!(id.version.is_none());
1040 }
1041
1042 #[test]
1043 fn clang_capabilities_include_gcc_style_and_cxx17() {
1044 let id = CompilerIdentity {
1045 kind: CompilerKind::Clang,
1046 version: CompilerVersion::parse("17.0.6"),
1047 target: None,
1048 raw_version_line: "clang version 17.0.6".into(),
1049 };
1050 let caps = derive_cxx_capabilities(&id);
1051 assert!(caps.gcc_style_flags.supported);
1052 assert!(caps.depfile_mmd_mf.supported);
1053 assert!(caps.std_flag.supported);
1054 assert!(caps.cxx_standard_17.supported);
1055 }
1056
1057 #[test]
1058 fn gcc_pre_5_does_not_claim_cxx17() {
1059 let id = CompilerIdentity {
1060 kind: CompilerKind::Gcc,
1061 version: CompilerVersion::parse("4.8.5"),
1062 target: None,
1063 raw_version_line: "g++ 4.8.5".into(),
1064 };
1065 let caps = derive_cxx_capabilities(&id);
1066 assert!(caps.gcc_style_flags.supported);
1067 assert!(!caps.cxx_standard_17.supported);
1068 }
1069
1070 #[test]
1071 fn msvc_capabilities_reject_gcc_style() {
1072 let id = CompilerIdentity {
1073 kind: CompilerKind::Msvc,
1074 version: CompilerVersion::parse("19.39.0"),
1075 target: None,
1076 raw_version_line: "Microsoft Optimizing Compiler".into(),
1077 };
1078 let caps = derive_cxx_capabilities(&id);
1079 assert!(!caps.gcc_style_flags.supported);
1080 assert_eq!(caps.gcc_style_flags.source, CapabilitySource::Unsupported);
1081 assert!(caps.msvc_style_flags.supported);
1082 }
1083
1084 #[test]
1085 fn unknown_compiler_capabilities_are_conservative() {
1086 let id = CompilerIdentity::unknown("strange compiler");
1087 let caps = derive_cxx_capabilities(&id);
1088 assert!(!caps.gcc_style_flags.supported);
1089 assert_eq!(
1090 caps.gcc_style_flags.source,
1091 CapabilitySource::AssumedDefault
1092 );
1093 assert!(!caps.depfile_mmd_mf.supported);
1094 }
1095
1096 #[test]
1097 fn ar_capabilities_recognize_gnu_ar() {
1098 let id = ArchiverIdentity {
1099 kind: ArchiverKind::Ar,
1100 version: CompilerVersion::parse("2.40"),
1101 raw_version_line: "GNU ar".into(),
1102 };
1103 let caps = derive_ar_capabilities(&id);
1104 assert!(caps.ar_crs.supported);
1105 assert!(caps.static_library_output.supported);
1106 }
1107
1108 #[test]
1109 fn ar_capabilities_reject_msvc_lib() {
1110 let id = ArchiverIdentity {
1111 kind: ArchiverKind::Lib,
1112 version: None,
1113 raw_version_line: "Microsoft Library Manager".into(),
1114 };
1115 let caps = derive_ar_capabilities(&id);
1116 assert!(!caps.ar_crs.supported);
1117 assert_eq!(caps.ar_crs.source, CapabilitySource::Unsupported);
1118 }
1119
1120 #[test]
1121 fn validate_rejects_msvc_cxx() {
1122 let id = CompilerIdentity {
1123 kind: CompilerKind::Msvc,
1124 version: None,
1125 target: None,
1126 raw_version_line: "MSVC".into(),
1127 };
1128 let caps = derive_cxx_capabilities(&id);
1129 let err = validate_cxx_for_backend("cl.exe", &id, &caps).unwrap_err();
1130 assert!(matches!(
1131 err,
1132 ToolDetectionError::UnsupportedCxxBackend { .. }
1133 ));
1134 }
1135
1136 #[test]
1137 fn validate_rejects_unknown_cxx() {
1138 let id = CompilerIdentity::unknown("???");
1139 let caps = derive_cxx_capabilities(&id);
1140 let err = validate_cxx_for_backend("custom-cxx", &id, &caps).unwrap_err();
1141 assert!(matches!(
1142 err,
1143 ToolDetectionError::UnknownCxxRequiresGccStyle { .. }
1144 ));
1145 }
1146
1147 #[test]
1148 fn validate_accepts_clang() {
1149 let id = CompilerIdentity {
1150 kind: CompilerKind::Clang,
1151 version: CompilerVersion::parse("17.0.6"),
1152 target: None,
1153 raw_version_line: "clang version 17.0.6".into(),
1154 };
1155 let caps = derive_cxx_capabilities(&id);
1156 assert!(validate_cxx_for_backend("clang++", &id, &caps).is_ok());
1157 }
1158
1159 #[test]
1160 fn validate_rejects_gcc_too_old_for_cxx17() {
1161 let id = CompilerIdentity {
1162 kind: CompilerKind::Gcc,
1163 version: CompilerVersion::parse("4.8.5"),
1164 target: None,
1165 raw_version_line: "g++ 4.8".into(),
1166 };
1167 let caps = derive_cxx_capabilities(&id);
1168 let err = validate_cxx_for_backend("g++", &id, &caps).unwrap_err();
1169 assert!(matches!(err, ToolDetectionError::CxxLacksStdCxx17 { .. }));
1170 }
1171
1172 #[test]
1173 fn validate_cc_accepts_pure_c_clang_without_cxx17_capability() {
1174 let id = CompilerIdentity {
1181 kind: CompilerKind::Clang,
1182 version: CompilerVersion::parse("17.0.6"),
1183 target: None,
1184 raw_version_line: "clang version 17.0.6".into(),
1185 };
1186 let mut caps = derive_cxx_capabilities(&id);
1187 caps.cxx_standard_17 = Capability {
1190 supported: false,
1191 source: CapabilitySource::Unsupported,
1192 };
1193 assert!(validate_cc_for_backend("cc", &id, &caps).is_ok());
1194 assert!(matches!(
1199 validate_cxx_for_backend("cc", &id, &caps).unwrap_err(),
1200 ToolDetectionError::CxxLacksStdCxx17 { .. }
1201 ));
1202 }
1203
1204 #[test]
1205 fn validate_cc_rejects_msvc() {
1206 let id = CompilerIdentity {
1207 kind: CompilerKind::Msvc,
1208 version: None,
1209 target: None,
1210 raw_version_line: "MSVC".into(),
1211 };
1212 let caps = derive_cxx_capabilities(&id);
1213 let err = validate_cc_for_backend("cl.exe", &id, &caps).unwrap_err();
1214 assert!(matches!(
1215 err,
1216 ToolDetectionError::UnsupportedCBackend { .. }
1217 ));
1218 }
1219
1220 #[test]
1221 fn validate_cc_rejects_unknown_compiler_without_gcc_style() {
1222 let id = CompilerIdentity::unknown("???");
1226 let caps = derive_cxx_capabilities(&id);
1227 let err = validate_cc_for_backend("custom-cc", &id, &caps).unwrap_err();
1228 assert!(matches!(
1229 err,
1230 ToolDetectionError::UnknownCRequiresGccStyle { .. }
1231 ));
1232 }
1233
1234 #[test]
1235 fn validate_cc_rejects_gcc_without_depfile_support() {
1236 let id = CompilerIdentity {
1241 kind: CompilerKind::Gcc,
1242 version: CompilerVersion::parse("9.4.0"),
1243 target: None,
1244 raw_version_line: "gcc 9.4".into(),
1245 };
1246 let mut caps = derive_cxx_capabilities(&id);
1247 caps.depfile_mmd_mf = Capability {
1248 supported: false,
1249 source: CapabilitySource::Unsupported,
1250 };
1251 let err = validate_cc_for_backend("cc", &id, &caps).unwrap_err();
1252 assert!(matches!(err, ToolDetectionError::CLacksDepfile { .. }));
1253 }
1254
1255 #[test]
1256 fn validate_rejects_msvc_archiver() {
1257 let id = ArchiverIdentity {
1258 kind: ArchiverKind::Lib,
1259 version: None,
1260 raw_version_line: "Microsoft Library Manager".into(),
1261 };
1262 let caps = derive_ar_capabilities(&id);
1263 let err = validate_ar_for_backend("lib.exe", &id, &caps).unwrap_err();
1264 assert!(matches!(
1265 err,
1266 ToolDetectionError::UnsupportedArchiver { .. }
1267 ));
1268 }
1269
1270 #[test]
1271 fn version_display_truncates_unset_components() {
1272 let v = CompilerVersion::parse("11").unwrap();
1273 assert_eq!(v.to_display_string(), "11");
1274 let v = CompilerVersion::parse("11.4").unwrap();
1275 assert_eq!(v.to_display_string(), "11.4");
1276 let v = CompilerVersion::parse("11.4.0").unwrap();
1277 assert_eq!(v.to_display_string(), "11.4.0");
1278 }
1279
1280 fn pretty(value: &serde_json::Value) -> String {
1291 serde_json::to_string_pretty(value).unwrap()
1292 }
1293
1294 fn cxx_identity_and_capabilities_json(version_output: &str) -> String {
1295 let id = parse_cxx_version_output(version_output);
1296 let caps = derive_cxx_capabilities(&id);
1297 pretty(&serde_json::json!({
1298 "identity": id.as_json(),
1299 "capabilities": cxx_capabilities_as_json(&caps),
1300 }))
1301 }
1302
1303 fn ar_identity_and_capabilities_json(version_output: &str) -> String {
1304 let id = parse_ar_version_output(version_output);
1305 let caps = derive_ar_capabilities(&id);
1306 pretty(&serde_json::json!({
1307 "identity": id.as_json(),
1308 "capabilities": ar_capabilities_as_json(&caps),
1309 }))
1310 }
1311
1312 #[test]
1313 fn snapshot_clang_identity_and_capabilities() {
1314 let actual = cxx_identity_and_capabilities_json(
1315 "clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\nThread model: posix\n",
1316 );
1317 let expected = r#"{
1318 "identity": {
1319 "kind": "clang",
1320 "version": "17.0.6",
1321 "target": "x86_64-unknown-linux-gnu",
1322 "raw_version_line": "clang version 17.0.6"
1323 },
1324 "capabilities": {
1325 "color_diagnostics_flag": {
1326 "supported": true,
1327 "source": "version"
1328 },
1329 "cxx_standard_17": {
1330 "supported": true,
1331 "source": "version"
1332 },
1333 "depfile_mmd_mf": {
1334 "supported": true,
1335 "source": "version"
1336 },
1337 "gcc_style_flags": {
1338 "supported": true,
1339 "source": "version"
1340 },
1341 "json_diagnostics": {
1342 "supported": true,
1343 "source": "version"
1344 },
1345 "msvc_style_flags": {
1346 "supported": false,
1347 "source": "assumed-default"
1348 },
1349 "response_files": {
1350 "supported": true,
1351 "source": "version"
1352 },
1353 "sarif_diagnostics": {
1354 "supported": false,
1355 "source": "assumed-default"
1356 },
1357 "std_flag": {
1358 "supported": true,
1359 "source": "version"
1360 }
1361 }
1362}"#;
1363 assert_eq!(actual, expected);
1364 }
1365
1366 #[test]
1367 fn snapshot_apple_clang_identity_and_capabilities() {
1368 let actual = cxx_identity_and_capabilities_json(
1369 "Apple clang version 14.0.3 (clang-1403.0.22.14.1)\nTarget: arm64-apple-darwin22.5.0\nThread model: posix\n",
1370 );
1371 let expected = r#"{
1372 "identity": {
1373 "kind": "apple-clang",
1374 "version": "14.0.3",
1375 "target": "arm64-apple-darwin22.5.0",
1376 "raw_version_line": "Apple clang version 14.0.3 (clang-1403.0.22.14.1)"
1377 },
1378 "capabilities": {
1379 "color_diagnostics_flag": {
1380 "supported": true,
1381 "source": "version"
1382 },
1383 "cxx_standard_17": {
1384 "supported": true,
1385 "source": "version"
1386 },
1387 "depfile_mmd_mf": {
1388 "supported": true,
1389 "source": "version"
1390 },
1391 "gcc_style_flags": {
1392 "supported": true,
1393 "source": "version"
1394 },
1395 "json_diagnostics": {
1396 "supported": true,
1397 "source": "version"
1398 },
1399 "msvc_style_flags": {
1400 "supported": false,
1401 "source": "assumed-default"
1402 },
1403 "response_files": {
1404 "supported": true,
1405 "source": "version"
1406 },
1407 "sarif_diagnostics": {
1408 "supported": false,
1409 "source": "assumed-default"
1410 },
1411 "std_flag": {
1412 "supported": true,
1413 "source": "version"
1414 }
1415 }
1416}"#;
1417 assert_eq!(actual, expected);
1418 }
1419
1420 #[test]
1421 fn snapshot_gcc_identity_and_capabilities() {
1422 let actual = cxx_identity_and_capabilities_json(
1423 "g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0\nCopyright (C) 2021 Free Software Foundation, Inc.\n",
1424 );
1425 let expected = r#"{
1426 "identity": {
1427 "kind": "gcc",
1428 "version": "11.4.0",
1429 "raw_version_line": "g++ (Ubuntu 11.4.0-1ubuntu1) 11.4.0"
1430 },
1431 "capabilities": {
1432 "color_diagnostics_flag": {
1433 "supported": true,
1434 "source": "version"
1435 },
1436 "cxx_standard_17": {
1437 "supported": true,
1438 "source": "version"
1439 },
1440 "depfile_mmd_mf": {
1441 "supported": true,
1442 "source": "version"
1443 },
1444 "gcc_style_flags": {
1445 "supported": true,
1446 "source": "version"
1447 },
1448 "json_diagnostics": {
1449 "supported": false,
1450 "source": "assumed-default"
1451 },
1452 "msvc_style_flags": {
1453 "supported": false,
1454 "source": "assumed-default"
1455 },
1456 "response_files": {
1457 "supported": true,
1458 "source": "version"
1459 },
1460 "sarif_diagnostics": {
1461 "supported": false,
1462 "source": "assumed-default"
1463 },
1464 "std_flag": {
1465 "supported": true,
1466 "source": "version"
1467 }
1468 }
1469}"#;
1470 assert_eq!(actual, expected);
1471 }
1472
1473 #[test]
1474 fn snapshot_msvc_identity_and_capabilities() {
1475 let actual = cxx_identity_and_capabilities_json(
1476 "Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64\n",
1477 );
1478 let expected = r#"{
1482 "identity": {
1483 "kind": "msvc",
1484 "version": "19.39.33523",
1485 "raw_version_line": "Microsoft (R) C/C++ Optimizing Compiler Version 19.39.33523 for x64"
1486 },
1487 "capabilities": {
1488 "color_diagnostics_flag": {
1489 "supported": false,
1490 "source": "assumed-default"
1491 },
1492 "cxx_standard_17": {
1493 "supported": false,
1494 "source": "unsupported"
1495 },
1496 "depfile_mmd_mf": {
1497 "supported": false,
1498 "source": "unsupported"
1499 },
1500 "gcc_style_flags": {
1501 "supported": false,
1502 "source": "unsupported"
1503 },
1504 "json_diagnostics": {
1505 "supported": false,
1506 "source": "assumed-default"
1507 },
1508 "msvc_style_flags": {
1509 "supported": true,
1510 "source": "version"
1511 },
1512 "response_files": {
1513 "supported": false,
1514 "source": "assumed-default"
1515 },
1516 "sarif_diagnostics": {
1517 "supported": false,
1518 "source": "assumed-default"
1519 },
1520 "std_flag": {
1521 "supported": false,
1522 "source": "unsupported"
1523 }
1524 }
1525}"#;
1526 assert_eq!(actual, expected);
1527 }
1528
1529 #[test]
1530 fn snapshot_unknown_compiler_capabilities_are_conservative() {
1531 let actual = cxx_identity_and_capabilities_json("My funky compiler 0.0\n");
1532 let expected = r#"{
1533 "identity": {
1534 "kind": "unknown",
1535 "raw_version_line": "My funky compiler 0.0"
1536 },
1537 "capabilities": {
1538 "color_diagnostics_flag": {
1539 "supported": false,
1540 "source": "assumed-default"
1541 },
1542 "cxx_standard_17": {
1543 "supported": false,
1544 "source": "assumed-default"
1545 },
1546 "depfile_mmd_mf": {
1547 "supported": false,
1548 "source": "assumed-default"
1549 },
1550 "gcc_style_flags": {
1551 "supported": false,
1552 "source": "assumed-default"
1553 },
1554 "json_diagnostics": {
1555 "supported": false,
1556 "source": "assumed-default"
1557 },
1558 "msvc_style_flags": {
1559 "supported": false,
1560 "source": "assumed-default"
1561 },
1562 "response_files": {
1563 "supported": false,
1564 "source": "assumed-default"
1565 },
1566 "sarif_diagnostics": {
1567 "supported": false,
1568 "source": "assumed-default"
1569 },
1570 "std_flag": {
1571 "supported": false,
1572 "source": "assumed-default"
1573 }
1574 }
1575}"#;
1576 assert_eq!(actual, expected);
1577 }
1578
1579 #[test]
1580 fn snapshot_gnu_ar_identity_and_capabilities() {
1581 let actual = ar_identity_and_capabilities_json(
1582 "GNU ar (GNU Binutils for Debian) 2.40\nCopyright (C) 2023 Free Software Foundation, Inc.\n",
1583 );
1584 let expected = r#"{
1585 "identity": {
1586 "kind": "ar",
1587 "version": "2.40",
1588 "raw_version_line": "GNU ar (GNU Binutils for Debian) 2.40"
1589 },
1590 "capabilities": {
1591 "ar_crs": {
1592 "supported": true,
1593 "source": "version"
1594 },
1595 "static_library_output": {
1596 "supported": true,
1597 "source": "version"
1598 }
1599 }
1600}"#;
1601 assert_eq!(actual, expected);
1602 }
1603
1604 #[test]
1605 fn snapshot_msvc_lib_archiver_is_marked_unsupported() {
1606 let actual = ar_identity_and_capabilities_json(
1607 "Microsoft (R) Library Manager Version 14.39.33523.0\nCopyright (C) Microsoft Corporation.\n",
1608 );
1609 let expected = r#"{
1610 "identity": {
1611 "kind": "lib",
1612 "version": "14.39.33523",
1613 "raw_version_line": "Microsoft (R) Library Manager Version 14.39.33523.0"
1614 },
1615 "capabilities": {
1616 "ar_crs": {
1617 "supported": false,
1618 "source": "unsupported"
1619 },
1620 "static_library_output": {
1621 "supported": false,
1622 "source": "unsupported"
1623 }
1624 }
1625}"#;
1626 assert_eq!(actual, expected);
1627 }
1628
1629 #[test]
1630 fn snapshot_full_detection_report_for_clang_plus_gnu_ar() {
1631 let cxx_id =
1636 parse_cxx_version_output("clang version 17.0.6\nTarget: x86_64-unknown-linux-gnu\n");
1637 let cxx_caps = derive_cxx_capabilities(&cxx_id);
1638 let ar_id = parse_ar_version_output("GNU ar (GNU Binutils) 2.40\n");
1639 let ar_caps = derive_ar_capabilities(&ar_id);
1640 let report = ToolchainDetectionReport {
1641 cxx: ToolDetection {
1642 path: std::path::PathBuf::from("/opt/llvm/bin/clang++"),
1643 identity: cxx_id,
1644 capabilities: cxx_caps,
1645 },
1646 cc: None,
1647 ar: ToolDetection {
1648 path: std::path::PathBuf::from("/usr/bin/ar"),
1649 identity: ar_id,
1650 capabilities: ar_caps,
1651 },
1652 };
1653 let actual = pretty(&report.as_json());
1654 let expected = r#"{
1655 "cxx": {
1656 "path": "/opt/llvm/bin/clang++",
1657 "identity": {
1658 "kind": "clang",
1659 "version": "17.0.6",
1660 "target": "x86_64-unknown-linux-gnu",
1661 "raw_version_line": "clang version 17.0.6"
1662 },
1663 "capabilities": {
1664 "color_diagnostics_flag": {
1665 "supported": true,
1666 "source": "version"
1667 },
1668 "cxx_standard_17": {
1669 "supported": true,
1670 "source": "version"
1671 },
1672 "depfile_mmd_mf": {
1673 "supported": true,
1674 "source": "version"
1675 },
1676 "gcc_style_flags": {
1677 "supported": true,
1678 "source": "version"
1679 },
1680 "json_diagnostics": {
1681 "supported": true,
1682 "source": "version"
1683 },
1684 "msvc_style_flags": {
1685 "supported": false,
1686 "source": "assumed-default"
1687 },
1688 "response_files": {
1689 "supported": true,
1690 "source": "version"
1691 },
1692 "sarif_diagnostics": {
1693 "supported": false,
1694 "source": "assumed-default"
1695 },
1696 "std_flag": {
1697 "supported": true,
1698 "source": "version"
1699 }
1700 }
1701 },
1702 "ar": {
1703 "path": "/usr/bin/ar",
1704 "identity": {
1705 "kind": "ar",
1706 "version": "2.40",
1707 "raw_version_line": "GNU ar (GNU Binutils) 2.40"
1708 },
1709 "capabilities": {
1710 "ar_crs": {
1711 "supported": true,
1712 "source": "version"
1713 },
1714 "static_library_output": {
1715 "supported": true,
1716 "source": "version"
1717 }
1718 }
1719 }
1720}"#;
1721 assert_eq!(actual, expected);
1722 }
1723}