Skip to main content

debcargo/
config.rs

1use indexmap::IndexMap;
2use serde::de::IgnoredAny;
3use serde::Deserialize;
4use toml;
5
6use crate::debian::control::PkgTestRestriction;
7use crate::errors::Result;
8
9use std::borrow::Cow;
10use std::collections::HashMap;
11use std::ffi::OsStr;
12use std::fs::File;
13use std::io::Read;
14use std::path::{Path, PathBuf};
15
16pub const RUST_MAINT: &str =
17    "Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>";
18
19pub const DEFAULT_REPACK_SUFFIX: &str = "ds";
20
21#[derive(Deserialize, Debug, Clone)]
22#[serde(default)]
23pub struct Config {
24    pub bin: Option<bool>,
25    pub bin_name: String,
26    pub lib: Option<bool>,
27    pub semver_suffix: bool,
28    pub overlay: Option<PathBuf>,
29    pub excludes: Option<Vec<String>>,
30    pub whitelist: Option<Vec<String>>,
31    pub repack_suffix: Option<String>,
32    pub allow_prerelease_deps: bool,
33    pub crate_src_path: Option<PathBuf>,
34    pub summary: Option<String>,
35    pub description: Option<String>,
36    pub maintainer: String,
37    pub uploaders: Option<Vec<String>>,
38    pub collapse_features: bool,
39    pub remove_features: Option<Vec<String>>,
40    pub remove_target_types: Option<Vec<TargetType>>,
41    pub filter_targets: Option<Vec<FilterTarget>>,
42    pub requires_root: Option<String>,
43
44    pub source: Option<SourceOverride>,
45    pub packages: IndexMap<String, PackageOverride>,
46
47    #[serde(flatten)]
48    pub unknown_fields: HashMap<String, IgnoredAny>,
49}
50
51#[derive(Deserialize, Debug, Clone, Default)]
52pub struct SourceOverride {
53    section: Option<String>,
54    policy: Option<String>,
55    homepage: Option<String>,
56    vcs_git: Option<String>,
57    vcs_browser: Option<String>,
58    build_depends: Option<Vec<String>>,
59    build_depends_arch: Option<Vec<String>>,
60    build_depends_indep: Option<Vec<String>>,
61    build_depends_excludes: Option<Vec<String>>,
62    build_features: Option<Vec<String>>,
63    skip_nocheck: Option<bool>,
64
65    #[serde(flatten)]
66    pub unknown_fields: HashMap<String, IgnoredAny>,
67}
68
69impl SourceOverride {
70    #[expect(clippy::too_many_arguments)]
71    pub fn new(
72        section: Option<String>,
73        policy: Option<String>,
74        homepage: Option<String>,
75        vcs_git: Option<String>,
76        vcs_browser: Option<String>,
77        build_depends: Option<Vec<String>>,
78        build_depends_arch: Option<Vec<String>>,
79        build_depends_indep: Option<Vec<String>>,
80        build_depends_excludes: Option<Vec<String>>,
81        build_features: Option<Vec<String>>,
82        skip_nocheck: Option<bool>,
83    ) -> Self {
84        Self {
85            section,
86            policy,
87            homepage,
88            vcs_git,
89            vcs_browser,
90            build_depends,
91            build_depends_arch,
92            build_depends_indep,
93            build_depends_excludes,
94            build_features,
95            skip_nocheck,
96            unknown_fields: HashMap::new(),
97        }
98    }
99}
100#[derive(Deserialize, Debug, Clone, Default)]
101pub struct PackageOverride {
102    section: Option<String>,
103    summary: Option<String>,
104    description: Option<String>,
105    architecture: Option<Vec<String>>,
106    multi_arch: Option<String>,
107    depends: Option<Vec<String>>,
108    recommends: Option<Vec<String>>,
109    suggests: Option<Vec<String>>,
110    provides: Option<Vec<String>>,
111    breaks: Option<Vec<String>>,
112    replaces: Option<Vec<String>>,
113    conflicts: Option<Vec<String>>,
114    extra_lines: Option<Vec<String>>,
115    test_is_broken: Option<bool>,
116    test_architecture: Option<Vec<String>>,
117    test_depends: Option<Vec<String>>,
118    test_restrictions: Option<Vec<PkgTestRestriction>>,
119    test_command: Option<String>,
120
121    #[serde(flatten)]
122    pub unknown_fields: HashMap<String, IgnoredAny>,
123}
124
125#[derive(Deserialize, Debug, Clone)]
126#[serde(rename_all = "lowercase")]
127/// Whether to automatically remove dependencies for certain targets
128pub enum FilterTarget {
129    /// Targets that do match any Debian release or ports architecture
130    Debian,
131    /// Targets that do not match WASM targets
132    Wasm,
133}
134impl FilterTarget {
135    pub(crate) fn target_list(&self) -> &[&str] {
136        match self {
137            Self::Debian => &[
138                // Release architectures
139                "aarch64-unknown-linux-gnu",
140                "armv7-unknown-linux-gnueabihf",
141                "i686-unknown-linux-gnu",
142                "loongarch64-unknown-linux-gnu",
143                "powerpc64le-unknown-linux-gnu",
144                "riscv64gc-unknown-linux-gnu",
145                "s390x-unknown-linux-gnu",
146                "x86_64-unknown-linux-gnu",
147                // Port architectures
148                "i686-unknown-hurd-gnu",
149                "m68k-unknown-linux-gnu", // soon ;)
150                "powerpc-unknown-linux-gnu",
151                "powerpc64-unknown-linux-gnu",
152                "sparc64-unknown-linux-gnu",
153                "x86_64-unknown-hurd-gnu",
154                "x86_64-unknown-linux-gnux32",
155            ],
156            Self::Wasm => &[
157                "wasm32-unknown-unknown",
158                "wasm32-wasip1",
159                "wasm32-wasip1-threads",
160                "wasm32-wasip2",
161                "wasm32-wasip3",
162            ],
163        }
164    }
165}
166
167#[derive(Deserialize, Debug, Clone)]
168#[serde(rename_all = "lowercase")]
169/// Simplified version of [`cargo::core::manifest::TargetKind`]
170pub enum TargetType {
171    /// Benchmark
172    Bench,
173    /// Example
174    Example,
175    /// Integration test
176    Test,
177}
178
179impl std::fmt::Display for TargetType {
180    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        match self {
182            TargetType::Bench => f.write_str("bench"),
183            TargetType::Example => f.write_str("example"),
184            TargetType::Test => f.write_str("test"),
185        }
186    }
187}
188
189impl Default for Config {
190    fn default() -> Self {
191        Config {
192            bin: None,
193            bin_name: "<default>".to_string(),
194            lib: None,
195            semver_suffix: false,
196            overlay: None,
197            excludes: None,
198            repack_suffix: None,
199            whitelist: None,
200            allow_prerelease_deps: false,
201            crate_src_path: None,
202            summary: None,
203            description: None,
204            maintainer: RUST_MAINT.to_string(),
205            uploaders: None,
206            collapse_features: true,
207            remove_features: None,
208            remove_target_types: None,
209            filter_targets: None,
210            source: None,
211            packages: IndexMap::new(),
212            requires_root: None,
213            unknown_fields: HashMap::new(),
214        }
215    }
216}
217
218impl Config {
219    pub fn parse(src: &Path) -> Result<Config> {
220        let mut config_file = File::open(src)?;
221        let mut content = String::new();
222        config_file.read_to_string(&mut content)?;
223
224        let config: Config = toml::from_str(&content)?;
225
226        let mut unknown_fields = Vec::new();
227
228        for field in config.unknown_fields.keys() {
229            unknown_fields.push(field.clone());
230        }
231
232        if let Some(ref source) = config.source {
233            for field in source.unknown_fields.keys() {
234                unknown_fields.push(format!("source.{field}"));
235            }
236        }
237
238        for field in config.packages.keys() {
239            if PackageKey::from_key(field).is_none() {
240                unknown_fields.push(format!("packages.{field}"));
241            }
242        }
243
244        for (name, package) in &config.packages {
245            for field in package.unknown_fields.keys() {
246                unknown_fields.push(format!("packages.{name}.{field}"));
247            }
248        }
249
250        if !unknown_fields.is_empty() {
251            debcargo_warn!(
252                "Warning: Unknown fields in {}: {:?}",
253                src.display(),
254                unknown_fields
255            );
256            debcargo_warn!("         These fields will be ignored. Please check for typos.");
257        }
258
259        Ok(config)
260    }
261
262    pub fn build_bin_package(&self) -> bool {
263        self.bin.unwrap_or(!self.semver_suffix)
264    }
265
266    pub fn build_lib_package(&self) -> bool {
267        self.lib.unwrap_or(true)
268    }
269
270    pub fn build_extra_package(&self) -> bool {
271        self.configured_packages()
272            .any(|p| matches!(p, PackageKey::Extra(_)))
273    }
274
275    pub fn overlay_dir(&self, config_path: Option<&Path>) -> Option<PathBuf> {
276        Some(config_path?.parent()?.join(self.overlay.as_ref()?))
277    }
278
279    pub fn crate_src_path(&self, config_path: Option<&Path>) -> Option<PathBuf> {
280        Some(config_path?.parent()?.join(self.crate_src_path.as_ref()?))
281    }
282
283    pub fn orig_tar_excludes(&self) -> Option<&Vec<String>> {
284        self.excludes.as_ref()
285    }
286
287    pub fn orig_tar_whitelist(&self) -> Option<&Vec<String>> {
288        self.whitelist.as_ref()
289    }
290
291    pub fn repack_suffix(&self) -> Option<&str> {
292        self.repack_suffix
293            .as_deref()
294            .or_else(|| self.orig_tar_excludes().and(Some(DEFAULT_REPACK_SUFFIX)))
295    }
296
297    pub fn maintainer(&self) -> &str {
298        self.maintainer.as_str()
299    }
300
301    pub fn uploaders(&self) -> Option<&Vec<String>> {
302        self.uploaders.as_ref()
303    }
304
305    pub fn requires_root(&self) -> Option<&String> {
306        self.requires_root.as_ref()
307    }
308
309    // Source shortcuts
310
311    pub fn section(&self) -> Option<&str> {
312        Some(self.source.as_ref()?.section.as_ref()?)
313    }
314
315    pub fn policy_version(&self) -> Option<&str> {
316        Some(self.source.as_ref()?.policy.as_ref()?)
317    }
318
319    pub fn homepage(&self) -> Option<&str> {
320        Some(self.source.as_ref()?.homepage.as_ref()?)
321    }
322
323    pub fn vcs_git(&self) -> Option<&str> {
324        Some(self.source.as_ref()?.vcs_git.as_ref()?)
325    }
326
327    pub fn vcs_browser(&self) -> Option<&str> {
328        Some(self.source.as_ref()?.vcs_browser.as_ref()?)
329    }
330
331    pub fn build_depends(&self) -> Option<&Vec<String>> {
332        self.source.as_ref()?.build_depends.as_ref()
333    }
334
335    pub fn build_depends_arch(&self) -> Option<&Vec<String>> {
336        self.source.as_ref()?.build_depends_arch.as_ref()
337    }
338
339    pub fn build_depends_indep(&self) -> Option<&Vec<String>> {
340        self.source.as_ref()?.build_depends_indep.as_ref()
341    }
342
343    pub fn build_depends_excludes(&self) -> Option<&Vec<String>> {
344        self.source.as_ref()?.build_depends_excludes.as_ref()
345    }
346
347    pub fn build_features(&self) -> Option<&Vec<String>> {
348        self.source.as_ref()?.build_features.as_ref()
349    }
350
351    pub fn skip_nocheck(&self) -> Option<bool> {
352        self.source.as_ref()?.skip_nocheck
353    }
354
355    // Packages accessors
356
357    pub fn configured_packages(&'_ self) -> impl Iterator<Item = PackageKey<'_>> {
358        self.packages.keys().filter_map(|k| PackageKey::from_key(k))
359    }
360
361    fn with_package<'a, T, F: FnOnce(&'a PackageOverride) -> Option<T>>(
362        &'a self,
363        key: PackageKey,
364        f: F,
365    ) -> Option<T> {
366        self.packages.get(&key.key_string()[..]).and_then(f)
367    }
368
369    pub fn package_section(&self, key: PackageKey) -> Option<&str> {
370        self.with_package(key, |pkg| pkg.section.as_deref())
371    }
372
373    pub fn package_summary(&self, key: PackageKey) -> Option<&str> {
374        self.with_package(key, |pkg| pkg.summary.as_deref())
375    }
376
377    pub fn package_description(&self, key: PackageKey) -> Option<&str> {
378        self.with_package(key, |pkg| pkg.description.as_deref())
379    }
380
381    pub fn package_architecture(&self, key: PackageKey) -> Option<&Vec<String>> {
382        self.with_package(key, |pkg| pkg.architecture.as_ref())
383    }
384
385    pub fn package_multi_arch(&self, key: PackageKey) -> Option<&str> {
386        self.with_package(key, |pkg| pkg.multi_arch.as_deref())
387    }
388
389    pub fn package_depends(&self, key: PackageKey) -> Option<&Vec<String>> {
390        self.with_package(key, |pkg| pkg.depends.as_ref())
391    }
392
393    pub fn package_recommends(&self, key: PackageKey) -> Option<&Vec<String>> {
394        self.with_package(key, |pkg| pkg.recommends.as_ref())
395    }
396
397    pub fn package_suggests(&self, key: PackageKey) -> Option<&Vec<String>> {
398        self.with_package(key, |pkg| pkg.suggests.as_ref())
399    }
400
401    pub fn package_provides(&self, key: PackageKey) -> Option<&Vec<String>> {
402        self.with_package(key, |pkg| pkg.provides.as_ref())
403    }
404
405    pub fn package_breaks(&self, key: PackageKey) -> Option<&Vec<String>> {
406        self.with_package(key, |pkg| pkg.breaks.as_ref())
407    }
408
409    pub fn package_replaces(&self, key: PackageKey) -> Option<&Vec<String>> {
410        self.with_package(key, |pkg| pkg.replaces.as_ref())
411    }
412
413    pub fn package_conflicts(&self, key: PackageKey) -> Option<&Vec<String>> {
414        self.with_package(key, |pkg| pkg.conflicts.as_ref())
415    }
416
417    pub fn package_extra_lines(&self, key: PackageKey) -> Option<&Vec<String>> {
418        self.with_package(key, |pkg| pkg.extra_lines.as_ref())
419    }
420
421    pub fn package_test_is_broken(&self, key: PackageKey) -> Option<bool> {
422        self.with_package(key, |pkg| pkg.test_is_broken)
423    }
424
425    pub fn package_test_restrictions(&self, key: PackageKey) -> Option<&Vec<PkgTestRestriction>> {
426        self.with_package(key, |pkg| pkg.test_restrictions.as_ref())
427    }
428
429    pub fn package_test_architecture(&self, key: PackageKey) -> Option<&Vec<String>> {
430        self.with_package(key, |pkg| pkg.test_architecture.as_ref())
431    }
432
433    pub fn package_test_depends(&self, key: PackageKey) -> Option<&Vec<String>> {
434        self.with_package(key, |pkg| pkg.test_depends.as_ref())
435    }
436
437    pub fn package_test_command(&self, key: PackageKey) -> Option<&String> {
438        self.with_package(key, |pkg| pkg.test_command.as_ref())
439    }
440}
441
442pub fn package_field_for_feature<'a, 'b, F: Fn(PackageKey) -> Option<&'a Vec<String>>>(
443    get_field: F,
444    feature: PackageKey<'b>,
445    f_provides: &'b [&'b str],
446) -> impl Iterator<Item = String> + use<'a, 'b, F> {
447    Some(feature)
448        .into_iter()
449        .chain(f_provides.iter().map(|s| PackageKey::feature(s)))
450        .flat_map(move |f| get_field(f).into_iter().flatten())
451        .map(ToString::to_string)
452}
453
454#[derive(Clone, Copy)]
455pub enum PackageKey<'a> {
456    Bin,
457    BareLib,
458    FeatureLib(&'a str),
459    Extra(&'a str),
460}
461
462impl<'a> PackageKey<'a> {
463    pub fn feature(f: &'a str) -> PackageKey<'a> {
464        use self::PackageKey::{BareLib, FeatureLib};
465        if f.is_empty() {
466            BareLib
467        } else {
468            FeatureLib(f)
469        }
470    }
471
472    pub fn from_key(k: &'a str) -> Option<PackageKey<'a>> {
473        use self::PackageKey::{BareLib, Bin, Extra, FeatureLib};
474        Some(match k {
475            "bin" => Bin,
476            "lib" => BareLib,
477            _ => {
478                if let Some(feature) = k.strip_prefix("lib+") {
479                    FeatureLib(feature)
480                } else {
481                    let package = k.strip_prefix("extra+")?;
482                    Extra(package)
483                }
484            }
485        })
486    }
487
488    fn key_string(&self) -> Cow<'static, str> {
489        use self::PackageKey::{BareLib, Bin, Extra, FeatureLib};
490        match self {
491            Bin => "bin".into(),
492            BareLib => "lib".into(),
493            FeatureLib(feature) => format!("lib+{feature}").into(),
494            Extra(package) => format!("extra+{package}").into(),
495        }
496    }
497}
498
499pub fn testing_ignore_debpolv() -> bool {
500    std::env::var_os("DEBCARGO_TESTING_IGNORE_DEBIAN_POLICY_VIOLATION").as_deref()
501        == Some(OsStr::new("1"))
502}
503
504pub fn testing_ruzt() -> bool {
505    std::env::var_os("DEBCARGO_TESTING_RUZT").as_deref() == Some(OsStr::new("1"))
506}