1use std::collections::BTreeMap;
13
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::{ResolvedProfileFlags, SourceLanguage, Target, classify_source};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
23pub enum CStandard {
24 #[serde(rename = "c89")]
25 C89,
26 #[serde(rename = "c99")]
27 C99,
28 #[serde(rename = "c11")]
29 C11,
30 #[serde(rename = "c17")]
31 C17,
32 #[serde(rename = "c23")]
33 C23,
34}
35
36impl CStandard {
37 pub const ALL: [Self; 5] = [Self::C89, Self::C99, Self::C11, Self::C17, Self::C23];
38
39 pub const fn as_str(self) -> &'static str {
40 match self {
41 Self::C89 => "c89",
42 Self::C99 => "c99",
43 Self::C11 => "c11",
44 Self::C17 => "c17",
45 Self::C23 => "c23",
46 }
47 }
48
49 pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
53 Self::ALL
54 .into_iter()
55 .find(|s| s.as_str() == value)
56 .ok_or_else(|| LanguageStandardParseError {
57 language: SourceLanguage::C,
58 value: value.to_owned(),
59 })
60 }
61}
62
63impl std::fmt::Display for CStandard {
64 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65 f.write_str(self.as_str())
66 }
67}
68
69impl std::str::FromStr for CStandard {
70 type Err = LanguageStandardParseError;
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 Self::parse(s)
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
79pub enum CxxStandard {
80 #[serde(rename = "c++98")]
81 Cxx98,
82 #[serde(rename = "c++03")]
83 Cxx03,
84 #[serde(rename = "c++11")]
85 Cxx11,
86 #[serde(rename = "c++14")]
87 Cxx14,
88 #[serde(rename = "c++17")]
89 Cxx17,
90 #[serde(rename = "c++20")]
91 Cxx20,
92 #[serde(rename = "c++23")]
93 Cxx23,
94}
95
96impl CxxStandard {
97 pub const ALL: [Self; 7] = [
98 Self::Cxx98,
99 Self::Cxx03,
100 Self::Cxx11,
101 Self::Cxx14,
102 Self::Cxx17,
103 Self::Cxx20,
104 Self::Cxx23,
105 ];
106
107 pub const fn as_str(self) -> &'static str {
108 match self {
109 Self::Cxx98 => "c++98",
110 Self::Cxx03 => "c++03",
111 Self::Cxx11 => "c++11",
112 Self::Cxx14 => "c++14",
113 Self::Cxx17 => "c++17",
114 Self::Cxx20 => "c++20",
115 Self::Cxx23 => "c++23",
116 }
117 }
118
119 pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
123 Self::ALL
124 .into_iter()
125 .find(|s| s.as_str() == value)
126 .ok_or_else(|| LanguageStandardParseError {
127 language: SourceLanguage::Cxx,
128 value: value.to_owned(),
129 })
130 }
131}
132
133impl std::fmt::Display for CxxStandard {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.write_str(self.as_str())
136 }
137}
138
139impl std::str::FromStr for CxxStandard {
140 type Err = LanguageStandardParseError;
141 fn from_str(s: &str) -> Result<Self, Self::Err> {
142 Self::parse(s)
143 }
144}
145
146pub const DEFAULT_C_STANDARD: CStandard = CStandard::C11;
150pub const DEFAULT_CXX_STANDARD: CxxStandard = CxxStandard::Cxx17;
151
152#[derive(Debug, Error, Clone, PartialEq, Eq)]
155#[error(
156 "unknown {} standard `{value}`: expected one of {}",
157 .language.human_label(),
158 valid_standard_values(*.language)
159)]
160pub struct LanguageStandardParseError {
161 pub language: SourceLanguage,
162 pub value: String,
163}
164
165fn valid_standard_values(language: SourceLanguage) -> String {
166 match language {
167 SourceLanguage::C => CStandard::ALL.map(CStandard::as_str).join(", "),
168 SourceLanguage::Cxx => CxxStandard::ALL.map(CxxStandard::as_str).join(", "),
169 }
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
176pub enum LanguageStandard {
177 C(CStandard),
178 Cxx(CxxStandard),
179}
180
181impl LanguageStandard {
182 pub const fn language(self) -> SourceLanguage {
183 match self {
184 Self::C(_) => SourceLanguage::C,
185 Self::Cxx(_) => SourceLanguage::Cxx,
186 }
187 }
188
189 pub const fn as_str(self) -> &'static str {
190 match self {
191 Self::C(s) => s.as_str(),
192 Self::Cxx(s) => s.as_str(),
193 }
194 }
195
196 pub const fn msvc_spelling(self) -> Option<&'static str> {
201 match self {
202 Self::C(CStandard::C11) => Some("/std:c11"),
203 Self::C(CStandard::C17) => Some("/std:c17"),
204 Self::Cxx(CxxStandard::Cxx14) => Some("/std:c++14"),
205 Self::Cxx(CxxStandard::Cxx17) => Some("/std:c++17"),
206 Self::Cxx(CxxStandard::Cxx20) => Some("/std:c++20"),
207 _ => None,
208 }
209 }
210}
211
212impl std::fmt::Display for LanguageStandard {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 f.write_str(self.as_str())
215 }
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum StandardDeclaration<S> {
234 Declared(S),
237 Workspace,
239 Inherited(S),
242}
243
244impl<S> StandardDeclaration<S> {
245 #[must_use]
248 pub fn value(self) -> Option<S> {
249 match self {
250 Self::Declared(s) | Self::Inherited(s) => Some(s),
251 Self::Workspace => {
252 debug_assert!(
253 false,
254 "unresolved `{{ workspace = true }}` standard marker reached a consumer"
255 );
256 None
257 }
258 }
259 }
260}
261
262impl<S: Serialize> Serialize for StandardDeclaration<S> {
267 fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
268 where
269 Ser: serde::Serializer,
270 {
271 match self {
272 Self::Declared(s) | Self::Inherited(s) => s.serialize(serializer),
273 Self::Workspace => Err(serde::ser::Error::custom(
274 "unresolved `{ workspace = true }` standard marker cannot be serialized",
275 )),
276 }
277 }
278}
279
280impl<'de, S: Deserialize<'de>> Deserialize<'de> for StandardDeclaration<S> {
283 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
284 where
285 D: serde::Deserializer<'de>,
286 {
287 S::deserialize(deserializer).map(Self::Declared)
288 }
289}
290
291#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
298pub struct LanguageStandardSettings {
299 #[serde(default, skip_serializing_if = "Option::is_none")]
300 pub c_standard: Option<StandardDeclaration<CStandard>>,
301 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub cxx_standard: Option<StandardDeclaration<CxxStandard>>,
303 #[serde(default, skip_serializing_if = "Option::is_none")]
304 pub interface_c_standard: Option<StandardDeclaration<CStandard>>,
305 #[serde(default, skip_serializing_if = "Option::is_none")]
306 pub interface_cxx_standard: Option<StandardDeclaration<CxxStandard>>,
307}
308
309impl LanguageStandardSettings {
310 #[must_use]
311 pub fn is_empty(&self) -> bool {
312 self.c_standard.is_none()
313 && self.cxx_standard.is_none()
314 && self.interface_c_standard.is_none()
315 && self.interface_cxx_standard.is_none()
316 }
317
318 #[must_use]
321 pub fn c_standard_value(&self) -> Option<CStandard> {
322 self.c_standard.and_then(StandardDeclaration::value)
323 }
324
325 #[must_use]
328 pub fn cxx_standard_value(&self) -> Option<CxxStandard> {
329 self.cxx_standard.and_then(StandardDeclaration::value)
330 }
331
332 #[must_use]
334 pub fn interface_c_standard_value(&self) -> Option<CStandard> {
335 self.interface_c_standard
336 .and_then(StandardDeclaration::value)
337 }
338
339 #[must_use]
342 pub fn interface_cxx_standard_value(&self) -> Option<CxxStandard> {
343 self.interface_cxx_standard
344 .and_then(StandardDeclaration::value)
345 }
346
347 #[must_use]
350 pub fn workspace_marker_field(&self) -> Option<&'static str> {
351 if self.c_standard == Some(StandardDeclaration::Workspace) {
352 return Some("c-standard");
353 }
354 if self.cxx_standard == Some(StandardDeclaration::Workspace) {
355 return Some("cxx-standard");
356 }
357 if self.interface_c_standard == Some(StandardDeclaration::Workspace) {
358 return Some("interface-c-standard");
359 }
360 if self.interface_cxx_standard == Some(StandardDeclaration::Workspace) {
361 return Some("interface-cxx-standard");
362 }
363 None
364 }
365}
366
367#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
372pub struct WorkspaceStandardDefaults {
373 #[serde(rename = "c-standard", skip_serializing_if = "Option::is_none")]
374 pub c_standard: Option<CStandard>,
375 #[serde(rename = "cxx-standard", skip_serializing_if = "Option::is_none")]
376 pub cxx_standard: Option<CxxStandard>,
377 #[serde(
378 rename = "interface-c-standard",
379 skip_serializing_if = "Option::is_none"
380 )]
381 pub interface_c_standard: Option<CStandard>,
382 #[serde(
383 rename = "interface-cxx-standard",
384 skip_serializing_if = "Option::is_none"
385 )]
386 pub interface_cxx_standard: Option<CxxStandard>,
387}
388
389impl WorkspaceStandardDefaults {
390 #[must_use]
391 pub fn is_empty(&self) -> bool {
392 self.c_standard.is_none()
393 && self.cxx_standard.is_none()
394 && self.interface_c_standard.is_none()
395 && self.interface_cxx_standard.is_none()
396 }
397}
398
399#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
401#[serde(rename_all = "kebab-case")]
402pub enum LanguageStandardSource {
403 BuiltinDefault,
404 Package,
405 Target,
406 Workspace,
407}
408
409impl LanguageStandardSource {
410 pub const fn as_key(self) -> &'static str {
411 match self {
412 Self::BuiltinDefault => "builtin-default",
413 Self::Package => "package",
414 Self::Target => "target",
415 Self::Workspace => "workspace",
416 }
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
425#[serde(rename_all = "kebab-case")]
426pub enum InterfaceStandardSource {
427 Target,
428 Package,
429 CompileStandard,
430 Workspace,
431}
432
433impl InterfaceStandardSource {
434 pub const fn as_key(self) -> &'static str {
435 match self {
436 Self::Target => "target",
437 Self::Package => "package",
438 Self::CompileStandard => "compile-standard",
439 Self::Workspace => "workspace",
440 }
441 }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
446pub struct ResolvedStandard<S> {
447 pub standard: S,
448 pub source: LanguageStandardSource,
449}
450
451#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
453pub struct InterfaceStandard<S> {
454 pub standard: S,
455 pub source: InterfaceStandardSource,
456}
457
458#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
460pub struct ResolvedLanguageStandards {
461 pub c: ResolvedStandard<CStandard>,
462 pub cxx: ResolvedStandard<CxxStandard>,
463}
464
465impl Default for ResolvedLanguageStandards {
466 fn default() -> Self {
467 resolve_language_standards(&LanguageStandardSettings::default())
468 }
469}
470
471fn package_resolution<S: Copy>(
476 declaration: Option<StandardDeclaration<S>>,
477 default: S,
478) -> ResolvedStandard<S> {
479 match declaration {
480 Some(StandardDeclaration::Declared(standard)) => ResolvedStandard {
481 standard,
482 source: LanguageStandardSource::Package,
483 },
484 Some(StandardDeclaration::Inherited(standard)) => ResolvedStandard {
485 standard,
486 source: LanguageStandardSource::Workspace,
487 },
488 Some(StandardDeclaration::Workspace) => {
489 debug_assert!(
490 false,
491 "unresolved `{{ workspace = true }}` standard marker reached resolution"
492 );
493 ResolvedStandard {
494 standard: default,
495 source: LanguageStandardSource::BuiltinDefault,
496 }
497 }
498 None => ResolvedStandard {
499 standard: default,
500 source: LanguageStandardSource::BuiltinDefault,
501 },
502 }
503}
504
505#[must_use]
509pub fn resolve_language_standards(package: &LanguageStandardSettings) -> ResolvedLanguageStandards {
510 ResolvedLanguageStandards {
511 c: package_resolution(package.c_standard, DEFAULT_C_STANDARD),
512 cxx: package_resolution(package.cxx_standard, DEFAULT_CXX_STANDARD),
513 }
514}
515
516#[must_use]
520pub fn effective_c(
521 package: &ResolvedLanguageStandards,
522 target: &Target,
523) -> ResolvedStandard<CStandard> {
524 target
525 .language
526 .c_standard_value()
527 .map_or(package.c, |standard| ResolvedStandard {
528 standard,
529 source: LanguageStandardSource::Target,
530 })
531}
532
533#[must_use]
535pub fn effective_cxx(
536 package: &ResolvedLanguageStandards,
537 target: &Target,
538) -> ResolvedStandard<CxxStandard> {
539 target
540 .language
541 .cxx_standard_value()
542 .map_or(package.cxx, |standard| ResolvedStandard {
543 standard,
544 source: LanguageStandardSource::Target,
545 })
546}
547
548fn interface_resolution<S: Copy>(
551 declaration: Option<StandardDeclaration<S>>,
552) -> Option<InterfaceStandard<S>> {
553 match declaration {
554 Some(StandardDeclaration::Declared(standard)) => Some(InterfaceStandard {
555 standard,
556 source: InterfaceStandardSource::Package,
557 }),
558 Some(StandardDeclaration::Inherited(standard)) => Some(InterfaceStandard {
559 standard,
560 source: InterfaceStandardSource::Workspace,
561 }),
562 Some(StandardDeclaration::Workspace) => {
563 debug_assert!(
564 false,
565 "unresolved `{{ workspace = true }}` standard marker reached resolution"
566 );
567 None
568 }
569 None => None,
570 }
571}
572
573#[must_use]
578pub fn interface_c(
579 package: &ResolvedLanguageStandards,
580 package_settings: &LanguageStandardSettings,
581 target: &Target,
582) -> InterfaceStandard<CStandard> {
583 if let Some(standard) = target.language.interface_c_standard_value() {
584 return InterfaceStandard {
585 standard,
586 source: InterfaceStandardSource::Target,
587 };
588 }
589 if let Some(interface) = interface_resolution(package_settings.interface_c_standard) {
590 return interface;
591 }
592 InterfaceStandard {
593 standard: effective_c(package, target).standard,
594 source: InterfaceStandardSource::CompileStandard,
595 }
596}
597
598#[must_use]
600pub fn interface_cxx(
601 package: &ResolvedLanguageStandards,
602 package_settings: &LanguageStandardSettings,
603 target: &Target,
604) -> InterfaceStandard<CxxStandard> {
605 if let Some(standard) = target.language.interface_cxx_standard_value() {
606 return InterfaceStandard {
607 standard,
608 source: InterfaceStandardSource::Target,
609 };
610 }
611 if let Some(interface) = interface_resolution(package_settings.interface_cxx_standard) {
612 return interface;
613 }
614 InterfaceStandard {
615 standard: effective_cxx(package, target).standard,
616 source: InterfaceStandardSource::CompileStandard,
617 }
618}
619
620#[must_use]
628pub fn imposes_requirement(
629 target: &Target,
630 package_settings: &LanguageStandardSettings,
631 language: SourceLanguage,
632) -> bool {
633 let has_sources = target
634 .sources
635 .iter()
636 .any(|s| classify_source(s) == Some(language));
637 let target_declares = match language {
638 SourceLanguage::C => {
639 target.language.c_standard.is_some() || target.language.interface_c_standard.is_some()
640 }
641 SourceLanguage::Cxx => {
642 target.language.cxx_standard.is_some()
643 || target.language.interface_cxx_standard.is_some()
644 }
645 };
646 let header_only_package_interface = target.kind.is_header_only()
647 && match language {
648 SourceLanguage::C => package_settings.interface_c_standard.is_some(),
649 SourceLanguage::Cxx => package_settings.interface_cxx_standard.is_some(),
650 };
651 has_sources || target_declares || header_only_package_interface
652}
653
654pub const STANDARD_FLAG_PREFIXES: [&str; 3] = ["-std=", "--std=", "/std:"];
657
658#[derive(Debug, Error, Clone, PartialEq, Eq)]
661#[error(
662 "package `{package}` declares a first-class {} standard (`{field}`) but its `{flag_list}` also select one via `{flag}`; remove the flag, or drop the `{field}` declaration and keep the raw flag",
663 .language.human_label()
664)]
665pub struct StandardFlagConflict {
666 pub package: String,
667 pub language: SourceLanguage,
668 pub field: &'static str,
671 pub flag_list: &'static str,
674 pub flag: String,
675 pub target: Option<String>,
682}
683
684fn first_standard_token(flags: &[String]) -> Option<String> {
685 flags
686 .iter()
687 .find(|f| STANDARD_FLAG_PREFIXES.iter().any(|p| f.starts_with(p)))
688 .cloned()
689}
690
691#[must_use]
702pub fn find_standard_flag_conflicts(
703 package: &str,
704 settings: &LanguageStandardSettings,
705 targets: &[Target],
706 flags: &ResolvedProfileFlags,
707) -> Vec<StandardFlagConflict> {
708 let mut out = Vec::new();
709 let mut push = |language: SourceLanguage,
710 field: &'static str,
711 flag_list: &'static str,
712 flag: String,
713 target: Option<String>| {
714 out.push(StandardFlagConflict {
715 package: package.to_owned(),
716 language,
717 field,
718 flag_list,
719 flag,
720 target,
721 });
722 };
723 if let Some(flag) = first_standard_token(&flags.cflags) {
724 if settings.c_standard.is_some() {
725 push(
726 SourceLanguage::C,
727 "c-standard",
728 "cflags",
729 flag.clone(),
730 None,
731 );
732 } else {
733 for target in targets {
734 if target.language.c_standard.is_some() {
735 push(
736 SourceLanguage::C,
737 "c-standard",
738 "cflags",
739 flag.clone(),
740 Some(target.name.as_str().to_owned()),
741 );
742 }
743 }
744 }
745 }
746 if let Some(flag) = first_standard_token(&flags.cxxflags) {
747 if settings.cxx_standard.is_some() {
748 push(
749 SourceLanguage::Cxx,
750 "cxx-standard",
751 "cxxflags",
752 flag.clone(),
753 None,
754 );
755 } else {
756 for target in targets {
757 if target.language.cxx_standard.is_some() {
758 push(
759 SourceLanguage::Cxx,
760 "cxx-standard",
761 "cxxflags",
762 flag.clone(),
763 Some(target.name.as_str().to_owned()),
764 );
765 }
766 }
767 }
768 }
769 out
770}
771
772#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778pub struct LanguageStandardsSummary {
779 pub c: ResolvedStandard<CStandard>,
780 pub cxx: ResolvedStandard<CxxStandard>,
781 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
782 pub targets: BTreeMap<String, TargetStandardsSummary>,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
788pub struct TargetStandardsSummary {
789 pub c: ResolvedStandard<CStandard>,
790 pub cxx: ResolvedStandard<CxxStandard>,
791 #[serde(default, skip_serializing_if = "Option::is_none")]
792 pub interface_c: Option<InterfaceStandard<CStandard>>,
793 #[serde(default, skip_serializing_if = "Option::is_none")]
794 pub interface_cxx: Option<InterfaceStandard<CxxStandard>>,
795}
796
797impl Default for LanguageStandardsSummary {
798 fn default() -> Self {
799 let resolved = ResolvedLanguageStandards::default();
800 Self {
801 c: resolved.c,
802 cxx: resolved.cxx,
803 targets: BTreeMap::new(),
804 }
805 }
806}
807
808impl LanguageStandardsSummary {
809 #[must_use]
811 pub fn from_package(package: &crate::Package) -> Self {
812 let resolved = resolve_language_standards(&package.language);
813 let targets = package
814 .targets
815 .iter()
816 .map(|target| {
817 let library_like = target.kind.produces_archive() || target.kind.is_header_only();
818 let summary = TargetStandardsSummary {
819 c: effective_c(&resolved, target),
820 cxx: effective_cxx(&resolved, target),
821 interface_c: library_like
822 .then(|| interface_c(&resolved, &package.language, target)),
823 interface_cxx: library_like
824 .then(|| interface_cxx(&resolved, &package.language, target)),
825 };
826 (target.name.as_str().to_owned(), summary)
827 })
828 .collect();
829 Self {
830 c: resolved.c,
831 cxx: resolved.cxx,
832 targets,
833 }
834 }
835
836 #[must_use]
840 pub fn fingerprint_lines(&self) -> Vec<String> {
841 let mut lines = vec![
842 format!("c={}", self.c.standard),
843 format!("cxx={}", self.cxx.standard),
844 ];
845 for (name, target) in &self.targets {
846 lines.push(format!("target={name}"));
847 lines.push(format!("c={}", target.c.standard));
848 lines.push(format!("cxx={}", target.cxx.standard));
849 if let Some(interface) = &target.interface_c {
850 lines.push(format!("interface-c={}", interface.standard));
851 }
852 if let Some(interface) = &target.interface_cxx {
853 lines.push(format!("interface-cxx={}", interface.standard));
854 }
855 }
856 lines
857 }
858}
859
860#[cfg(test)]
861mod tests {
862 use super::*;
863 use crate::{TargetKind, TargetName};
864 use camino::Utf8PathBuf;
865
866 fn target(kind: TargetKind, sources: &[&str], language: LanguageStandardSettings) -> Target {
867 Target {
868 name: TargetName::new("t").unwrap(),
869 kind,
870 sources: sources.iter().map(Utf8PathBuf::from).collect(),
871 include_dirs: Vec::new(),
872 defines: Vec::new(),
873 deps: Vec::new(),
874 language,
875 }
876 }
877
878 #[test]
879 fn standards_parse_round_trip_and_reject_unknown_values() {
880 for s in CStandard::ALL {
881 assert_eq!(CStandard::parse(s.as_str()).unwrap(), s);
882 }
883 for s in CxxStandard::ALL {
884 assert_eq!(CxxStandard::parse(s.as_str()).unwrap(), s);
885 }
886 let err = CStandard::parse("c++17").unwrap_err();
887 assert!(
888 err.to_string()
889 .contains("expected one of c89, c99, c11, c17, c23"),
890 "unexpected message: {err}"
891 );
892 let err = CxxStandard::parse("c++26").unwrap_err();
893 assert!(
894 err.to_string().contains("c++23"),
895 "unexpected message: {err}"
896 );
897 assert!(
898 err.to_string().contains("unknown C++ standard `c++26`"),
899 "unexpected message: {err}"
900 );
901 }
902
903 #[test]
904 fn standards_order_chronologically() {
905 assert!(CStandard::C99 < CStandard::C23);
906 assert!(CStandard::C11 < CStandard::C17);
907 assert!(CxxStandard::Cxx14 < CxxStandard::Cxx20);
908 assert!(CxxStandard::Cxx98 < CxxStandard::Cxx03);
909 }
910
911 #[test]
912 fn msvc_spellings_cover_exactly_the_stable_flags() {
913 assert_eq!(
914 LanguageStandard::Cxx(CxxStandard::Cxx20).msvc_spelling(),
915 Some("/std:c++20")
916 );
917 assert_eq!(
918 LanguageStandard::C(CStandard::C17).msvc_spelling(),
919 Some("/std:c17")
920 );
921 assert_eq!(LanguageStandard::C(CStandard::C99).msvc_spelling(), None);
922 assert_eq!(
923 LanguageStandard::Cxx(CxxStandard::Cxx23).msvc_spelling(),
924 None
925 );
926 assert_eq!(
927 LanguageStandard::Cxx(CxxStandard::Cxx11).msvc_spelling(),
928 None
929 );
930 }
931
932 #[test]
933 fn effective_standard_prefers_target_then_package_then_builtin() {
934 let undeclared = resolve_language_standards(&LanguageStandardSettings::default());
935 let plain = target(
936 TargetKind::Executable,
937 &["a.cc"],
938 LanguageStandardSettings::default(),
939 );
940 let effective = effective_cxx(&undeclared, &plain);
941 assert_eq!(effective.standard, CxxStandard::Cxx17);
942 assert_eq!(effective.source, LanguageStandardSource::BuiltinDefault);
943
944 let package = resolve_language_standards(&LanguageStandardSettings {
945 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx14)),
946 ..Default::default()
947 });
948 let effective = effective_cxx(&package, &plain);
949 assert_eq!(effective.standard, CxxStandard::Cxx14);
950 assert_eq!(effective.source, LanguageStandardSource::Package);
951
952 let overridden = target(
953 TargetKind::Executable,
954 &["a.cc"],
955 LanguageStandardSettings {
956 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
957 ..Default::default()
958 },
959 );
960 let effective = effective_cxx(&package, &overridden);
961 assert_eq!(effective.standard, CxxStandard::Cxx20);
962 assert_eq!(effective.source, LanguageStandardSource::Target);
963 }
964
965 #[test]
966 fn interface_standard_falls_back_to_effective_compile_standard() {
967 let package_settings = LanguageStandardSettings {
968 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
969 ..Default::default()
970 };
971 let resolved = resolve_language_standards(&package_settings);
972 let lib = target(
973 TargetKind::Library,
974 &["a.cc"],
975 LanguageStandardSettings::default(),
976 );
977 let interface = interface_cxx(&resolved, &package_settings, &lib);
978 assert_eq!(interface.standard, CxxStandard::Cxx20);
979 assert_eq!(interface.source, InterfaceStandardSource::CompileStandard);
980
981 let package_interface = LanguageStandardSettings {
982 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
983 interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
984 ..Default::default()
985 };
986 let resolved = resolve_language_standards(&package_interface);
987 let interface = interface_cxx(&resolved, &package_interface, &lib);
988 assert_eq!(interface.standard, CxxStandard::Cxx17);
989 assert_eq!(interface.source, InterfaceStandardSource::Package);
990
991 let lib_override = target(
992 TargetKind::Library,
993 &["a.cc"],
994 LanguageStandardSettings {
995 interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx14)),
996 ..Default::default()
997 },
998 );
999 let interface = interface_cxx(&resolved, &package_interface, &lib_override);
1000 assert_eq!(interface.standard, CxxStandard::Cxx14);
1001 assert_eq!(interface.source, InterfaceStandardSource::Target);
1002 }
1003
1004 #[test]
1005 fn imposes_requirement_relevance_rules() {
1006 let none = LanguageStandardSettings::default();
1007 let c_lib = target(
1009 TargetKind::Library,
1010 &["a.c"],
1011 LanguageStandardSettings::default(),
1012 );
1013 assert!(imposes_requirement(&c_lib, &none, SourceLanguage::C));
1014 assert!(!imposes_requirement(&c_lib, &none, SourceLanguage::Cxx));
1015
1016 let package_impl = LanguageStandardSettings {
1019 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1020 ..Default::default()
1021 };
1022 assert!(!imposes_requirement(
1023 &c_lib,
1024 &package_impl,
1025 SourceLanguage::Cxx
1026 ));
1027
1028 let declared = target(
1030 TargetKind::Library,
1031 &["a.c"],
1032 LanguageStandardSettings {
1033 interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1034 ..Default::default()
1035 },
1036 );
1037 assert!(imposes_requirement(&declared, &none, SourceLanguage::Cxx));
1038
1039 let header_only = target(
1041 TargetKind::HeaderOnly,
1042 &[],
1043 LanguageStandardSettings::default(),
1044 );
1045 assert!(!imposes_requirement(
1046 &header_only,
1047 &none,
1048 SourceLanguage::Cxx
1049 ));
1050 let package_interface = LanguageStandardSettings {
1051 interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1052 ..Default::default()
1053 };
1054 assert!(imposes_requirement(
1055 &header_only,
1056 &package_interface,
1057 SourceLanguage::Cxx
1058 ));
1059 assert!(!imposes_requirement(
1061 &header_only,
1062 &package_impl,
1063 SourceLanguage::Cxx
1064 ));
1065 }
1066
1067 #[test]
1068 fn conflict_fires_only_for_declared_language_and_matching_bucket() {
1069 let flags = ResolvedProfileFlags {
1070 cxxflags: vec!["-std=c++14".to_owned()],
1071 ..Default::default()
1072 };
1073 let declared_cxx = LanguageStandardSettings {
1074 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1075 ..Default::default()
1076 };
1077
1078 assert!(
1080 find_standard_flag_conflicts("p", &LanguageStandardSettings::default(), &[], &flags)
1081 .is_empty()
1082 );
1083
1084 let conflicts = find_standard_flag_conflicts("p", &declared_cxx, &[], &flags);
1087 let conflict = conflicts.first().unwrap();
1088 assert_eq!(conflicts.len(), 1);
1089 assert_eq!(conflict.language, SourceLanguage::Cxx);
1090 assert_eq!(conflict.flag, "-std=c++14");
1091 assert_eq!(conflict.field, "cxx-standard");
1092 assert_eq!(conflict.target, None);
1093 assert!(conflict.to_string().contains("cxx-standard"));
1094
1095 let c_only_flags = ResolvedProfileFlags {
1097 cflags: vec!["-std=c99".to_owned()],
1098 ..Default::default()
1099 };
1100 assert!(find_standard_flag_conflicts("p", &declared_cxx, &[], &c_only_flags).is_empty());
1101
1102 let t = target(
1104 TargetKind::Executable,
1105 &["a.c"],
1106 LanguageStandardSettings {
1107 c_standard: Some(StandardDeclaration::Declared(CStandard::C17)),
1108 ..Default::default()
1109 },
1110 );
1111 let conflicts = find_standard_flag_conflicts(
1112 "p",
1113 &LanguageStandardSettings::default(),
1114 std::slice::from_ref(&t),
1115 &c_only_flags,
1116 );
1117 let conflict = conflicts.first().unwrap();
1118 assert_eq!(conflict.language, SourceLanguage::C);
1119 assert_eq!(conflict.flag_list, "cflags");
1120 assert_eq!(conflict.target.as_deref(), Some("t"));
1124
1125 let msvc_flags = ResolvedProfileFlags {
1127 cxxflags: vec!["/std:c++latest".to_owned()],
1128 ..Default::default()
1129 };
1130 assert!(!find_standard_flag_conflicts("p", &declared_cxx, &[], &msvc_flags).is_empty());
1131 }
1132
1133 #[test]
1134 fn summary_lists_every_target_with_interface_only_for_library_like() {
1135 use crate::{Package, PackageName};
1136 let package = Package::new(
1137 PackageName::new("demo").unwrap(),
1138 semver::Version::parse("0.1.0").unwrap(),
1139 vec![
1140 target(
1141 TargetKind::Executable,
1142 &["main.cc"],
1143 LanguageStandardSettings::default(),
1144 ),
1145 Target {
1146 name: TargetName::new("core").unwrap(),
1147 kind: TargetKind::Library,
1148 sources: vec![Utf8PathBuf::from("core.cc")],
1149 include_dirs: Vec::new(),
1150 defines: Vec::new(),
1151 deps: Vec::new(),
1152 language: LanguageStandardSettings {
1153 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1154 interface_cxx_standard: Some(StandardDeclaration::Declared(
1155 CxxStandard::Cxx17,
1156 )),
1157 ..Default::default()
1158 },
1159 },
1160 ],
1161 Vec::new(),
1162 )
1163 .unwrap();
1164 let summary = LanguageStandardsSummary::from_package(&package);
1165 assert_eq!(summary.cxx.standard, CxxStandard::Cxx17);
1166 assert_eq!(summary.targets.len(), 2);
1167 let exe = &summary.targets["t"];
1168 assert!(exe.interface_c.is_none() && exe.interface_cxx.is_none());
1169 let core = &summary.targets["core"];
1170 assert_eq!(core.cxx.standard, CxxStandard::Cxx20);
1171 assert_eq!(core.interface_cxx.unwrap().standard, CxxStandard::Cxx17);
1172 assert_eq!(
1173 core.interface_c.unwrap().source,
1174 InterfaceStandardSource::CompileStandard
1175 );
1176 }
1177
1178 #[test]
1179 fn package_level_interface_fields_are_inert_without_library_like_targets() {
1180 use crate::{Package, PackageName};
1181 let package = Package::new(
1186 PackageName::new("demo").unwrap(),
1187 semver::Version::parse("0.1.0").unwrap(),
1188 vec![target(
1189 TargetKind::Executable,
1190 &["main.cc"],
1191 LanguageStandardSettings::default(),
1192 )],
1193 Vec::new(),
1194 )
1195 .unwrap()
1196 .with_language(LanguageStandardSettings {
1197 interface_c_standard: Some(StandardDeclaration::Declared(CStandard::C17)),
1198 interface_cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1199 ..Default::default()
1200 });
1201 let summary = LanguageStandardsSummary::from_package(&package);
1202 let exe = &summary.targets["t"];
1203 assert!(
1204 exe.interface_c.is_none() && exe.interface_cxx.is_none(),
1205 "package-level interface fields must stay inert on executables"
1206 );
1207 }
1208
1209 #[test]
1210 fn fingerprint_lines_are_values_only_and_deterministic() {
1211 let mut summary = LanguageStandardsSummary::default();
1212 let lines = summary.fingerprint_lines();
1213 assert_eq!(lines, vec!["c=c11".to_owned(), "cxx=c++17".to_owned()]);
1214
1215 summary.cxx = ResolvedStandard {
1217 standard: CxxStandard::Cxx17,
1218 source: LanguageStandardSource::Package,
1219 };
1220 assert_eq!(summary.fingerprint_lines(), lines);
1221
1222 summary.targets.insert(
1223 "core".to_owned(),
1224 TargetStandardsSummary {
1225 c: summary.c,
1226 cxx: ResolvedStandard {
1227 standard: CxxStandard::Cxx20,
1228 source: LanguageStandardSource::Target,
1229 },
1230 interface_c: None,
1231 interface_cxx: Some(InterfaceStandard {
1232 standard: CxxStandard::Cxx17,
1233 source: InterfaceStandardSource::Target,
1234 }),
1235 },
1236 );
1237 assert_eq!(
1238 summary.fingerprint_lines(),
1239 vec![
1240 "c=c11".to_owned(),
1241 "cxx=c++17".to_owned(),
1242 "target=core".to_owned(),
1243 "c=c11".to_owned(),
1244 "cxx=c++20".to_owned(),
1245 "interface-cxx=c++17".to_owned(),
1246 ]
1247 );
1248 }
1249
1250 #[test]
1251 fn standard_declaration_serde_is_a_bare_string_and_rejects_markers() {
1252 let declared: StandardDeclaration<CxxStandard> =
1253 StandardDeclaration::Declared(CxxStandard::Cxx20);
1254 let inherited: StandardDeclaration<CxxStandard> =
1255 StandardDeclaration::Inherited(CxxStandard::Cxx20);
1256 assert_eq!(serde_json::to_string(&declared).unwrap(), "\"c++20\"");
1257 assert_eq!(serde_json::to_string(&inherited).unwrap(), "\"c++20\"");
1258 let marker: StandardDeclaration<CxxStandard> = StandardDeclaration::Workspace;
1259 assert!(serde_json::to_string(&marker).is_err());
1260 let parsed: StandardDeclaration<CxxStandard> = serde_json::from_str("\"c++20\"").unwrap();
1261 assert_eq!(parsed, StandardDeclaration::Declared(CxxStandard::Cxx20));
1262 }
1263
1264 #[test]
1265 fn inherited_standard_resolves_with_workspace_source() {
1266 let settings = LanguageStandardSettings {
1267 cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx20)),
1268 ..Default::default()
1269 };
1270 let resolved = resolve_language_standards(&settings);
1271 assert_eq!(resolved.cxx.standard, CxxStandard::Cxx20);
1272 assert_eq!(resolved.cxx.source, LanguageStandardSource::Workspace);
1273 assert_eq!(resolved.c.source, LanguageStandardSource::BuiltinDefault);
1274 }
1275
1276 #[test]
1277 fn inherited_interface_standard_resolves_with_workspace_source() {
1278 let settings = LanguageStandardSettings {
1279 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1280 interface_cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx17)),
1281 ..Default::default()
1282 };
1283 let resolved = resolve_language_standards(&settings);
1284 let lib = target(
1285 TargetKind::Library,
1286 &["a.cc"],
1287 LanguageStandardSettings::default(),
1288 );
1289 let interface = interface_cxx(&resolved, &settings, &lib);
1290 assert_eq!(interface.standard, CxxStandard::Cxx17);
1291 assert_eq!(interface.source, InterfaceStandardSource::Workspace);
1292 }
1293
1294 #[test]
1295 fn inherited_values_behave_like_declarations_for_conflicts_and_relevance() {
1296 let flags = ResolvedProfileFlags {
1297 cxxflags: vec!["-std=gnu++20".to_owned()],
1298 ..Default::default()
1299 };
1300 let inherited = LanguageStandardSettings {
1301 cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx17)),
1302 ..Default::default()
1303 };
1304 assert_eq!(
1305 find_standard_flag_conflicts("p", &inherited, &[], &flags).len(),
1306 1
1307 );
1308
1309 let header_only = target(
1310 TargetKind::HeaderOnly,
1311 &[],
1312 LanguageStandardSettings::default(),
1313 );
1314 let pkg = LanguageStandardSettings {
1315 interface_cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx20)),
1316 ..Default::default()
1317 };
1318 assert!(imposes_requirement(&header_only, &pkg, SourceLanguage::Cxx));
1319 }
1320
1321 #[test]
1322 fn fingerprint_is_identical_for_declared_and_inherited_values() {
1323 use crate::{Package, PackageName};
1324 let make = |decl: StandardDeclaration<CxxStandard>| {
1325 let package = Package::new(
1326 PackageName::new("demo").unwrap(),
1327 semver::Version::parse("0.1.0").unwrap(),
1328 vec![target(
1329 TargetKind::Executable,
1330 &["main.cc"],
1331 LanguageStandardSettings::default(),
1332 )],
1333 Vec::new(),
1334 )
1335 .unwrap()
1336 .with_language(LanguageStandardSettings {
1337 cxx_standard: Some(decl),
1338 ..Default::default()
1339 });
1340 LanguageStandardsSummary::from_package(&package).fingerprint_lines()
1341 };
1342 assert_eq!(
1343 make(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1344 make(StandardDeclaration::Inherited(CxxStandard::Cxx20))
1345 );
1346 }
1347
1348 #[test]
1349 fn workspace_marker_field_reports_the_first_marker() {
1350 assert_eq!(
1351 LanguageStandardSettings::default().workspace_marker_field(),
1352 None
1353 );
1354 let settings = LanguageStandardSettings {
1355 interface_c_standard: Some(StandardDeclaration::Workspace),
1356 ..Default::default()
1357 };
1358 assert_eq!(
1359 settings.workspace_marker_field(),
1360 Some("interface-c-standard")
1361 );
1362 let settings = LanguageStandardSettings {
1363 c_standard: Some(StandardDeclaration::Workspace),
1364 interface_c_standard: Some(StandardDeclaration::Workspace),
1365 ..Default::default()
1366 };
1367 assert_eq!(settings.workspace_marker_field(), Some("c-standard"));
1368 }
1369}