1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize};
// ---------------------------------------------------------------------------
// PartialConfig (split/merge CI fan-out)
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[serde(default, deny_unknown_fields)]
pub struct PartialConfig {
/// How to split builds: "os" (by OS, default) or "target" (by full triple).
/// "os" groups all arch variants for the same OS into one split job.
/// "target" gives each unique target triple its own split job.
///
/// The legacy `goos` spelling is accepted as a back-compat alias for `os`
/// (folded at parse time, with a deprecation warning); imported configs
/// keep loading.
#[serde(default, deserialize_with = "deserialize_partial_by")]
pub by: Option<String>,
}
/// Normalize `partial.by` at parse time so the legacy Go-style `goos` spelling
/// folds into the canonical `os` value. Any other value passes through
/// unchanged for the central validator (`config::validate_partial`) to accept
/// or reject. A deprecation warning fires when the alias is hit.
fn deserialize_partial_by<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let raw = Option::<String>::deserialize(deserializer)?;
Ok(raw.map(|v| {
if v == "goos" {
tracing::warn!(
"DEPRECATION: partial.by: 'goos' is renamed to 'os'. The 'goos' \
spelling still works but will be removed in a future release; \
switch to 'os'."
);
"os".to_string()
} else {
v
}
}))
}
#[cfg(test)]
mod by_alias_tests {
use super::*;
/// The legacy `goos` spelling folds into the canonical `os` at parse time
/// so imported configs keep loading.
#[test]
fn goos_alias_folds_into_os() {
let cfg: PartialConfig = serde_yaml_ng::from_str("by: goos").unwrap();
assert_eq!(cfg.by.as_deref(), Some("os"));
}
#[test]
fn canonical_os_passes_through() {
let cfg: PartialConfig = serde_yaml_ng::from_str("by: os").unwrap();
assert_eq!(cfg.by.as_deref(), Some("os"));
}
#[test]
fn target_passes_through() {
let cfg: PartialConfig = serde_yaml_ng::from_str("by: target").unwrap();
assert_eq!(cfg.by.as_deref(), Some("target"));
}
/// A genuinely unknown value is left intact for the central validator
/// (`config::validate_partial`) to reject — the alias step only rewrites
/// `goos`.
#[test]
fn unknown_value_passes_through_unchanged() {
let cfg: PartialConfig = serde_yaml_ng::from_str("by: bogus").unwrap();
assert_eq!(cfg.by.as_deref(), Some("bogus"));
}
}