1use std::fmt;
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct Target(pub String);
11
12impl Target {
13 #[must_use]
15 pub fn short(&self) -> String {
16 let parts: Vec<&str> = self.0.split('-').collect();
17 match parts.as_slice() {
18 [arch, _vendor, os, ..] => format!("{arch}-{os}"),
19 [arch, os] => format!("{arch}-{os}"),
20 _ => self.0.clone(),
21 }
22 }
23}
24
25impl fmt::Display for Target {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str(&self.0)
28 }
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub struct RustcVersion {
34 pub version: semver::Version,
36 pub commit_hash: String,
38 pub llvm_version: String,
40}
41
42impl RustcVersion {
43 #[must_use]
45 pub fn short(&self) -> String {
46 self.version.to_string()
47 }
48}
49
50impl fmt::Display for RustcVersion {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 write!(f, "{} ({})", self.version, self.commit_hash)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, utoipa::ToSchema)]
58pub struct Profile {
59 pub opt_level: String,
61 pub debuginfo: u32,
63 pub debug_assertions: bool,
65 pub overflow_checks: bool,
67 pub panic: PanicStrategy,
69 #[serde(default, skip_serializing_if = "StripLevel::is_none")]
74 pub strip: StripLevel,
75}
76
77impl Profile {
78 #[must_use]
88 pub fn divergence(&self, requested: &Self) -> Option<(String, String)> {
89 let mut cached = Vec::new();
90 let mut wanted = Vec::new();
91 let mut note = |field: &str, mine: String, theirs: String| {
92 if mine != theirs {
93 cached.push(format!("{field}={mine}"));
94 wanted.push(format!("{field}={theirs}"));
95 }
96 };
97 note(
98 "opt-level",
99 self.opt_level.clone(),
100 requested.opt_level.clone(),
101 );
102 note(
103 "debuginfo",
104 self.debuginfo.to_string(),
105 requested.debuginfo.to_string(),
106 );
107 note(
108 "debug-assertions",
109 self.debug_assertions.to_string(),
110 requested.debug_assertions.to_string(),
111 );
112 note(
113 "overflow-checks",
114 self.overflow_checks.to_string(),
115 requested.overflow_checks.to_string(),
116 );
117 note(
118 "panic",
119 format!("{:?}", self.panic).to_lowercase(),
120 format!("{:?}", requested.panic).to_lowercase(),
121 );
122 note(
123 "strip",
124 format!("{:?}", self.strip).to_lowercase(),
125 format!("{:?}", requested.strip).to_lowercase(),
126 );
127 if cached.is_empty() {
128 return None;
129 }
130 Some((cached.join(", "), wanted.join(", ")))
131 }
132
133 #[must_use]
135 pub fn is_debug(&self) -> bool {
136 self.opt_level == "0" && self.debug_assertions
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, utoipa::ToSchema)]
142pub enum PanicStrategy {
143 Unwind,
145 Abort,
147}
148
149impl PanicStrategy {
150 #[must_use]
152 pub const fn as_str(&self) -> &str {
153 match self {
154 Self::Unwind => "unwind",
155 Self::Abort => "abort",
156 }
157 }
158}
159
160impl fmt::Display for PanicStrategy {
161 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
162 f.write_str(self.as_str())
163 }
164}
165
166#[derive(
172 Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize, utoipa::ToSchema,
173)]
174#[serde(rename_all = "lowercase")]
175pub enum StripLevel {
176 #[default]
178 None,
179 Debuginfo,
181 Symbols,
183}
184
185impl StripLevel {
186 #[must_use]
188 pub const fn as_str(&self) -> &str {
189 match self {
190 Self::None => "none",
191 Self::Debuginfo => "debuginfo",
192 Self::Symbols => "symbols",
193 }
194 }
195
196 #[must_use]
198 pub const fn is_none(&self) -> bool {
199 matches!(self, Self::None)
200 }
201}
202
203impl fmt::Display for StripLevel {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 f.write_str(self.as_str())
206 }
207}
208
209#[cfg(test)]
210mod profile_divergence_tests {
211 use super::{PanicStrategy, Profile, StripLevel};
212
213 fn dev() -> Profile {
214 Profile {
215 opt_level: "0".to_owned(),
216 debuginfo: 2,
217 debug_assertions: true,
218 overflow_checks: true,
219 panic: PanicStrategy::Unwind,
220 strip: StripLevel::None,
221 }
222 }
223
224 #[test]
225 fn equal_profiles_do_not_diverge() {
226 assert_eq!(dev().divergence(&dev()), None);
227 }
228
229 #[test]
230 fn only_the_diverging_fields_are_named() {
231 let requested = Profile {
232 debuginfo: 1,
233 ..dev()
234 };
235 assert_eq!(
236 dev().divergence(&requested),
237 Some(("debuginfo=2".to_owned(), "debuginfo=1".to_owned()))
238 );
239 }
240
241 #[test]
242 fn several_diverging_fields_stay_in_parallel() {
243 let requested = Profile {
244 opt_level: "3".to_owned(),
245 debuginfo: 0,
246 ..dev()
247 };
248 assert_eq!(
249 dev().divergence(&requested),
250 Some((
251 "opt-level=0, debuginfo=2".to_owned(),
252 "opt-level=3, debuginfo=0".to_owned()
253 ))
254 );
255 }
256}