1#[expect(clippy::module_inception)]
23mod config;
24pub mod flags;
25pub mod target_selection;
26#[cfg(test)]
27mod tests;
28pub mod toml;
29
30use std::collections::HashSet;
31use std::path::PathBuf;
32
33use build_helper::exit;
34pub use config::*;
35use serde::{Deserialize, Deserializer};
36use serde_derive::Deserialize;
37pub use target_selection::TargetSelection;
38pub use toml::BUILDER_CONFIG_FILENAME;
39pub use toml::change_id::ChangeId;
40pub use toml::rust::LldMode;
41pub use toml::target::Target;
42
43use crate::Display;
44use crate::str::FromStr;
45
46#[macro_export]
48macro_rules! define_config {
49 ($(#[$attr:meta])* struct $name:ident {
50 $($field:ident: Option<$field_ty:ty> = $field_key:literal,)*
51 }) => {
52 $(#[$attr])*
53 pub struct $name {
54 $(pub $field: Option<$field_ty>,)*
55 }
56
57 impl Merge for $name {
58 fn merge(
59 &mut self,
60 _parent_config_path: Option<PathBuf>,
61 _included_extensions: &mut HashSet<PathBuf>,
62 other: Self,
63 replace: ReplaceOpt
64 ) {
65 $(
66 match replace {
67 ReplaceOpt::IgnoreDuplicate => {
68 if self.$field.is_none() {
69 self.$field = other.$field;
70 }
71 },
72 ReplaceOpt::Override => {
73 if other.$field.is_some() {
74 self.$field = other.$field;
75 }
76 }
77 ReplaceOpt::ErrorOnDuplicate => {
78 if other.$field.is_some() {
79 if self.$field.is_some() {
80 if cfg!(test) {
81 panic!("overriding existing option")
82 } else {
83 eprintln!("overriding existing option: `{}`", stringify!($field));
84 exit!(2);
85 }
86 } else {
87 self.$field = other.$field;
88 }
89 }
90 }
91 }
92 )*
93 }
94 }
95
96 impl<'de> Deserialize<'de> for $name {
100 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
101 where
102 D: Deserializer<'de>,
103 {
104 struct Field;
105 impl<'de> serde::de::Visitor<'de> for Field {
106 type Value = $name;
107 fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.write_str(concat!("struct ", stringify!($name)))
109 }
110
111 #[inline]
112 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
113 where
114 A: serde::de::MapAccess<'de>,
115 {
116 $(let mut $field: Option<$field_ty> = None;)*
117 while let Some(key) =
118 match serde::de::MapAccess::next_key::<String>(&mut map) {
119 Ok(val) => val,
120 Err(err) => {
121 return Err(err);
122 }
123 }
124 {
125 match &*key {
126 $($field_key => {
127 if $field.is_some() {
128 return Err(<A::Error as serde::de::Error>::duplicate_field(
129 $field_key,
130 ));
131 }
132 $field = match serde::de::MapAccess::next_value::<$field_ty>(
133 &mut map,
134 ) {
135 Ok(val) => Some(val),
136 Err(err) => {
137 return Err(err);
138 }
139 };
140 })*
141 key => {
142 return Err(serde::de::Error::unknown_field(key, FIELDS));
143 }
144 }
145 }
146 Ok($name { $($field),* })
147 }
148 }
149 const FIELDS: &'static [&'static str] = &[
150 $($field_key,)*
151 ];
152 Deserializer::deserialize_struct(
153 deserializer,
154 stringify!($name),
155 FIELDS,
156 Field,
157 )
158 }
159 }
160 }
161}
162
163#[macro_export]
164macro_rules! check_ci_llvm {
165 ($name:expr) => {
166 assert!(
167 $name.is_none(),
168 "setting {} is incompatible with download-ci-llvm.",
169 stringify!($name).replace("_", "-")
170 );
171 };
172}
173
174pub(crate) trait Merge {
175 fn merge(
176 &mut self,
177 parent_config_path: Option<PathBuf>,
178 included_extensions: &mut HashSet<PathBuf>,
179 other: Self,
180 replace: ReplaceOpt,
181 );
182}
183
184impl<T> Merge for Option<T> {
185 fn merge(
186 &mut self,
187 _parent_config_path: Option<PathBuf>,
188 _included_extensions: &mut HashSet<PathBuf>,
189 other: Self,
190 replace: ReplaceOpt,
191 ) {
192 match replace {
193 ReplaceOpt::IgnoreDuplicate => {
194 if self.is_none() {
195 *self = other;
196 }
197 }
198 ReplaceOpt::Override => {
199 if other.is_some() {
200 *self = other;
201 }
202 }
203 ReplaceOpt::ErrorOnDuplicate => {
204 if other.is_some() {
205 if self.is_some() {
206 if cfg!(test) {
207 panic!("overriding existing option")
208 } else {
209 eprintln!("overriding existing option");
210 exit!(2);
211 }
212 } else {
213 *self = other;
214 }
215 }
216 }
217 }
218 }
219}
220
221#[derive(Clone, Debug, Default, Eq, PartialEq)]
222pub enum CompilerBuiltins {
223 #[default]
224 BuildRustOnly,
226 BuildLLVMFuncs,
230 LinkLLVMBuiltinsLib(String),
233}
234
235impl<'de> Deserialize<'de> for CompilerBuiltins {
236 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237 where
238 D: Deserializer<'de>,
239 {
240 Ok(match Deserialize::deserialize(deserializer)? {
241 StringOrBool::Bool(false) => Self::BuildRustOnly,
242 StringOrBool::Bool(true) => Self::BuildLLVMFuncs,
243 StringOrBool::String(path) => Self::LinkLLVMBuiltinsLib(path),
244 })
245 }
246}
247
248#[derive(Copy, Clone, Default, Debug, Eq, PartialEq)]
249pub enum DebuginfoLevel {
250 #[default]
251 None,
252 LineDirectivesOnly,
253 LineTablesOnly,
254 Limited,
255 Full,
256}
257
258impl<'de> Deserialize<'de> for DebuginfoLevel {
261 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262 where
263 D: Deserializer<'de>,
264 {
265 use serde::de::Error;
266
267 Ok(match Deserialize::deserialize(deserializer)? {
268 StringOrInt::String(s) if s == "none" => DebuginfoLevel::None,
269 StringOrInt::Int(0) => DebuginfoLevel::None,
270 StringOrInt::String(s) if s == "line-directives-only" => {
271 DebuginfoLevel::LineDirectivesOnly
272 }
273 StringOrInt::String(s) if s == "line-tables-only" => DebuginfoLevel::LineTablesOnly,
274 StringOrInt::String(s) if s == "limited" => DebuginfoLevel::Limited,
275 StringOrInt::Int(1) => DebuginfoLevel::Limited,
276 StringOrInt::String(s) if s == "full" => DebuginfoLevel::Full,
277 StringOrInt::Int(2) => DebuginfoLevel::Full,
278 StringOrInt::Int(n) => {
279 let other = serde::de::Unexpected::Signed(n);
280 return Err(D::Error::invalid_value(other, &"expected 0, 1, or 2"));
281 }
282 StringOrInt::String(s) => {
283 let other = serde::de::Unexpected::Str(&s);
284 return Err(D::Error::invalid_value(
285 other,
286 &"expected none, line-tables-only, limited, or full",
287 ));
288 }
289 })
290 }
291}
292
293impl Display for DebuginfoLevel {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 use DebuginfoLevel::*;
297 f.write_str(match self {
298 None => "0",
299 LineDirectivesOnly => "line-directives-only",
300 LineTablesOnly => "line-tables-only",
301 Limited => "1",
302 Full => "2",
303 })
304 }
305}
306
307#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
308#[serde(untagged)]
309pub enum StringOrBool {
310 String(String),
311 Bool(bool),
312}
313
314impl Default for StringOrBool {
315 fn default() -> StringOrBool {
316 StringOrBool::Bool(false)
317 }
318}
319
320impl StringOrBool {
321 pub fn is_string_or_true(&self) -> bool {
322 matches!(self, Self::String(_) | Self::Bool(true))
323 }
324}
325
326#[derive(Deserialize)]
327#[serde(untagged)]
328pub enum StringOrInt {
329 String(String),
330 Int(i64),
331}
332
333#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
334pub enum LlvmLibunwind {
335 #[default]
336 No,
337 InTree,
338 System,
339}
340
341impl FromStr for LlvmLibunwind {
342 type Err = String;
343
344 fn from_str(value: &str) -> Result<Self, Self::Err> {
345 match value {
346 "no" => Ok(Self::No),
347 "in-tree" => Ok(Self::InTree),
348 "system" => Ok(Self::System),
349 invalid => Err(format!("Invalid value '{invalid}' for rust.llvm-libunwind config.")),
350 }
351 }
352}
353
354#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
355pub enum SplitDebuginfo {
356 Packed,
357 Unpacked,
358 #[default]
359 Off,
360}
361
362impl std::str::FromStr for SplitDebuginfo {
363 type Err = ();
364
365 fn from_str(s: &str) -> Result<Self, Self::Err> {
366 match s {
367 "packed" => Ok(SplitDebuginfo::Packed),
368 "unpacked" => Ok(SplitDebuginfo::Unpacked),
369 "off" => Ok(SplitDebuginfo::Off),
370 _ => Err(()),
371 }
372 }
373}
374
375#[derive(Copy, Clone, Debug)]
377pub enum ReplaceOpt {
378 IgnoreDuplicate,
380 Override,
382 ErrorOnDuplicate,
384}
385
386#[derive(Clone, Default)]
387pub enum DryRun {
388 #[default]
390 Disabled,
391 SelfCheck,
393 UserSelected,
395}
396
397#[derive(Default, Clone, PartialEq, Debug)]
399pub enum RustcLto {
400 Off,
401 #[default]
402 ThinLocal,
403 Thin,
404 Fat,
405}
406
407impl std::str::FromStr for RustcLto {
408 type Err = String;
409
410 fn from_str(s: &str) -> Result<Self, Self::Err> {
411 match s {
412 "thin-local" => Ok(RustcLto::ThinLocal),
413 "thin" => Ok(RustcLto::Thin),
414 "fat" => Ok(RustcLto::Fat),
415 "off" => Ok(RustcLto::Off),
416 _ => Err(format!("Invalid value for rustc LTO: {s}")),
417 }
418 }
419}
420
421#[derive(Default, Clone)]
423pub enum GccCiMode {
424 BuildLocally,
426 #[default]
429 DownloadFromCi,
430}
431
432pub fn threads_from_config(v: u32) -> u32 {
433 match v {
434 0 => std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get) as u32,
435 n => n,
436 }
437}