Skip to main content

debian_control/lossy/
apt.rs

1//! APT related structures
2use crate::lossy::{Relations, SourceRelation};
3use deb822_fast::{FromDeb822, FromDeb822Paragraph, ToDeb822, ToDeb822Paragraph};
4
5fn deserialize_yesno(s: &str) -> Result<bool, String> {
6    match s {
7        "yes" => Ok(true),
8        "no" => Ok(false),
9        _ => Err(format!("invalid value for yesno: {}", s)),
10    }
11}
12
13fn serialize_yesno(b: &bool) -> String {
14    if *b {
15        "yes".to_string()
16    } else {
17        "no".to_string()
18    }
19}
20
21fn deserialize_components(value: &str) -> Result<Vec<String>, String> {
22    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
23}
24
25fn join_whitespace(components: &[String]) -> String {
26    components.join(" ")
27}
28
29fn deserialize_architectures(value: &str) -> Result<Vec<String>, String> {
30    Ok(value.split_whitespace().map(|s| s.to_string()).collect())
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
34/// A Release file
35pub struct Release {
36    #[deb822(field = "Codename")]
37    /// The codename of the release
38    pub codename: String,
39
40    #[deb822(
41        field = "Components",
42        deserialize_with = deserialize_components,
43        serialize_with = join_whitespace
44    )]
45    /// Components supported by the release
46    pub components: Vec<String>,
47
48    #[deb822(
49        field = "Architectures",
50        deserialize_with = deserialize_architectures,
51        serialize_with = join_whitespace
52    )]
53    /// Architectures supported by the release
54    pub architectures: Vec<String>,
55
56    #[deb822(field = "Description")]
57    /// Description of the release
58    pub description: String,
59
60    #[deb822(field = "Origin")]
61    /// Origin of the release
62    pub origin: String,
63
64    #[deb822(field = "Label")]
65    /// Label of the release
66    pub label: String,
67
68    #[deb822(field = "Suite")]
69    /// Suite of the release
70    pub suite: String,
71
72    #[deb822(field = "Version")]
73    /// Version of the release
74    pub version: String,
75
76    #[deb822(field = "Date")]
77    /// Date the release was published
78    pub date: String,
79
80    #[deb822(field = "NotAutomatic", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
81    /// Whether the release is not automatic
82    pub not_automatic: bool,
83
84    #[deb822(field = "ButAutomaticUpgrades", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
85    /// Indicates if packages retrieved from this release should be automatically upgraded
86    pub but_automatic_upgrades: bool,
87
88    #[deb822(field = "Acquire-By-Hash", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
89    /// Whether packages files can be acquired by hash
90    pub acquire_by_hash: bool,
91}
92
93fn deserialize_binaries(value: &str) -> Result<Vec<String>, String> {
94    Ok(value.split(",").map(|s| s.trim().to_string()).collect())
95}
96
97fn deserialize_testsuite_triggers(value: &str) -> Result<Vec<String>, String> {
98    Ok(value.split(",").map(|s| s.trim().to_string()).collect())
99}
100
101fn join_lines(components: &[String]) -> String {
102    components.join("\n")
103}
104
105fn deserialize_package_list(value: &str) -> Result<Vec<String>, String> {
106    Ok(value.split('\n').map(|s| s.to_string()).collect())
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
110/// A source
111pub struct Source {
112    #[deb822(field = "Directory")]
113    /// The directory of the source
114    pub directory: String,
115
116    #[deb822(field = "Description")]
117    /// Description of the source
118    pub description: Option<String>,
119
120    #[deb822(field = "Version")]
121    /// Version of the source
122    pub version: debversion::Version,
123
124    #[deb822(field = "Package")]
125    /// Package of the source
126    pub package: String,
127
128    #[deb822(field = "Binary", deserialize_with = deserialize_binaries, serialize_with = join_whitespace)]
129    /// Binaries of the source
130    pub binaries: Option<Vec<String>>,
131
132    #[deb822(field = "Maintainer")]
133    /// Maintainer of the source
134    pub maintainer: Option<String>,
135
136    #[deb822(field = "Build-Depends")]
137    /// Build dependencies of the source
138    pub build_depends: Option<Relations>,
139
140    #[deb822(field = "Build-Depends-Indep")]
141    /// Build dependencies independent of the architecture of the source
142    pub build_depends_indep: Option<Relations>,
143
144    #[deb822(field = "Build-Depends-Arch")]
145    /// Build dependencies dependent on the architecture of the source
146    pub build_depends_arch: Option<Relations>,
147
148    #[deb822(field = "Build-Conflicts")]
149    /// Build conflicts of the source
150    pub build_conflicts: Option<Relations>,
151
152    #[deb822(field = "Build-Conflicts-Indep")]
153    /// Build conflicts independent of the architecture of the source
154    pub build_conflicts_indep: Option<Relations>,
155
156    #[deb822(field = "Build-Conflicts-Arch")]
157    /// Build conflicts dependent on the architecture of the source
158    pub build_conflicts_arch: Option<Relations>,
159
160    #[deb822(field = "Standards-Version")]
161    /// Standards version of the source
162    pub standards_version: Option<String>,
163
164    #[deb822(field = "Homepage")]
165    /// Homepage of the source
166    pub homepage: Option<String>,
167
168    #[deb822(field = "Autobuild")]
169    /// Whether the source should be autobuilt
170    pub autobuild: Option<bool>,
171
172    #[deb822(field = "Testsuite")]
173    /// Testsuite of the source
174    pub testsuite: Option<String>,
175
176    #[deb822(field = "Testsuite-Triggers", deserialize_with = deserialize_testsuite_triggers, serialize_with = join_whitespace)]
177    /// The packages triggering the testsuite of the source
178    pub testsuite_triggers: Option<Vec<String>>,
179
180    #[deb822(field = "Vcs-Browser")]
181    /// VCS browser of the source
182    pub vcs_browser: Option<String>,
183
184    #[deb822(field = "Vcs-Git")]
185    /// VCS Git of the source
186    pub vcs_git: Option<String>,
187
188    #[deb822(field = "Vcs-Bzr")]
189    /// VCS Bzr of the source
190    pub vcs_bzr: Option<String>,
191
192    #[deb822(field = "Vcs-Hg")]
193    /// VCS Hg of the source
194    pub vcs_hg: Option<String>,
195
196    #[deb822(field = "Vcs-Svn")]
197    /// VCS SVN of the source
198    pub vcs_svn: Option<String>,
199
200    #[deb822(field = "Vcs-Darcs")]
201    /// VCS Darcs of the source
202    pub vcs_darcs: Option<String>,
203
204    #[deb822(field = "Vcs-Cvs")]
205    /// VCS CVS of the source
206    pub vcs_cvs: Option<String>,
207
208    #[deb822(field = "Vcs-Arch")]
209    /// VCS Arch of the source
210    pub vcs_arch: Option<String>,
211
212    #[deb822(field = "Vcs-Mtn")]
213    /// VCS Mtn of the source
214    pub vcs_mtn: Option<String>,
215
216    #[deb822(field = "Dgit")]
217    /// Dgit information (commit, suite, ref, url)
218    pub dgit: Option<crate::fields::DgitInfo>,
219
220    #[deb822(field = "Priority")]
221    /// Priority of the source
222    pub priority: Option<crate::fields::Priority>,
223
224    #[deb822(field = "Section")]
225    /// Section of the source
226    pub section: Option<String>,
227
228    #[deb822(field = "Format")]
229    /// Format of the source
230    pub format: Option<String>,
231
232    #[deb822(field = "Package-List", deserialize_with = deserialize_package_list, serialize_with = join_lines)]
233    /// Package list of the source
234    pub package_list: Vec<String>,
235}
236
237impl std::str::FromStr for Source {
238    type Err = String;
239
240    fn from_str(s: &str) -> Result<Self, Self::Err> {
241        let para = s
242            .parse::<deb822_fast::Paragraph>()
243            .map_err(|e| e.to_string())?;
244
245        FromDeb822Paragraph::from_paragraph(&para)
246    }
247}
248
249impl std::fmt::Display for Source {
250    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
251        let para: deb822_fast::Paragraph = self.to_paragraph();
252        write!(f, "{}", para)
253    }
254}
255
256/// A package
257#[derive(Debug, Clone, PartialEq, Eq, ToDeb822, FromDeb822)]
258pub struct Package {
259    /// The name of the package
260    #[deb822(field = "Package")]
261    pub name: String,
262
263    /// The version of the package
264    #[deb822(field = "Version")]
265    pub version: debversion::Version,
266
267    /// The name and version of the source package, if different from `name`
268    #[deb822(field = "Source")]
269    pub source: Option<SourceRelation>,
270
271    /// The architecture of the package
272    #[deb822(field = "Architecture")]
273    pub architecture: String,
274
275    /// The maintainer of the package
276    #[deb822(field = "Maintainer")]
277    pub maintainer: Option<String>,
278
279    /// The installed size of the package
280    #[deb822(field = "Installed-Size")]
281    pub installed_size: Option<usize>,
282
283    /// Dependencies
284    #[deb822(field = "Depends")]
285    pub depends: Option<Relations>,
286
287    /// Pre-Depends
288    #[deb822(field = "Pre-Depends")]
289    pub pre_depends: Option<Relations>,
290
291    /// Recommends
292    #[deb822(field = "Recommends")]
293    pub recommends: Option<Relations>,
294
295    /// Suggests
296    #[deb822(field = "Suggests")]
297    pub suggests: Option<Relations>,
298
299    /// Enhances
300    #[deb822(field = "Enhances")]
301    pub enhances: Option<Relations>,
302
303    /// Breaks
304    #[deb822(field = "Breaks")]
305    pub breaks: Option<Relations>,
306
307    /// Conflicts
308    #[deb822(field = "Conflicts")]
309    pub conflicts: Option<Relations>,
310
311    /// Provides
312    #[deb822(field = "Provides")]
313    pub provides: Option<Relations>,
314
315    /// Replaces
316    #[deb822(field = "Replaces")]
317    pub replaces: Option<Relations>,
318
319    /// Built-Using
320    #[deb822(field = "Built-Using")]
321    pub built_using: Option<Relations>,
322
323    /// Static-Built-Using
324    #[deb822(field = "Static-Built-Using")]
325    pub static_built_using: Option<Relations>,
326
327    /// Description
328    #[deb822(field = "Description")]
329    pub description: Option<String>,
330
331    /// Homepage
332    #[deb822(field = "Homepage")]
333    pub homepage: Option<String>,
334
335    /// Priority
336    #[deb822(field = "Priority")]
337    pub priority: Option<crate::fields::Priority>,
338
339    /// Section
340    #[deb822(field = "Section")]
341    pub section: Option<String>,
342
343    /// Essential
344    #[deb822(field = "Essential", deserialize_with = deserialize_yesno, serialize_with = serialize_yesno)]
345    pub essential: Option<bool>,
346
347    /// Tag
348    #[deb822(field = "Tag")]
349    pub tag: Option<String>,
350
351    /// Size
352    #[deb822(field = "Size")]
353    pub size: Option<usize>,
354
355    /// MD5sum
356    #[deb822(field = "MD5sum")]
357    pub md5sum: Option<String>,
358
359    /// SHA256
360    #[deb822(field = "SHA256")]
361    pub sha256: Option<String>,
362
363    /// Description (MD5)
364    #[deb822(field = "Description-MD5")]
365    pub description_md5: Option<String>,
366}
367
368impl std::str::FromStr for Package {
369    type Err = String;
370
371    fn from_str(s: &str) -> Result<Self, Self::Err> {
372        let para = s
373            .parse::<deb822_fast::Paragraph>()
374            .map_err(|e| e.to_string())?;
375
376        FromDeb822Paragraph::from_paragraph(&para)
377    }
378}
379
380impl std::fmt::Display for Package {
381    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
382        let para: deb822_fast::Paragraph = self.to_paragraph();
383        write!(f, "{}", para)
384    }
385}
386
387#[cfg(test)]
388mod tests {
389    use super::*;
390    use deb822_fast::Paragraph;
391    use deb822_fast::ToDeb822Paragraph;
392
393    #[test]
394    fn test_release() {
395        let release = Release {
396            codename: "focal".to_string(),
397            components: vec!["main".to_string(), "restricted".to_string()],
398            architectures: vec!["amd64".to_string(), "arm64".to_string()],
399            description: "Ubuntu 20.04 LTS".to_string(),
400            origin: "Ubuntu".to_string(),
401            label: "Ubuntu".to_string(),
402            suite: "focal".to_string(),
403            version: "20.04".to_string(),
404            date: "Thu, 23 Apr 2020 17:19:19 UTC".to_string(),
405            not_automatic: false,
406            but_automatic_upgrades: true,
407            acquire_by_hash: true,
408        };
409
410        let deb822 = r#"Codename: focal
411Components: main restricted
412Architectures: amd64 arm64
413Description: Ubuntu 20.04 LTS
414Origin: Ubuntu
415Label: Ubuntu
416Suite: focal
417Version: 20.04
418Date: Thu, 23 Apr 2020 17:19:19 UTC
419NotAutomatic: no
420ButAutomaticUpgrades: yes
421Acquire-By-Hash: yes
422"#;
423
424        let para = deb822.parse::<Paragraph>().unwrap();
425
426        let release: deb822_fast::Paragraph = release.to_paragraph();
427
428        assert_eq!(release, para);
429    }
430
431    #[test]
432    fn test_package() {
433        let package = r#"Package: apt
434Version: 2.1.10
435Architecture: amd64
436Maintainer: APT Development Team <apt@lists.debian.org>
437Installed-Size: 3524
438Depends: libc6 (>= 2.14), libgcc1
439Pre-Depends: dpkg (>= 1.15.6)
440Recommends: gnupg
441Suggests: apt-doc, aptitude | synaptic | wajig
442"#;
443
444        let package: Package = package.parse().unwrap();
445
446        assert_eq!(package.name, "apt");
447        assert_eq!(package.version, "2.1.10".parse().unwrap());
448        assert_eq!(package.architecture, "amd64");
449    }
450
451    #[test]
452    fn test_package_essential() {
453        let package = r#"Package: base-files
454Version: 11.1
455Architecture: amd64
456Essential: yes
457"#;
458
459        let package: Package = package.parse().unwrap();
460
461        assert_eq!(package.name, "base-files");
462        assert_eq!(package.essential, Some(true));
463    }
464
465    #[test]
466    fn test_package_essential_no() {
467        let package = r#"Package: apt
468Version: 2.1.10
469Architecture: amd64
470Essential: no
471"#;
472
473        let package: Package = package.parse().unwrap();
474
475        assert_eq!(package.name, "apt");
476        assert_eq!(package.essential, Some(false));
477    }
478
479    #[test]
480    fn test_package_with_different_source() {
481        let package = r#"Package: apt
482Source: not-apt (1.1.5)
483Version: 2.1.10
484Architecture: amd64
485Maintainer: APT Development Team <apt@lists.debian.org>
486Installed-Size: 3524
487Depends: libc6 (>= 2.14), libgcc1
488Pre-Depends: dpkg (>= 1.15.6)
489Recommends: gnupg
490Suggests: apt-doc, aptitude | synaptic | wajig
491"#;
492
493        let package: Package = package.parse().unwrap();
494
495        assert_eq!(package.name, "apt");
496        assert_eq!(package.version, "2.1.10".parse().unwrap());
497        assert_eq!(package.architecture, "amd64");
498        assert_eq!(
499            package.source,
500            Some(SourceRelation {
501                name: "not-apt".to_string(),
502                version: Some("1.1.5".parse().unwrap())
503            })
504        );
505    }
506
507    #[test]
508    fn test_source() {
509        let source = r#"Package: abinit
510Binary: abinit, abinit-doc, abinit-data
511Version: 9.10.4-3
512Maintainer: Debichem Team <debichem-devel@lists.alioth.debian.org>
513Uploaders: Andreas Tille <tille@debian.org>, Michael Banck <mbanck@debian.org>
514Build-Depends: debhelper (>= 11), gfortran, liblapack-dev, python3, graphviz, markdown, ghostscript, help2man, libfftw3-dev, libhdf5-dev, libnetcdff-dev, libssl-dev, libxc-dev, mpi-default-dev, python3-dev, python3-numpy, python3-pandas, python3-yaml, texlive-latex-extra, texlive-fonts-recommended, texlive-extra-utils, texlive-pstricks, texlive-publishers, texlive-luatex
515Architecture: any all
516Standards-Version: 3.9.8
517Format: 3.0 (quilt)
518Files:
519 843550cbd14395c0b9408158a91a239c 2464 abinit_9.10.4-3.dsc
520 a323f11fbd4a7d0f461d99c931903b5c 130747285 abinit_9.10.4.orig.tar.gz
521 27c12d3dac5cd105cebaa2af4247e807 15068 abinit_9.10.4-3.debian.tar.xz
522Vcs-Browser: https://salsa.debian.org/debichem-team/abinit
523Vcs-Git: https://salsa.debian.org/debichem-team/abinit.git
524Checksums-Sha256:
525 c3c217b14bc5705a1d8930a2e7fcef58e64beaa22abc213e2eacc7d5537ef840 2464 abinit_9.10.4-3.dsc
526 6bf3c276c333956f722761f189f2b4324e150c8a50470ecb72ee07cc1c457d48 130747285 abinit_9.10.4.orig.tar.gz
527 80c4fb7575d67f3167d7c34fd59477baf839810d0b863e19f1dd9fea1bc0b3b5 15068 abinit_9.10.4-3.debian.tar.xz
528Homepage: http://www.abinit.org/
529Package-List: 
530 abinit deb science optional arch=any
531 abinit-data deb science optional arch=all
532 abinit-doc deb doc optional arch=all
533Testsuite: autopkgtest
534Testsuite-Triggers: python3, python3-numpy, python3-pandas, python3-yaml
535Directory: pool/main/a/abinit
536Priority: optional
537Section: science
538"#;
539
540        let source: Source = source.parse().unwrap();
541
542        assert_eq!(source.package, "abinit");
543        assert_eq!(source.version, "9.10.4-3".parse().unwrap());
544        assert_eq!(
545            source.binaries,
546            Some(vec![
547                "abinit".to_string(),
548                "abinit-doc".to_string(),
549                "abinit-data".to_string()
550            ])
551        );
552
553        let build_depends = source.build_depends.as_ref();
554        let build_depends: Vec<_> = build_depends.iter().collect();
555        let build_depends = build_depends[0];
556
557        let expected_build_depends = &[
558            "debhelper",
559            "gfortran",
560            "liblapack-dev",
561            "python3",
562            "graphviz",
563            "markdown",
564            "ghostscript",
565            "help2man",
566            "libfftw3-dev",
567            "libhdf5-dev",
568            "libnetcdff-dev",
569            "libssl-dev",
570            "libxc-dev",
571            "mpi-default-dev",
572            "python3-dev",
573            "python3-numpy",
574            "python3-pandas",
575            "python3-yaml",
576            "texlive-latex-extra",
577            "texlive-fonts-recommended",
578            "texlive-extra-utils",
579            "texlive-pstricks",
580            "texlive-publishers",
581            "texlive-luatex",
582        ];
583
584        assert_eq!(build_depends.len(), expected_build_depends.len());
585        assert_eq!(build_depends[0][0].name, expected_build_depends[0]);
586        assert_eq!(
587            build_depends[build_depends.len() - 1][0].name,
588            expected_build_depends[build_depends.len() - 1]
589        );
590
591        assert_eq!(
592            source.testsuite_triggers,
593            Some(
594                ["python3", "python3-numpy", "python3-pandas", "python3-yaml"]
595                    .into_iter()
596                    .map(String::from)
597                    .collect()
598            )
599        );
600    }
601
602    #[test]
603    fn test_source_with_dgit() {
604        let source = r#"Package: test-pkg
605Version: 1.0-1
606Maintainer: Test Maintainer <test@example.com>
607Dgit: c1370424e2404d3c22bd09c828d4b28d81d897ad debian archive/debian/1.1.0 https://git.dgit.debian.org/test-pkg
608Directory: pool/main/t/test-pkg
609Package-List: 
610 abinit deb science optional arch=any
611 abinit-data deb science optional arch=all
612 abinit-doc deb doc optional arch=all
613"#;
614
615        let source: Source = source.parse().unwrap();
616        assert_eq!(source.package, "test-pkg");
617        let dgit = source.dgit.as_ref().unwrap();
618        assert_eq!(dgit.commit, "c1370424e2404d3c22bd09c828d4b28d81d897ad");
619        assert_eq!(dgit.suite, "debian");
620        assert_eq!(dgit.git_ref, "archive/debian/1.1.0");
621        assert_eq!(dgit.url, "https://git.dgit.debian.org/test-pkg");
622    }
623}