1use std::collections::BTreeMap;
16use std::marker::PhantomData;
17
18use serde::de::{MapAccess, Visitor, value::MapAccessDeserializer};
19use serde::{Deserialize, Serialize};
20use thiserror::Error;
21
22use crate::{ResolvedProfileFlags, SourceLanguage, Target, classify_source};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
34pub enum CStandard {
35 #[serde(rename = "c89")]
36 C89,
37 #[serde(rename = "c99")]
38 C99,
39 #[serde(rename = "c11")]
40 C11,
41 #[serde(rename = "c17")]
42 C17,
43 #[serde(rename = "c23")]
44 C23,
45}
46
47impl CStandard {
48 pub const ALL: [Self; 5] = [Self::C89, Self::C99, Self::C11, Self::C17, Self::C23];
49
50 pub const fn as_str(self) -> &'static str {
51 match self {
52 Self::C89 => "c89",
53 Self::C99 => "c99",
54 Self::C11 => "c11",
55 Self::C17 => "c17",
56 Self::C23 => "c23",
57 }
58 }
59
60 pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
67 reject_non_identifier(SourceLanguage::C, value)?;
68 let normalized = if value == "c90" { "c89" } else { value };
69 Self::ALL
70 .into_iter()
71 .find(|s| s.as_str() == normalized)
72 .ok_or_else(|| LanguageStandardParseError::Unknown {
73 language: SourceLanguage::C,
74 value: value.to_owned(),
75 })
76 }
77}
78
79impl std::fmt::Display for CStandard {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 f.write_str(self.as_str())
82 }
83}
84
85impl std::str::FromStr for CStandard {
86 type Err = LanguageStandardParseError;
87 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 Self::parse(s)
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
101pub enum CxxStandard {
102 #[serde(rename = "c++98")]
103 Cxx98,
104 #[serde(rename = "c++11")]
105 Cxx11,
106 #[serde(rename = "c++14")]
107 Cxx14,
108 #[serde(rename = "c++17")]
109 Cxx17,
110 #[serde(rename = "c++20")]
111 Cxx20,
112 #[serde(rename = "c++23")]
113 Cxx23,
114 #[serde(rename = "c++26")]
115 Cxx26,
116}
117
118impl CxxStandard {
119 pub const ALL: [Self; 7] = [
120 Self::Cxx98,
121 Self::Cxx11,
122 Self::Cxx14,
123 Self::Cxx17,
124 Self::Cxx20,
125 Self::Cxx23,
126 Self::Cxx26,
127 ];
128
129 pub const fn as_str(self) -> &'static str {
130 match self {
131 Self::Cxx98 => "c++98",
132 Self::Cxx11 => "c++11",
133 Self::Cxx14 => "c++14",
134 Self::Cxx17 => "c++17",
135 Self::Cxx20 => "c++20",
136 Self::Cxx23 => "c++23",
137 Self::Cxx26 => "c++26",
138 }
139 }
140
141 pub fn parse(value: &str) -> Result<Self, LanguageStandardParseError> {
149 reject_non_identifier(SourceLanguage::Cxx, value)?;
150 let normalized = if value == "c++03" { "c++98" } else { value };
151 Self::ALL
152 .into_iter()
153 .find(|s| s.as_str() == normalized)
154 .ok_or_else(|| LanguageStandardParseError::Unknown {
155 language: SourceLanguage::Cxx,
156 value: value.to_owned(),
157 })
158 }
159}
160
161impl std::fmt::Display for CxxStandard {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.write_str(self.as_str())
164 }
165}
166
167impl std::str::FromStr for CxxStandard {
168 type Err = LanguageStandardParseError;
169 fn from_str(s: &str) -> Result<Self, Self::Err> {
170 Self::parse(s)
171 }
172}
173
174fn reject_non_identifier(
178 language: SourceLanguage,
179 value: &str,
180) -> Result<(), LanguageStandardParseError> {
181 if value.contains(['>', '<', ',']) {
183 return Err(LanguageStandardParseError::RangeReserved {
184 language,
185 value: value.to_owned(),
186 });
187 }
188 if value == "none" {
189 return Err(LanguageStandardParseError::NoneOnImplementation { language });
190 }
191 Ok(())
192}
193
194#[derive(Debug, Error, Clone, PartialEq, Eq)]
199pub enum LanguageStandardParseError {
200 #[error(
201 "unknown {} standard `{value}`: expected one of {}",
202 .language.human_label(),
203 valid_standard_values(*.language)
204 )]
205 Unknown {
206 language: SourceLanguage,
207 value: String,
208 },
209 #[error(
210 "range requirement `{value}` is reserved for a future version of Cabin; declare a single {} standard",
211 .language.human_label()
212 )]
213 RangeReserved {
214 language: SourceLanguage,
215 value: String,
216 },
217 #[error(
218 "`none` is only valid on `interface-c-standard` / `interface-cxx-standard`, where it marks the target's headers as not consumable from that language; compiled {} sources need a concrete standard",
219 .language.human_label()
220 )]
221 NoneOnImplementation { language: SourceLanguage },
222}
223
224fn valid_standard_values(language: SourceLanguage) -> String {
225 match language {
226 SourceLanguage::C => CStandard::ALL.map(CStandard::as_str).join(", "),
227 SourceLanguage::Cxx => CxxStandard::ALL.map(CxxStandard::as_str).join(", "),
228 }
229}
230
231const fn implementation_field(language: SourceLanguage) -> &'static str {
234 match language {
235 SourceLanguage::C => "c-standard",
236 SourceLanguage::Cxx => "cxx-standard",
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub enum LanguageStandard {
245 C(CStandard),
246 Cxx(CxxStandard),
247}
248
249impl LanguageStandard {
250 pub const fn language(self) -> SourceLanguage {
251 match self {
252 Self::C(_) => SourceLanguage::C,
253 Self::Cxx(_) => SourceLanguage::Cxx,
254 }
255 }
256
257 pub const fn as_str(self) -> &'static str {
258 match self {
259 Self::C(s) => s.as_str(),
260 Self::Cxx(s) => s.as_str(),
261 }
262 }
263
264 pub const fn msvc_spelling(self) -> Option<&'static str> {
269 match self {
270 Self::C(CStandard::C11) => Some("/std:c11"),
271 Self::C(CStandard::C17) => Some("/std:c17"),
272 Self::Cxx(CxxStandard::Cxx14) => Some("/std:c++14"),
273 Self::Cxx(CxxStandard::Cxx17) => Some("/std:c++17"),
274 Self::Cxx(CxxStandard::Cxx20) => Some("/std:c++20"),
275 _ => None,
276 }
277 }
278}
279
280impl std::fmt::Display for LanguageStandard {
281 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282 f.write_str(self.as_str())
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
298#[serde(deny_unknown_fields)]
299pub struct StandardRequirement<S> {
300 pub min: S,
301 #[serde(default = "none")]
302 pub max: Option<S>,
303}
304
305fn none<S>() -> Option<S> {
308 None
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319pub enum InterfaceRequirement<S> {
320 None,
325 Requirement(StandardRequirement<S>),
326}
327
328impl<S> InterfaceRequirement<S> {
329 #[must_use]
331 pub fn min(self) -> Option<S> {
332 match self {
333 Self::None => None,
334 Self::Requirement(requirement) => Some(requirement.min),
335 }
336 }
337}
338
339pub fn parse_interface_c(
345 value: &str,
346) -> Result<InterfaceRequirement<CStandard>, LanguageStandardParseError> {
347 if value == "none" {
348 return Ok(InterfaceRequirement::None);
349 }
350 CStandard::parse(value)
351 .map(|min| InterfaceRequirement::Requirement(StandardRequirement { min, max: None }))
352}
353
354pub fn parse_interface_cxx(
360 value: &str,
361) -> Result<InterfaceRequirement<CxxStandard>, LanguageStandardParseError> {
362 if value == "none" {
363 return Ok(InterfaceRequirement::None);
364 }
365 CxxStandard::parse(value)
366 .map(|min| InterfaceRequirement::Requirement(StandardRequirement { min, max: None }))
367}
368
369impl<S: std::fmt::Display> std::fmt::Display for InterfaceRequirement<S> {
370 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371 match self {
372 Self::None => f.write_str("none"),
373 Self::Requirement(StandardRequirement { min, max: None }) => min.fmt(f),
374 Self::Requirement(StandardRequirement {
375 min,
376 max: Some(max),
377 }) => write!(f, "{min}..{max}"),
378 }
379 }
380}
381
382impl<S: Serialize> Serialize for InterfaceRequirement<S> {
387 fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
388 where
389 Ser: serde::Serializer,
390 {
391 match self {
392 Self::None => serializer.serialize_str("none"),
393 Self::Requirement(requirement) => requirement.serialize(serializer),
394 }
395 }
396}
397
398impl<'de, S: Deserialize<'de>> Deserialize<'de> for InterfaceRequirement<S> {
399 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
400 where
401 D: serde::Deserializer<'de>,
402 {
403 struct InterfaceRequirementVisitor<S>(PhantomData<S>);
404
405 impl<'de, S: Deserialize<'de>> Visitor<'de> for InterfaceRequirementVisitor<S> {
406 type Value = InterfaceRequirement<S>;
407
408 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 f.write_str("`none` or a `{ min, max }` requirement table")
410 }
411
412 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
413 where
414 E: serde::de::Error,
415 {
416 if v == "none" {
417 Ok(InterfaceRequirement::None)
418 } else {
419 Err(E::invalid_value(serde::de::Unexpected::Str(v), &self))
420 }
421 }
422
423 fn visit_map<M>(self, map: M) -> Result<Self::Value, M::Error>
424 where
425 M: MapAccess<'de>,
426 {
427 StandardRequirement::deserialize(MapAccessDeserializer::new(map))
428 .map(InterfaceRequirement::Requirement)
429 }
430 }
431
432 deserializer.deserialize_any(InterfaceRequirementVisitor(PhantomData))
433 }
434}
435
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub enum StandardDeclaration<S> {
452 Declared(S),
455 Workspace,
457 Inherited(S),
460}
461
462impl<S> StandardDeclaration<S> {
463 #[must_use]
466 pub fn value(self) -> Option<S> {
467 match self {
468 Self::Declared(s) | Self::Inherited(s) => Some(s),
469 Self::Workspace => {
470 debug_assert!(
471 false,
472 "unresolved `{{ workspace = true }}` standard marker reached a consumer"
473 );
474 None
475 }
476 }
477 }
478}
479
480impl<S: Serialize> Serialize for StandardDeclaration<S> {
485 fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
486 where
487 Ser: serde::Serializer,
488 {
489 match self {
490 Self::Declared(s) | Self::Inherited(s) => s.serialize(serializer),
491 Self::Workspace => Err(serde::ser::Error::custom(
492 "unresolved `{ workspace = true }` standard marker cannot be serialized",
493 )),
494 }
495 }
496}
497
498impl<'de, S: Deserialize<'de>> Deserialize<'de> for StandardDeclaration<S> {
501 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
502 where
503 D: serde::Deserializer<'de>,
504 {
505 S::deserialize(deserializer).map(Self::Declared)
506 }
507}
508
509#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
520pub struct LanguageStandardSettings {
521 #[serde(default, skip_serializing_if = "Option::is_none")]
522 pub c_standard: Option<StandardDeclaration<CStandard>>,
523 #[serde(default, skip_serializing_if = "Option::is_none")]
524 pub cxx_standard: Option<StandardDeclaration<CxxStandard>>,
525 #[serde(default, skip_serializing_if = "Option::is_none")]
526 pub interface_c_standard: Option<StandardDeclaration<InterfaceRequirement<CStandard>>>,
527 #[serde(default, skip_serializing_if = "Option::is_none")]
528 pub interface_cxx_standard: Option<StandardDeclaration<InterfaceRequirement<CxxStandard>>>,
529 #[serde(default, skip_serializing_if = "Option::is_none")]
530 pub gnu_extensions: Option<bool>,
531}
532
533impl LanguageStandardSettings {
534 #[must_use]
535 pub fn is_empty(&self) -> bool {
536 self.c_standard.is_none()
537 && self.cxx_standard.is_none()
538 && self.interface_c_standard.is_none()
539 && self.interface_cxx_standard.is_none()
540 && self.gnu_extensions.is_none()
541 }
542
543 #[must_use]
546 pub fn c_standard_value(&self) -> Option<CStandard> {
547 self.c_standard.and_then(StandardDeclaration::value)
548 }
549
550 #[must_use]
553 pub fn cxx_standard_value(&self) -> Option<CxxStandard> {
554 self.cxx_standard.and_then(StandardDeclaration::value)
555 }
556
557 #[must_use]
559 pub fn interface_c_standard_value(&self) -> Option<InterfaceRequirement<CStandard>> {
560 self.interface_c_standard
561 .and_then(StandardDeclaration::value)
562 }
563
564 #[must_use]
567 pub fn interface_cxx_standard_value(&self) -> Option<InterfaceRequirement<CxxStandard>> {
568 self.interface_cxx_standard
569 .and_then(StandardDeclaration::value)
570 }
571
572 #[must_use]
575 pub fn workspace_marker_field(&self) -> Option<&'static str> {
576 if self.c_standard == Some(StandardDeclaration::Workspace) {
577 return Some("c-standard");
578 }
579 if self.cxx_standard == Some(StandardDeclaration::Workspace) {
580 return Some("cxx-standard");
581 }
582 if self.interface_c_standard == Some(StandardDeclaration::Workspace) {
583 return Some("interface-c-standard");
584 }
585 if self.interface_cxx_standard == Some(StandardDeclaration::Workspace) {
586 return Some("interface-cxx-standard");
587 }
588 None
589 }
590}
591
592#[must_use]
595pub fn effective_gnu_extensions(package: &LanguageStandardSettings, target: &Target) -> bool {
596 target
597 .language
598 .gnu_extensions
599 .or(package.gnu_extensions)
600 .unwrap_or(false)
601}
602
603#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
608pub struct WorkspaceStandardDefaults {
609 #[serde(rename = "c-standard", skip_serializing_if = "Option::is_none")]
610 pub c_standard: Option<CStandard>,
611 #[serde(rename = "cxx-standard", skip_serializing_if = "Option::is_none")]
612 pub cxx_standard: Option<CxxStandard>,
613 #[serde(
614 rename = "interface-c-standard",
615 skip_serializing_if = "Option::is_none"
616 )]
617 pub interface_c_standard: Option<InterfaceRequirement<CStandard>>,
618 #[serde(
619 rename = "interface-cxx-standard",
620 skip_serializing_if = "Option::is_none"
621 )]
622 pub interface_cxx_standard: Option<InterfaceRequirement<CxxStandard>>,
623}
624
625impl WorkspaceStandardDefaults {
626 #[must_use]
627 pub fn is_empty(&self) -> bool {
628 self.c_standard.is_none()
629 && self.cxx_standard.is_none()
630 && self.interface_c_standard.is_none()
631 && self.interface_cxx_standard.is_none()
632 }
633}
634
635#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
637#[serde(rename_all = "kebab-case")]
638pub enum LanguageStandardSource {
639 Package,
640 Target,
641 Workspace,
642}
643
644impl LanguageStandardSource {
645 pub const fn as_key(self) -> &'static str {
646 match self {
647 Self::Package => "package",
648 Self::Target => "target",
649 Self::Workspace => "workspace",
650 }
651 }
652}
653
654#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
659#[serde(rename_all = "kebab-case")]
660pub enum InterfaceStandardSource {
661 Target,
662 Package,
663 CompileStandard,
664 Workspace,
665}
666
667impl InterfaceStandardSource {
668 pub const fn as_key(self) -> &'static str {
669 match self {
670 Self::Target => "target",
671 Self::Package => "package",
672 Self::CompileStandard => "compile-standard",
673 Self::Workspace => "workspace",
674 }
675 }
676}
677
678#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
680pub struct ResolvedStandard<S> {
681 pub standard: S,
682 pub source: LanguageStandardSource,
683}
684
685#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
687pub struct InterfaceStandard<S> {
688 pub requirement: InterfaceRequirement<S>,
689 pub source: InterfaceStandardSource,
690}
691
692#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
697pub struct ResolvedLanguageStandards {
698 pub c: Option<ResolvedStandard<CStandard>>,
699 pub cxx: Option<ResolvedStandard<CxxStandard>>,
700}
701
702fn package_resolution<S: Copy>(
707 declaration: Option<StandardDeclaration<S>>,
708) -> Option<ResolvedStandard<S>> {
709 match declaration {
710 Some(StandardDeclaration::Declared(standard)) => Some(ResolvedStandard {
711 standard,
712 source: LanguageStandardSource::Package,
713 }),
714 Some(StandardDeclaration::Inherited(standard)) => Some(ResolvedStandard {
715 standard,
716 source: LanguageStandardSource::Workspace,
717 }),
718 Some(StandardDeclaration::Workspace) => {
719 debug_assert!(
720 false,
721 "unresolved `{{ workspace = true }}` standard marker reached resolution"
722 );
723 None
724 }
725 None => None,
726 }
727}
728
729#[must_use]
732pub fn resolve_language_standards(package: &LanguageStandardSettings) -> ResolvedLanguageStandards {
733 ResolvedLanguageStandards {
734 c: package_resolution(package.c_standard),
735 cxx: package_resolution(package.cxx_standard),
736 }
737}
738
739#[must_use]
743pub fn effective_c(
744 package: &ResolvedLanguageStandards,
745 target: &Target,
746) -> Option<ResolvedStandard<CStandard>> {
747 target
748 .language
749 .c_standard_value()
750 .map_or(package.c, |standard| {
751 Some(ResolvedStandard {
752 standard,
753 source: LanguageStandardSource::Target,
754 })
755 })
756}
757
758#[must_use]
760pub fn effective_cxx(
761 package: &ResolvedLanguageStandards,
762 target: &Target,
763) -> Option<ResolvedStandard<CxxStandard>> {
764 target
765 .language
766 .cxx_standard_value()
767 .map_or(package.cxx, |standard| {
768 Some(ResolvedStandard {
769 standard,
770 source: LanguageStandardSource::Target,
771 })
772 })
773}
774
775fn interface_resolution<S: Copy>(
778 declaration: Option<StandardDeclaration<InterfaceRequirement<S>>>,
779) -> Option<InterfaceStandard<S>> {
780 match declaration {
781 Some(StandardDeclaration::Declared(requirement)) => Some(InterfaceStandard {
782 requirement,
783 source: InterfaceStandardSource::Package,
784 }),
785 Some(StandardDeclaration::Inherited(requirement)) => Some(InterfaceStandard {
786 requirement,
787 source: InterfaceStandardSource::Workspace,
788 }),
789 Some(StandardDeclaration::Workspace) => {
790 debug_assert!(
791 false,
792 "unresolved `{{ workspace = true }}` standard marker reached resolution"
793 );
794 None
795 }
796 None => None,
797 }
798}
799
800#[must_use]
807pub fn interface_c(
808 package: &ResolvedLanguageStandards,
809 package_settings: &LanguageStandardSettings,
810 target: &Target,
811) -> Option<InterfaceStandard<CStandard>> {
812 if let Some(requirement) = target.language.interface_c_standard_value() {
813 return Some(InterfaceStandard {
814 requirement,
815 source: InterfaceStandardSource::Target,
816 });
817 }
818 if let Some(interface) = interface_resolution(package_settings.interface_c_standard) {
819 return Some(interface);
820 }
821 effective_c(package, target).map(|resolved| InterfaceStandard {
822 requirement: InterfaceRequirement::Requirement(StandardRequirement {
823 min: resolved.standard,
824 max: None,
825 }),
826 source: InterfaceStandardSource::CompileStandard,
827 })
828}
829
830#[must_use]
832pub fn interface_cxx(
833 package: &ResolvedLanguageStandards,
834 package_settings: &LanguageStandardSettings,
835 target: &Target,
836) -> Option<InterfaceStandard<CxxStandard>> {
837 if let Some(requirement) = target.language.interface_cxx_standard_value() {
838 return Some(InterfaceStandard {
839 requirement,
840 source: InterfaceStandardSource::Target,
841 });
842 }
843 if let Some(interface) = interface_resolution(package_settings.interface_cxx_standard) {
844 return Some(interface);
845 }
846 effective_cxx(package, target).map(|resolved| InterfaceStandard {
847 requirement: InterfaceRequirement::Requirement(StandardRequirement {
848 min: resolved.standard,
849 max: None,
850 }),
851 source: InterfaceStandardSource::CompileStandard,
852 })
853}
854
855#[must_use]
863pub fn imposes_requirement(
864 target: &Target,
865 package_settings: &LanguageStandardSettings,
866 language: SourceLanguage,
867) -> bool {
868 let has_sources = target
869 .sources
870 .iter()
871 .any(|s| classify_source(s) == Some(language));
872 let target_declares = match language {
873 SourceLanguage::C => {
874 target.language.c_standard.is_some() || target.language.interface_c_standard.is_some()
875 }
876 SourceLanguage::Cxx => {
877 target.language.cxx_standard.is_some()
878 || target.language.interface_cxx_standard.is_some()
879 }
880 };
881 let header_only_package_interface = target.kind.is_header_only()
882 && match language {
883 SourceLanguage::C => package_settings.interface_c_standard.is_some(),
884 SourceLanguage::Cxx => package_settings.interface_cxx_standard.is_some(),
885 };
886 has_sources || target_declares || header_only_package_interface
887}
888
889pub const STANDARD_FLAG_PREFIXES: [&str; 3] = ["-std=", "--std=", "/std:"];
892
893#[derive(Debug, Error, Clone, PartialEq, Eq)]
896#[error(
897 "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",
898 .language.human_label()
899)]
900pub struct StandardFlagConflict {
901 pub package: String,
902 pub language: SourceLanguage,
903 pub field: &'static str,
906 pub flag_list: &'static str,
909 pub flag: String,
910 pub target: Option<String>,
917}
918
919fn first_standard_token(flags: &[String]) -> Option<String> {
920 flags
921 .iter()
922 .find(|f| STANDARD_FLAG_PREFIXES.iter().any(|p| f.starts_with(p)))
923 .cloned()
924}
925
926#[must_use]
937pub fn find_standard_flag_conflicts(
938 package: &str,
939 settings: &LanguageStandardSettings,
940 targets: &[Target],
941 flags: &ResolvedProfileFlags,
942) -> Vec<StandardFlagConflict> {
943 let mut out = Vec::new();
944 let mut check = |language: SourceLanguage,
947 field: &'static str,
948 flag_list: &'static str,
949 list: &[String],
950 package_declares: bool,
951 target_declares: fn(&Target) -> bool| {
952 let Some(flag) = first_standard_token(list) else {
953 return;
954 };
955 let mut push = |flag: String, target: Option<String>| {
956 out.push(StandardFlagConflict {
957 package: package.to_owned(),
958 language,
959 field,
960 flag_list,
961 flag,
962 target,
963 });
964 };
965 if package_declares {
966 push(flag, None);
967 } else {
968 for target in targets {
969 if target_declares(target) {
970 push(flag.clone(), Some(target.name.as_str().to_owned()));
971 }
972 }
973 }
974 };
975 check(
976 SourceLanguage::C,
977 "c-standard",
978 "cflags",
979 &flags.cflags,
980 settings.c_standard.is_some(),
981 |target| target.language.c_standard.is_some(),
982 );
983 check(
984 SourceLanguage::Cxx,
985 "cxx-standard",
986 "cxxflags",
987 &flags.cxxflags,
988 settings.cxx_standard.is_some(),
989 |target| target.language.cxx_standard.is_some(),
990 );
991 out
992}
993
994#[derive(Debug, Error, Clone, PartialEq, Eq)]
998#[error(
999 "target `{target}` in package `{package}` sets `{field} = \"{interface_min}\"` but compiles its {} sources as `{implementation}`; the target's own translation units could not include its own public headers - raise `{}` or lower the interface minimum",
1000 .language.human_label(),
1001 implementation_field(*.language)
1002)]
1003pub struct InterfaceStandardContradiction {
1004 pub package: String,
1005 pub target: String,
1006 pub language: SourceLanguage,
1007 pub field: &'static str,
1010 pub implementation: LanguageStandard,
1011 pub interface_min: LanguageStandard,
1012}
1013
1014#[must_use]
1023pub fn find_interface_standard_contradictions(
1024 package: &crate::Package,
1025) -> Vec<InterfaceStandardContradiction> {
1026 let resolved = resolve_language_standards(&package.language);
1027 let mut out = Vec::new();
1028 for target in &package.targets {
1029 let library_like = target.kind.is_library_like();
1030 if !library_like {
1031 continue;
1032 }
1033 let compiles = |language: SourceLanguage| {
1034 target
1035 .sources
1036 .iter()
1037 .any(|s| classify_source(s) == Some(language))
1038 };
1039 if compiles(SourceLanguage::C)
1040 && let (Some(implementation), Some(interface)) = (
1041 effective_c(&resolved, target),
1042 interface_c(&resolved, &package.language, target),
1043 )
1044 && let Some(min) = interface.requirement.min()
1045 && min > implementation.standard
1046 {
1047 out.push(InterfaceStandardContradiction {
1048 package: package.name.as_str().to_owned(),
1049 target: target.name.as_str().to_owned(),
1050 language: SourceLanguage::C,
1051 field: "interface-c-standard",
1052 implementation: LanguageStandard::C(implementation.standard),
1053 interface_min: LanguageStandard::C(min),
1054 });
1055 }
1056 if compiles(SourceLanguage::Cxx)
1057 && let (Some(implementation), Some(interface)) = (
1058 effective_cxx(&resolved, target),
1059 interface_cxx(&resolved, &package.language, target),
1060 )
1061 && let Some(min) = interface.requirement.min()
1062 && min > implementation.standard
1063 {
1064 out.push(InterfaceStandardContradiction {
1065 package: package.name.as_str().to_owned(),
1066 target: target.name.as_str().to_owned(),
1067 language: SourceLanguage::Cxx,
1068 field: "interface-cxx-standard",
1069 implementation: LanguageStandard::Cxx(implementation.standard),
1070 interface_min: LanguageStandard::Cxx(min),
1071 });
1072 }
1073 }
1074 out
1075}
1076
1077#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1084pub struct LanguageStandardsSummary {
1085 #[serde(default, skip_serializing_if = "Option::is_none")]
1086 pub c: Option<ResolvedStandard<CStandard>>,
1087 #[serde(default, skip_serializing_if = "Option::is_none")]
1088 pub cxx: Option<ResolvedStandard<CxxStandard>>,
1089 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1090 pub targets: BTreeMap<String, TargetStandardsSummary>,
1091}
1092
1093#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
1096pub struct TargetStandardsSummary {
1097 #[serde(default, skip_serializing_if = "Option::is_none")]
1098 pub c: Option<ResolvedStandard<CStandard>>,
1099 #[serde(default, skip_serializing_if = "Option::is_none")]
1100 pub cxx: Option<ResolvedStandard<CxxStandard>>,
1101 #[serde(default, skip_serializing_if = "Option::is_none")]
1102 pub interface_c: Option<InterfaceStandard<CStandard>>,
1103 #[serde(default, skip_serializing_if = "Option::is_none")]
1104 pub interface_cxx: Option<InterfaceStandard<CxxStandard>>,
1105 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1108 pub gnu_extensions: bool,
1109}
1110
1111impl LanguageStandardsSummary {
1112 #[must_use]
1114 pub fn from_package(package: &crate::Package) -> Self {
1115 let resolved = resolve_language_standards(&package.language);
1116 let targets = package
1117 .targets
1118 .iter()
1119 .map(|target| {
1120 let library_like = target.kind.is_library_like();
1121 let summary = TargetStandardsSummary {
1122 c: effective_c(&resolved, target),
1123 cxx: effective_cxx(&resolved, target),
1124 interface_c: library_like
1125 .then(|| interface_c(&resolved, &package.language, target))
1126 .flatten(),
1127 interface_cxx: library_like
1128 .then(|| interface_cxx(&resolved, &package.language, target))
1129 .flatten(),
1130 gnu_extensions: effective_gnu_extensions(&package.language, target),
1131 };
1132 (target.name.as_str().to_owned(), summary)
1133 })
1134 .collect();
1135 Self {
1136 c: resolved.c,
1137 cxx: resolved.cxx,
1138 targets,
1139 }
1140 }
1141
1142 #[must_use]
1147 pub fn fingerprint_lines(&self) -> Vec<String> {
1148 let mut lines = Vec::new();
1149 if let Some(resolved) = &self.c {
1150 lines.push(format!("c={}", resolved.standard));
1151 }
1152 if let Some(resolved) = &self.cxx {
1153 lines.push(format!("cxx={}", resolved.standard));
1154 }
1155 for (name, target) in &self.targets {
1156 lines.push(format!("target={name}"));
1157 if let Some(resolved) = &target.c {
1158 lines.push(format!("c={}", resolved.standard));
1159 }
1160 if let Some(resolved) = &target.cxx {
1161 lines.push(format!("cxx={}", resolved.standard));
1162 }
1163 if let Some(interface) = &target.interface_c {
1164 lines.push(format!("interface-c={}", interface.requirement));
1165 }
1166 if let Some(interface) = &target.interface_cxx {
1167 lines.push(format!("interface-cxx={}", interface.requirement));
1168 }
1169 if target.gnu_extensions {
1170 lines.push("gnu-extensions=true".to_owned());
1171 }
1172 }
1173 lines
1174 }
1175}
1176
1177#[cfg(test)]
1178mod tests {
1179 use super::*;
1180 use crate::{TargetKind, TargetName};
1181 use camino::Utf8PathBuf;
1182
1183 fn target(kind: TargetKind, sources: &[&str], language: LanguageStandardSettings) -> Target {
1184 Target {
1185 name: TargetName::new("t").unwrap(),
1186 kind,
1187 sources: sources.iter().map(Utf8PathBuf::from).collect(),
1188 include_dirs: Vec::new(),
1189 defines: Vec::new(),
1190 deps: Vec::new(),
1191 required_features: Vec::new(),
1192 language,
1193 }
1194 }
1195
1196 fn requirement<S>(min: S) -> InterfaceRequirement<S> {
1197 InterfaceRequirement::Requirement(StandardRequirement { min, max: None })
1198 }
1199
1200 #[test]
1201 fn every_accepted_identifier_parses_and_round_trips() {
1202 for s in CStandard::ALL {
1203 assert_eq!(CStandard::parse(s.as_str()).unwrap(), s);
1204 }
1205 for s in CxxStandard::ALL {
1206 assert_eq!(CxxStandard::parse(s.as_str()).unwrap(), s);
1207 }
1208 }
1209
1210 #[test]
1211 fn aliases_normalize_immediately() {
1212 assert_eq!(CStandard::parse("c90").unwrap(), CStandard::C89);
1213 assert_eq!(CxxStandard::parse("c++03").unwrap(), CxxStandard::Cxx98);
1214 assert_eq!(CStandard::parse("c90").unwrap().as_str(), "c89");
1216 assert_eq!(CxxStandard::parse("c++03").unwrap().as_str(), "c++98");
1217 }
1218
1219 #[test]
1220 fn unknown_values_list_the_accepted_identifiers() {
1221 let err = CStandard::parse("c++17").unwrap_err();
1222 assert_eq!(
1223 err.to_string(),
1224 "unknown C standard `c++17`: expected one of c89, c99, c11, c17, c23"
1225 );
1226 let err = CxxStandard::parse("c++29").unwrap_err();
1227 assert_eq!(
1228 err.to_string(),
1229 "unknown C++ standard `c++29`: expected one of c++98, c++11, c++14, c++17, c++20, c++23, c++26"
1230 );
1231 }
1232
1233 #[test]
1234 fn gnu_spellings_are_ordinary_unknown_values() {
1235 for value in ["gnu89", "gnu99", "gnu11", "gnu17", "gnu23", "gnu90"] {
1236 let err = CStandard::parse(value).unwrap_err();
1237 assert!(
1238 matches!(&err, LanguageStandardParseError::Unknown { value: v, .. } if v == value),
1239 "unexpected error for {value}: {err}"
1240 );
1241 assert!(!err.to_string().contains("gnu-extensions"));
1244 }
1245 for value in [
1246 "gnu++98", "gnu++03", "gnu++11", "gnu++14", "gnu++17", "gnu++20", "gnu++23", "gnu++26",
1247 ] {
1248 let err = CxxStandard::parse(value).unwrap_err();
1249 assert!(
1250 matches!(&err, LanguageStandardParseError::Unknown { value: v, .. } if v == value),
1251 "unexpected error for {value}: {err}"
1252 );
1253 assert!(!err.to_string().contains("gnu-extensions"));
1254 }
1255 }
1256
1257 #[test]
1258 fn range_like_values_get_the_reserved_diagnostic() {
1259 for value in [">=c11", "<=c17", ">c99", "<c23", "c11,c17", "c11, c17"] {
1260 let err = CStandard::parse(value).unwrap_err();
1261 assert!(
1262 matches!(err, LanguageStandardParseError::RangeReserved { .. }),
1263 "expected reserved-range error for {value}, got: {err}"
1264 );
1265 assert!(err.to_string().contains("reserved for a future version"));
1266 }
1267 for value in [">=c++17", "<=c++20", ">c++11", "<c++23", "c++17,c++20"] {
1268 let err = CxxStandard::parse(value).unwrap_err();
1269 assert!(
1270 matches!(err, LanguageStandardParseError::RangeReserved { .. }),
1271 "expected reserved-range error for {value}, got: {err}"
1272 );
1273 }
1274 assert!(matches!(
1276 parse_interface_c(">=c11").unwrap_err(),
1277 LanguageStandardParseError::RangeReserved { .. }
1278 ));
1279 assert!(matches!(
1280 parse_interface_cxx(">=c++17").unwrap_err(),
1281 LanguageStandardParseError::RangeReserved { .. }
1282 ));
1283 }
1284
1285 #[test]
1286 fn none_is_interface_only() {
1287 assert_eq!(
1288 parse_interface_c("none").unwrap(),
1289 InterfaceRequirement::None
1290 );
1291 assert_eq!(
1292 parse_interface_cxx("none").unwrap(),
1293 InterfaceRequirement::None
1294 );
1295 let err = CStandard::parse("none").unwrap_err();
1296 assert!(
1297 matches!(err, LanguageStandardParseError::NoneOnImplementation { .. }),
1298 "expected misplaced-none error, got: {err}"
1299 );
1300 assert!(err.to_string().contains("interface-c-standard"));
1301 let err = CxxStandard::parse("none").unwrap_err();
1302 assert!(matches!(
1303 err,
1304 LanguageStandardParseError::NoneOnImplementation { .. }
1305 ));
1306 }
1307
1308 #[test]
1309 fn interface_parsers_accept_every_identifier_and_alias() {
1310 for s in CStandard::ALL {
1311 assert_eq!(parse_interface_c(s.as_str()).unwrap(), requirement(s));
1312 }
1313 for s in CxxStandard::ALL {
1314 assert_eq!(parse_interface_cxx(s.as_str()).unwrap(), requirement(s));
1315 }
1316 assert_eq!(
1317 parse_interface_c("c90").unwrap(),
1318 requirement(CStandard::C89)
1319 );
1320 assert_eq!(
1321 parse_interface_cxx("c++03").unwrap(),
1322 requirement(CxxStandard::Cxx98)
1323 );
1324 }
1325
1326 #[test]
1327 fn standards_order_chronologically() {
1328 assert!(CStandard::C89 < CStandard::C99);
1329 assert!(CStandard::C99 < CStandard::C11);
1330 assert!(CStandard::C11 < CStandard::C17);
1331 assert!(CStandard::C17 < CStandard::C23);
1332 assert!(CxxStandard::Cxx98 < CxxStandard::Cxx11);
1333 assert!(CxxStandard::Cxx11 < CxxStandard::Cxx14);
1334 assert!(CxxStandard::Cxx14 < CxxStandard::Cxx17);
1335 assert!(CxxStandard::Cxx17 < CxxStandard::Cxx20);
1336 assert!(CxxStandard::Cxx20 < CxxStandard::Cxx23);
1337 assert!(CxxStandard::Cxx23 < CxxStandard::Cxx26);
1338 }
1339
1340 #[test]
1341 fn msvc_spellings_cover_exactly_the_stable_flags() {
1342 assert_eq!(
1343 LanguageStandard::Cxx(CxxStandard::Cxx20).msvc_spelling(),
1344 Some("/std:c++20")
1345 );
1346 assert_eq!(
1347 LanguageStandard::C(CStandard::C17).msvc_spelling(),
1348 Some("/std:c17")
1349 );
1350 assert_eq!(LanguageStandard::C(CStandard::C99).msvc_spelling(), None);
1351 assert_eq!(
1352 LanguageStandard::Cxx(CxxStandard::Cxx23).msvc_spelling(),
1353 None
1354 );
1355 assert_eq!(
1356 LanguageStandard::Cxx(CxxStandard::Cxx26).msvc_spelling(),
1357 None
1358 );
1359 assert_eq!(
1360 LanguageStandard::Cxx(CxxStandard::Cxx11).msvc_spelling(),
1361 None
1362 );
1363 }
1364
1365 #[test]
1366 fn standard_requirement_serde_round_trips_preserving_max() {
1367 let min_only = StandardRequirement {
1368 min: CxxStandard::Cxx17,
1369 max: None,
1370 };
1371 let json = serde_json::to_string(&min_only).unwrap();
1372 assert_eq!(json, r#"{"min":"c++17","max":null}"#);
1374 let parsed: StandardRequirement<CxxStandard> = serde_json::from_str(&json).unwrap();
1375 assert_eq!(parsed, min_only);
1376
1377 let with_max = StandardRequirement {
1378 min: CStandard::C11,
1379 max: Some(CStandard::C17),
1380 };
1381 let json = serde_json::to_string(&with_max).unwrap();
1382 assert_eq!(json, r#"{"min":"c11","max":"c17"}"#);
1383 let parsed: StandardRequirement<CStandard> = serde_json::from_str(&json).unwrap();
1384 assert_eq!(parsed, with_max);
1385
1386 let parsed: StandardRequirement<CStandard> =
1388 serde_json::from_str(r#"{"min":"c11"}"#).unwrap();
1389 assert_eq!(parsed.max, None);
1390 assert!(
1393 serde_json::from_str::<StandardRequirement<CStandard>>(
1394 r#"{"min":"c11","exact":"c17"}"#
1395 )
1396 .is_err()
1397 );
1398 }
1399
1400 #[test]
1401 fn interface_requirement_serde_round_trips() {
1402 let none: InterfaceRequirement<CxxStandard> = InterfaceRequirement::None;
1403 let json = serde_json::to_string(&none).unwrap();
1404 assert_eq!(json, "\"none\"");
1405 let parsed: InterfaceRequirement<CxxStandard> = serde_json::from_str(&json).unwrap();
1406 assert_eq!(parsed, none);
1407
1408 let req = requirement(CxxStandard::Cxx20);
1409 let json = serde_json::to_string(&req).unwrap();
1410 assert_eq!(json, r#"{"min":"c++20","max":null}"#);
1411 let parsed: InterfaceRequirement<CxxStandard> = serde_json::from_str(&json).unwrap();
1412 assert_eq!(parsed, req);
1413
1414 assert!(serde_json::from_str::<InterfaceRequirement<CxxStandard>>("\"c++20\"").is_err());
1416 }
1417
1418 #[test]
1419 fn interface_requirement_displays_min_max_and_none() {
1420 assert_eq!(
1421 InterfaceRequirement::<CxxStandard>::None.to_string(),
1422 "none"
1423 );
1424 assert_eq!(requirement(CxxStandard::Cxx17).to_string(), "c++17");
1425 assert_eq!(
1426 InterfaceRequirement::Requirement(StandardRequirement {
1427 min: CStandard::C11,
1428 max: Some(CStandard::C17),
1429 })
1430 .to_string(),
1431 "c11..c17"
1432 );
1433 }
1434
1435 #[test]
1436 fn gnu_extensions_default_false_with_target_over_package() {
1437 let plain = target(
1438 TargetKind::Executable,
1439 &["a.cc"],
1440 LanguageStandardSettings::default(),
1441 );
1442 let none = LanguageStandardSettings::default();
1443 assert!(!effective_gnu_extensions(&none, &plain));
1444
1445 let package_on = LanguageStandardSettings {
1446 gnu_extensions: Some(true),
1447 ..Default::default()
1448 };
1449 assert!(effective_gnu_extensions(&package_on, &plain));
1450
1451 let target_off = target(
1452 TargetKind::Executable,
1453 &["a.cc"],
1454 LanguageStandardSettings {
1455 gnu_extensions: Some(false),
1456 ..Default::default()
1457 },
1458 );
1459 assert!(!effective_gnu_extensions(&package_on, &target_off));
1460
1461 let target_on = target(
1462 TargetKind::Executable,
1463 &["a.cc"],
1464 LanguageStandardSettings {
1465 gnu_extensions: Some(true),
1466 ..Default::default()
1467 },
1468 );
1469 assert!(effective_gnu_extensions(&none, &target_on));
1470 }
1471
1472 #[test]
1473 fn effective_standard_prefers_target_then_package_then_none() {
1474 let undeclared = resolve_language_standards(&LanguageStandardSettings::default());
1475 let plain = target(
1476 TargetKind::Executable,
1477 &["a.cc"],
1478 LanguageStandardSettings::default(),
1479 );
1480 assert_eq!(effective_cxx(&undeclared, &plain), None);
1481 assert_eq!(effective_c(&undeclared, &plain), None);
1482
1483 let package = resolve_language_standards(&LanguageStandardSettings {
1484 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx14)),
1485 ..Default::default()
1486 });
1487 let effective = effective_cxx(&package, &plain).unwrap();
1488 assert_eq!(effective.standard, CxxStandard::Cxx14);
1489 assert_eq!(effective.source, LanguageStandardSource::Package);
1490 assert_eq!(effective_c(&package, &plain), None);
1492
1493 let overridden = target(
1494 TargetKind::Executable,
1495 &["a.cc"],
1496 LanguageStandardSettings {
1497 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1498 ..Default::default()
1499 },
1500 );
1501 let effective = effective_cxx(&package, &overridden).unwrap();
1502 assert_eq!(effective.standard, CxxStandard::Cxx20);
1503 assert_eq!(effective.source, LanguageStandardSource::Target);
1504 }
1505
1506 #[test]
1507 fn interface_standard_falls_back_to_explicit_compile_standard_or_none() {
1508 let package_settings = LanguageStandardSettings {
1509 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1510 ..Default::default()
1511 };
1512 let resolved = resolve_language_standards(&package_settings);
1513 let lib = target(
1514 TargetKind::Library,
1515 &["a.cc"],
1516 LanguageStandardSettings::default(),
1517 );
1518 let interface = interface_cxx(&resolved, &package_settings, &lib).unwrap();
1519 assert_eq!(interface.requirement, requirement(CxxStandard::Cxx20));
1520 assert_eq!(interface.source, InterfaceStandardSource::CompileStandard);
1521 let undeclared = LanguageStandardSettings::default();
1524 let resolved_undeclared = resolve_language_standards(&undeclared);
1525 assert_eq!(interface_cxx(&resolved_undeclared, &undeclared, &lib), None);
1526 assert_eq!(interface_c(&resolved_undeclared, &undeclared, &lib), None);
1527
1528 let package_interface = LanguageStandardSettings {
1529 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1530 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1531 CxxStandard::Cxx17,
1532 ))),
1533 ..Default::default()
1534 };
1535 let resolved = resolve_language_standards(&package_interface);
1536 let interface = interface_cxx(&resolved, &package_interface, &lib).unwrap();
1537 assert_eq!(interface.requirement, requirement(CxxStandard::Cxx17));
1538 assert_eq!(interface.source, InterfaceStandardSource::Package);
1539
1540 let lib_override = target(
1541 TargetKind::Library,
1542 &["a.cc"],
1543 LanguageStandardSettings {
1544 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1545 CxxStandard::Cxx14,
1546 ))),
1547 ..Default::default()
1548 },
1549 );
1550 let interface = interface_cxx(&resolved, &package_interface, &lib_override).unwrap();
1551 assert_eq!(interface.requirement, requirement(CxxStandard::Cxx14));
1552 assert_eq!(interface.source, InterfaceStandardSource::Target);
1553 }
1554
1555 #[test]
1556 fn declared_none_interface_survives_resolution() {
1557 let package_settings = LanguageStandardSettings {
1558 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1559 ..Default::default()
1560 };
1561 let resolved = resolve_language_standards(&package_settings);
1562 let lib = target(
1563 TargetKind::Library,
1564 &["a.cc"],
1565 LanguageStandardSettings {
1566 interface_cxx_standard: Some(StandardDeclaration::Declared(
1567 InterfaceRequirement::None,
1568 )),
1569 ..Default::default()
1570 },
1571 );
1572 let interface = interface_cxx(&resolved, &package_settings, &lib).unwrap();
1573 assert_eq!(interface.requirement, InterfaceRequirement::None);
1574 assert_eq!(interface.source, InterfaceStandardSource::Target);
1575 assert_eq!(interface.requirement.min(), None);
1576 }
1577
1578 #[test]
1579 fn imposes_requirement_relevance_rules() {
1580 let none = LanguageStandardSettings::default();
1581 let c_lib = target(
1583 TargetKind::Library,
1584 &["a.c"],
1585 LanguageStandardSettings::default(),
1586 );
1587 assert!(imposes_requirement(&c_lib, &none, SourceLanguage::C));
1588 assert!(!imposes_requirement(&c_lib, &none, SourceLanguage::Cxx));
1589
1590 let package_impl = LanguageStandardSettings {
1593 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1594 ..Default::default()
1595 };
1596 assert!(!imposes_requirement(
1597 &c_lib,
1598 &package_impl,
1599 SourceLanguage::Cxx
1600 ));
1601
1602 let declared = target(
1604 TargetKind::Library,
1605 &["a.c"],
1606 LanguageStandardSettings {
1607 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1608 CxxStandard::Cxx17,
1609 ))),
1610 ..Default::default()
1611 },
1612 );
1613 assert!(imposes_requirement(&declared, &none, SourceLanguage::Cxx));
1614
1615 let header_only = target(
1617 TargetKind::HeaderOnly,
1618 &[],
1619 LanguageStandardSettings::default(),
1620 );
1621 assert!(!imposes_requirement(
1622 &header_only,
1623 &none,
1624 SourceLanguage::Cxx
1625 ));
1626 let package_interface = LanguageStandardSettings {
1627 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1628 CxxStandard::Cxx20,
1629 ))),
1630 ..Default::default()
1631 };
1632 assert!(imposes_requirement(
1633 &header_only,
1634 &package_interface,
1635 SourceLanguage::Cxx
1636 ));
1637 assert!(!imposes_requirement(
1639 &header_only,
1640 &package_impl,
1641 SourceLanguage::Cxx
1642 ));
1643 }
1644
1645 #[test]
1646 fn conflict_fires_only_for_declared_language_and_matching_bucket() {
1647 let flags = ResolvedProfileFlags {
1648 cxxflags: vec!["-std=c++14".to_owned()],
1649 ..Default::default()
1650 };
1651 let declared_cxx = LanguageStandardSettings {
1652 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1653 ..Default::default()
1654 };
1655
1656 assert!(
1658 find_standard_flag_conflicts("p", &LanguageStandardSettings::default(), &[], &flags)
1659 .is_empty()
1660 );
1661
1662 let conflicts = find_standard_flag_conflicts("p", &declared_cxx, &[], &flags);
1665 let conflict = conflicts.first().unwrap();
1666 assert_eq!(conflicts.len(), 1);
1667 assert_eq!(conflict.language, SourceLanguage::Cxx);
1668 assert_eq!(conflict.flag, "-std=c++14");
1669 assert_eq!(conflict.field, "cxx-standard");
1670 assert_eq!(conflict.target, None);
1671 assert!(conflict.to_string().contains("cxx-standard"));
1672
1673 let c_only_flags = ResolvedProfileFlags {
1675 cflags: vec!["-std=c99".to_owned()],
1676 ..Default::default()
1677 };
1678 assert!(find_standard_flag_conflicts("p", &declared_cxx, &[], &c_only_flags).is_empty());
1679
1680 let t = target(
1682 TargetKind::Executable,
1683 &["a.c"],
1684 LanguageStandardSettings {
1685 c_standard: Some(StandardDeclaration::Declared(CStandard::C17)),
1686 ..Default::default()
1687 },
1688 );
1689 let conflicts = find_standard_flag_conflicts(
1690 "p",
1691 &LanguageStandardSettings::default(),
1692 std::slice::from_ref(&t),
1693 &c_only_flags,
1694 );
1695 let conflict = conflicts.first().unwrap();
1696 assert_eq!(conflict.language, SourceLanguage::C);
1697 assert_eq!(conflict.flag_list, "cflags");
1698 assert_eq!(conflict.target.as_deref(), Some("t"));
1702
1703 let msvc_flags = ResolvedProfileFlags {
1705 cxxflags: vec!["/std:c++latest".to_owned()],
1706 ..Default::default()
1707 };
1708 assert!(!find_standard_flag_conflicts("p", &declared_cxx, &[], &msvc_flags).is_empty());
1709 }
1710
1711 fn package_with(targets: Vec<Target>, language: LanguageStandardSettings) -> crate::Package {
1712 use crate::{Package, PackageName};
1713 Package::new(
1714 PackageName::new("demo").unwrap(),
1715 semver::Version::parse("0.1.0").unwrap(),
1716 targets,
1717 Vec::new(),
1718 )
1719 .unwrap()
1720 .with_language(language)
1721 }
1722
1723 #[test]
1724 fn contradiction_fires_when_interface_minimum_exceeds_implementation() {
1725 let lib = target(
1728 TargetKind::Library,
1729 &["a.cc"],
1730 LanguageStandardSettings {
1731 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1732 CxxStandard::Cxx20,
1733 ))),
1734 ..Default::default()
1735 },
1736 );
1737 let package = package_with(
1738 vec![lib],
1739 LanguageStandardSettings {
1740 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1741 ..Default::default()
1742 },
1743 );
1744 let contradictions = find_interface_standard_contradictions(&package);
1745 assert_eq!(contradictions.len(), 1);
1746 let contradiction = &contradictions[0];
1747 assert_eq!(contradiction.target, "t");
1748 assert_eq!(contradiction.field, "interface-cxx-standard");
1749 assert_eq!(
1750 contradiction.implementation,
1751 LanguageStandard::Cxx(CxxStandard::Cxx17)
1752 );
1753 assert_eq!(
1754 contradiction.interface_min,
1755 LanguageStandard::Cxx(CxxStandard::Cxx20)
1756 );
1757 let message = contradiction.to_string();
1758 assert!(
1759 message.contains("could not include its own public headers"),
1760 "message must state the reason plainly: {message}"
1761 );
1762
1763 let c_lib = target(
1765 TargetKind::Library,
1766 &["a.c"],
1767 LanguageStandardSettings::default(),
1768 );
1769 let package = package_with(
1770 vec![c_lib],
1771 LanguageStandardSettings {
1772 c_standard: Some(StandardDeclaration::Declared(CStandard::C11)),
1773 interface_c_standard: Some(StandardDeclaration::Declared(requirement(
1774 CStandard::C23,
1775 ))),
1776 ..Default::default()
1777 },
1778 );
1779 let contradictions = find_interface_standard_contradictions(&package);
1780 assert_eq!(contradictions.len(), 1);
1781 assert_eq!(contradictions[0].field, "interface-c-standard");
1782 }
1783
1784 #[test]
1785 fn contradiction_ignores_equal_older_none_and_non_compiling_targets() {
1786 for interface in [CxxStandard::Cxx17, CxxStandard::Cxx14] {
1788 let lib = target(
1789 TargetKind::Library,
1790 &["a.cc"],
1791 LanguageStandardSettings {
1792 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1793 interface,
1794 ))),
1795 ..Default::default()
1796 },
1797 );
1798 let package = package_with(
1799 vec![lib],
1800 LanguageStandardSettings {
1801 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1802 ..Default::default()
1803 },
1804 );
1805 assert!(find_interface_standard_contradictions(&package).is_empty());
1806 }
1807
1808 let lib = target(
1810 TargetKind::Library,
1811 &["a.cc"],
1812 LanguageStandardSettings {
1813 interface_cxx_standard: Some(StandardDeclaration::Declared(
1814 InterfaceRequirement::None,
1815 )),
1816 ..Default::default()
1817 },
1818 );
1819 let package = package_with(
1820 vec![lib],
1821 LanguageStandardSettings {
1822 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1823 ..Default::default()
1824 },
1825 );
1826 assert!(find_interface_standard_contradictions(&package).is_empty());
1827
1828 let header_only = target(
1831 TargetKind::HeaderOnly,
1832 &[],
1833 LanguageStandardSettings::default(),
1834 );
1835 let package = package_with(
1836 vec![header_only],
1837 LanguageStandardSettings {
1838 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1839 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1840 CxxStandard::Cxx20,
1841 ))),
1842 ..Default::default()
1843 },
1844 );
1845 assert!(find_interface_standard_contradictions(&package).is_empty());
1846
1847 let c_lib = target(
1850 TargetKind::Library,
1851 &["a.c"],
1852 LanguageStandardSettings {
1853 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1854 CxxStandard::Cxx26,
1855 ))),
1856 ..Default::default()
1857 },
1858 );
1859 let package = package_with(
1860 vec![c_lib],
1861 LanguageStandardSettings {
1862 c_standard: Some(StandardDeclaration::Declared(CStandard::C11)),
1863 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1864 ..Default::default()
1865 },
1866 );
1867 assert!(find_interface_standard_contradictions(&package).is_empty());
1868
1869 let exe = target(
1871 TargetKind::Executable,
1872 &["a.cc"],
1873 LanguageStandardSettings::default(),
1874 );
1875 let package = package_with(
1876 vec![exe],
1877 LanguageStandardSettings {
1878 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1879 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1880 CxxStandard::Cxx20,
1881 ))),
1882 ..Default::default()
1883 },
1884 );
1885 assert!(find_interface_standard_contradictions(&package).is_empty());
1886 }
1887
1888 #[test]
1889 fn summary_lists_every_target_with_interface_only_for_library_like() {
1890 use crate::{Package, PackageName};
1891 let package = Package::new(
1892 PackageName::new("demo").unwrap(),
1893 semver::Version::parse("0.1.0").unwrap(),
1894 vec![
1895 target(
1896 TargetKind::Executable,
1897 &["main.cc"],
1898 LanguageStandardSettings::default(),
1899 ),
1900 Target {
1901 name: TargetName::new("core").unwrap(),
1902 kind: TargetKind::Library,
1903 sources: vec![Utf8PathBuf::from("core.cc")],
1904 include_dirs: Vec::new(),
1905 defines: Vec::new(),
1906 deps: Vec::new(),
1907 required_features: Vec::new(),
1908 language: LanguageStandardSettings {
1909 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
1910 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1911 CxxStandard::Cxx17,
1912 ))),
1913 gnu_extensions: Some(true),
1914 ..Default::default()
1915 },
1916 },
1917 ],
1918 Vec::new(),
1919 )
1920 .unwrap()
1921 .with_language(LanguageStandardSettings {
1922 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx17)),
1923 ..Default::default()
1924 });
1925 let summary = LanguageStandardsSummary::from_package(&package);
1926 assert_eq!(summary.cxx.unwrap().standard, CxxStandard::Cxx17);
1927 assert_eq!(summary.c, None);
1928 assert_eq!(summary.targets.len(), 2);
1929 let exe = &summary.targets["t"];
1930 assert!(exe.interface_c.is_none() && exe.interface_cxx.is_none());
1931 assert!(!exe.gnu_extensions);
1932 let core = &summary.targets["core"];
1933 assert_eq!(core.cxx.unwrap().standard, CxxStandard::Cxx20);
1934 assert_eq!(
1935 core.interface_cxx.unwrap().requirement,
1936 requirement(CxxStandard::Cxx17)
1937 );
1938 assert!(core.gnu_extensions);
1939 assert_eq!(core.interface_c, None);
1942 }
1943
1944 #[test]
1945 fn package_level_interface_fields_are_inert_without_library_like_targets() {
1946 use crate::{Package, PackageName};
1947 let package = Package::new(
1952 PackageName::new("demo").unwrap(),
1953 semver::Version::parse("0.1.0").unwrap(),
1954 vec![target(
1955 TargetKind::Executable,
1956 &["main.cc"],
1957 LanguageStandardSettings::default(),
1958 )],
1959 Vec::new(),
1960 )
1961 .unwrap()
1962 .with_language(LanguageStandardSettings {
1963 interface_c_standard: Some(StandardDeclaration::Declared(requirement(CStandard::C17))),
1964 interface_cxx_standard: Some(StandardDeclaration::Declared(requirement(
1965 CxxStandard::Cxx20,
1966 ))),
1967 ..Default::default()
1968 });
1969 let summary = LanguageStandardsSummary::from_package(&package);
1970 let exe = &summary.targets["t"];
1971 assert!(
1972 exe.interface_c.is_none() && exe.interface_cxx.is_none(),
1973 "package-level interface fields must stay inert on executables"
1974 );
1975 }
1976
1977 #[test]
1978 fn fingerprint_lines_are_values_only_and_deterministic() {
1979 let mut summary = LanguageStandardsSummary::default();
1980 assert!(summary.fingerprint_lines().is_empty());
1982
1983 summary.c = Some(ResolvedStandard {
1984 standard: CStandard::C11,
1985 source: LanguageStandardSource::Package,
1986 });
1987 summary.cxx = Some(ResolvedStandard {
1988 standard: CxxStandard::Cxx17,
1989 source: LanguageStandardSource::Package,
1990 });
1991 let lines = summary.fingerprint_lines();
1992 assert_eq!(lines, vec!["c=c11".to_owned(), "cxx=c++17".to_owned()]);
1993
1994 summary.cxx = Some(ResolvedStandard {
1996 standard: CxxStandard::Cxx17,
1997 source: LanguageStandardSource::Workspace,
1998 });
1999 assert_eq!(summary.fingerprint_lines(), lines);
2000
2001 summary.targets.insert(
2002 "core".to_owned(),
2003 TargetStandardsSummary {
2004 c: summary.c,
2005 cxx: Some(ResolvedStandard {
2006 standard: CxxStandard::Cxx20,
2007 source: LanguageStandardSource::Target,
2008 }),
2009 interface_c: Some(InterfaceStandard {
2010 requirement: InterfaceRequirement::None,
2011 source: InterfaceStandardSource::Target,
2012 }),
2013 interface_cxx: Some(InterfaceStandard {
2014 requirement: requirement(CxxStandard::Cxx17),
2015 source: InterfaceStandardSource::Target,
2016 }),
2017 gnu_extensions: true,
2018 },
2019 );
2020 assert_eq!(
2021 summary.fingerprint_lines(),
2022 vec![
2023 "c=c11".to_owned(),
2024 "cxx=c++17".to_owned(),
2025 "target=core".to_owned(),
2026 "c=c11".to_owned(),
2027 "cxx=c++20".to_owned(),
2028 "interface-c=none".to_owned(),
2029 "interface-cxx=c++17".to_owned(),
2030 "gnu-extensions=true".to_owned(),
2031 ]
2032 );
2033 }
2034
2035 #[test]
2036 fn standard_declaration_serde_is_a_bare_value_and_rejects_markers() {
2037 let declared: StandardDeclaration<CxxStandard> =
2038 StandardDeclaration::Declared(CxxStandard::Cxx20);
2039 let inherited: StandardDeclaration<CxxStandard> =
2040 StandardDeclaration::Inherited(CxxStandard::Cxx20);
2041 assert_eq!(serde_json::to_string(&declared).unwrap(), "\"c++20\"");
2042 assert_eq!(serde_json::to_string(&inherited).unwrap(), "\"c++20\"");
2043 let marker: StandardDeclaration<CxxStandard> = StandardDeclaration::Workspace;
2044 assert!(serde_json::to_string(&marker).is_err());
2045 let parsed: StandardDeclaration<CxxStandard> = serde_json::from_str("\"c++20\"").unwrap();
2046 assert_eq!(parsed, StandardDeclaration::Declared(CxxStandard::Cxx20));
2047
2048 let declared_interface: StandardDeclaration<InterfaceRequirement<CxxStandard>> =
2051 StandardDeclaration::Declared(requirement(CxxStandard::Cxx20));
2052 let json = serde_json::to_string(&declared_interface).unwrap();
2053 assert_eq!(json, r#"{"min":"c++20","max":null}"#);
2054 let parsed: StandardDeclaration<InterfaceRequirement<CxxStandard>> =
2055 serde_json::from_str(&json).unwrap();
2056 assert_eq!(
2057 parsed,
2058 StandardDeclaration::Declared(requirement(CxxStandard::Cxx20))
2059 );
2060 let parsed: StandardDeclaration<InterfaceRequirement<CxxStandard>> =
2061 serde_json::from_str("\"none\"").unwrap();
2062 assert_eq!(
2063 parsed,
2064 StandardDeclaration::Declared(InterfaceRequirement::None)
2065 );
2066 }
2067
2068 #[test]
2069 fn inherited_standard_resolves_with_workspace_source() {
2070 let settings = LanguageStandardSettings {
2071 cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx20)),
2072 ..Default::default()
2073 };
2074 let resolved = resolve_language_standards(&settings);
2075 let cxx = resolved.cxx.unwrap();
2076 assert_eq!(cxx.standard, CxxStandard::Cxx20);
2077 assert_eq!(cxx.source, LanguageStandardSource::Workspace);
2078 assert_eq!(resolved.c, None);
2079 }
2080
2081 #[test]
2082 fn inherited_interface_standard_resolves_with_workspace_source() {
2083 let settings = LanguageStandardSettings {
2084 cxx_standard: Some(StandardDeclaration::Declared(CxxStandard::Cxx20)),
2085 interface_cxx_standard: Some(StandardDeclaration::Inherited(requirement(
2086 CxxStandard::Cxx17,
2087 ))),
2088 ..Default::default()
2089 };
2090 let resolved = resolve_language_standards(&settings);
2091 let lib = target(
2092 TargetKind::Library,
2093 &["a.cc"],
2094 LanguageStandardSettings::default(),
2095 );
2096 let interface = interface_cxx(&resolved, &settings, &lib).unwrap();
2097 assert_eq!(interface.requirement, requirement(CxxStandard::Cxx17));
2098 assert_eq!(interface.source, InterfaceStandardSource::Workspace);
2099 }
2100
2101 #[test]
2102 fn inherited_values_behave_like_declarations_for_conflicts_and_relevance() {
2103 let flags = ResolvedProfileFlags {
2104 cxxflags: vec!["-std=c++20".to_owned()],
2105 ..Default::default()
2106 };
2107 let inherited = LanguageStandardSettings {
2108 cxx_standard: Some(StandardDeclaration::Inherited(CxxStandard::Cxx17)),
2109 ..Default::default()
2110 };
2111 assert_eq!(
2112 find_standard_flag_conflicts("p", &inherited, &[], &flags).len(),
2113 1
2114 );
2115
2116 let header_only = target(
2117 TargetKind::HeaderOnly,
2118 &[],
2119 LanguageStandardSettings::default(),
2120 );
2121 let pkg = LanguageStandardSettings {
2122 interface_cxx_standard: Some(StandardDeclaration::Inherited(requirement(
2123 CxxStandard::Cxx20,
2124 ))),
2125 ..Default::default()
2126 };
2127 assert!(imposes_requirement(&header_only, &pkg, SourceLanguage::Cxx));
2128 }
2129
2130 #[test]
2131 fn fingerprint_is_identical_for_declared_and_inherited_values() {
2132 use crate::{Package, PackageName};
2133 let make = |decl: StandardDeclaration<CxxStandard>| {
2134 let package = Package::new(
2135 PackageName::new("demo").unwrap(),
2136 semver::Version::parse("0.1.0").unwrap(),
2137 vec![target(
2138 TargetKind::Executable,
2139 &["main.cc"],
2140 LanguageStandardSettings::default(),
2141 )],
2142 Vec::new(),
2143 )
2144 .unwrap()
2145 .with_language(LanguageStandardSettings {
2146 cxx_standard: Some(decl),
2147 ..Default::default()
2148 });
2149 LanguageStandardsSummary::from_package(&package).fingerprint_lines()
2150 };
2151 assert_eq!(
2152 make(StandardDeclaration::Declared(CxxStandard::Cxx20)),
2153 make(StandardDeclaration::Inherited(CxxStandard::Cxx20))
2154 );
2155 }
2156
2157 #[test]
2158 fn workspace_marker_field_reports_the_first_marker() {
2159 assert_eq!(
2160 LanguageStandardSettings::default().workspace_marker_field(),
2161 None
2162 );
2163 let settings = LanguageStandardSettings {
2164 interface_c_standard: Some(StandardDeclaration::Workspace),
2165 ..Default::default()
2166 };
2167 assert_eq!(
2168 settings.workspace_marker_field(),
2169 Some("interface-c-standard")
2170 );
2171 let settings = LanguageStandardSettings {
2172 c_standard: Some(StandardDeclaration::Workspace),
2173 interface_c_standard: Some(StandardDeclaration::Workspace),
2174 ..Default::default()
2175 };
2176 assert_eq!(settings.workspace_marker_field(), Some("c-standard"));
2177 }
2178}