debian_analyzer/
abstract_control.rs

1//! Abstract interface for editing debian packages, whether backed by real control files or
2//! debcargo files.
3use crate::relations::ensure_relation;
4use debian_control::lossless::relations::{Entry, Relations};
5use std::path::Path;
6
7/// Interface for editing debian packages, whether backed by real control files or debcargo files.
8pub trait AbstractControlEditor {
9    /// Get the source package.
10    fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>>;
11
12    /// Get the binary packages.
13    fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>>;
14
15    /// Commit the changes.
16    fn commit(&self) -> bool;
17
18    /// Wrap and sort the control file.
19    fn wrap_and_sort(&mut self);
20}
21
22/// An abstract source package.
23pub trait AbstractSource<'a> {
24    /// Get the name of the source package.
25    fn name(&self) -> Option<String>;
26
27    /// Ensure that a build dependency is present.
28    fn ensure_build_dep(&mut self, dep: Entry);
29
30    /// Set the maintainer of the source package.
31    fn set_maintainer(&mut self, maintainer: &str);
32
33    /// Set the uploaders of the source package.
34    fn set_uploaders(&mut self, uploaders: &[&str]);
35}
36
37/// An abstract binary package.
38pub trait AbstractBinary {
39    /// Get the name of the binary package.
40    fn name(&self) -> Option<String>;
41}
42
43use crate::debcargo::{DebcargoBinary, DebcargoEditor, DebcargoSource};
44use debian_control::{Binary as PlainBinary, Control as PlainControl, Source as PlainSource};
45
46impl AbstractControlEditor for DebcargoEditor {
47    fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
48        Some(Box::new(DebcargoEditor::source(self)) as Box<dyn AbstractSource<'a>>)
49    }
50
51    fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
52        DebcargoEditor::binaries(self)
53            .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
54            .collect()
55    }
56
57    fn commit(&self) -> bool {
58        DebcargoEditor::commit(self).unwrap()
59    }
60
61    fn wrap_and_sort(&mut self) {}
62}
63
64impl AbstractBinary for PlainBinary {
65    fn name(&self) -> Option<String> {
66        self.name()
67    }
68}
69
70impl AbstractSource<'_> for PlainSource {
71    fn name(&self) -> Option<String> {
72        self.name()
73    }
74
75    fn ensure_build_dep(&mut self, dep: Entry) {
76        if let Some(mut build_deps) = self.build_depends() {
77            ensure_relation(&mut build_deps, dep);
78            self.set_build_depends(&build_deps);
79        } else {
80            self.set_build_depends(&Relations::from(vec![dep]));
81        }
82    }
83
84    fn set_maintainer(&mut self, maintainer: &str) {
85        (self as &mut debian_control::lossless::Source).set_maintainer(maintainer);
86    }
87
88    fn set_uploaders(&mut self, uploaders: &[&str]) {
89        (self as &mut debian_control::lossless::Source).set_uploaders(uploaders);
90    }
91}
92
93impl AbstractBinary for DebcargoBinary<'_> {
94    fn name(&self) -> Option<String> {
95        Some(self.name().to_string())
96    }
97}
98
99impl<'a> AbstractSource<'a> for DebcargoSource<'a> {
100    fn name(&self) -> Option<String> {
101        self.name()
102    }
103
104    fn ensure_build_dep(&mut self, dep: Entry) {
105        // TODO: Check that it's not already there
106        if let Some(build_deps) = self
107            .toml_section_mut()
108            .get_mut("build_depends")
109            .and_then(|v| v.as_array_mut())
110        {
111            build_deps.push(dep.to_string());
112        }
113    }
114
115    fn set_maintainer(&mut self, maintainer: &str) {
116        (self as &mut crate::debcargo::DebcargoSource).set_maintainer(maintainer);
117    }
118
119    fn set_uploaders(&mut self, uploaders: &[&str]) {
120        (self as &mut crate::debcargo::DebcargoSource)
121            .set_uploaders(uploaders.iter().map(|s| s.to_string()).collect::<Vec<_>>());
122    }
123}
124
125impl<E: crate::editor::Editor<PlainControl>> AbstractControlEditor for E {
126    fn source<'a>(&'a mut self) -> Option<Box<dyn AbstractSource<'a> + 'a>> {
127        PlainControl::source(self).map(|s| Box::new(s) as Box<dyn AbstractSource>)
128    }
129
130    fn binaries<'a>(&'a mut self) -> Vec<Box<dyn AbstractBinary + 'a>> {
131        PlainControl::binaries(self)
132            .map(|b| Box::new(b) as Box<dyn AbstractBinary>)
133            .collect()
134    }
135
136    fn commit(&self) -> bool {
137        !(self as &dyn crate::editor::Editor<PlainControl>)
138            .commit()
139            .unwrap()
140            .is_empty()
141    }
142
143    fn wrap_and_sort(&mut self) {
144        (self as &mut dyn crate::editor::Editor<PlainControl>).wrap_and_sort(
145            deb822_lossless::Indentation::Spaces(4),
146            false,
147            None,
148        )
149    }
150}
151
152/// Open a control file for editing.
153pub fn edit_control<'a>(
154    tree: &dyn breezyshim::workingtree::WorkingTree,
155    subpath: &Path,
156) -> Result<Box<dyn AbstractControlEditor + 'a>, crate::editor::EditorError> {
157    if tree.has_filename(&subpath.join("debian/debcargo.toml")) {
158        Ok(Box::new(crate::debcargo::DebcargoEditor::from_directory(
159            &tree.abspath(subpath).unwrap(),
160        )?))
161    } else {
162        let control_path = tree.abspath(&subpath.join(std::path::Path::new("debian/control")));
163        Ok(Box::new(crate::control::TemplatedControlEditor::open(
164            control_path.unwrap(),
165        )?) as Box<dyn AbstractControlEditor>)
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat};
172    use breezyshim::prelude::*;
173    use std::path::Path;
174    use std::str::FromStr;
175
176    #[test]
177    fn test_edit_control_debcargo() {
178        let td = tempfile::tempdir().unwrap();
179        let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
180        // Write dummy debcargo.toml
181        tree.mkdir(Path::new("debian")).unwrap();
182        std::fs::write(
183            td.path().join("debian/debcargo.toml"),
184            br#"
185maintainer = "Alice <alice@example.com>"
186homepage = "https://example.com"
187description = "Example package"
188"#,
189        )
190        .unwrap();
191
192        std::fs::write(
193            td.path().join("Cargo.toml"),
194            br#"
195[package]
196name = "example"
197version = "0.1.0"
198edition = "2018"
199"#,
200        )
201        .unwrap();
202
203        tree.add(&[(Path::new("debian")), (Path::new("debian/debcargo.toml"))])
204            .unwrap();
205
206        let editor = super::edit_control(&tree, Path::new("")).unwrap();
207
208        editor.commit();
209    }
210
211    #[test]
212    fn test_edit_control_regular() {
213        let td = tempfile::tempdir().unwrap();
214        let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
215        // Write dummy debian/control
216        tree.mkdir(Path::new("debian")).unwrap();
217        tree.put_file_bytes_non_atomic(
218            Path::new("debian/control"),
219            br#"
220Source: example
221Maintainer: Alice <alice@example.com>
222Homepage: https://example.com
223
224Package: example
225Architecture: any
226Description: Example package
227"#,
228        )
229        .unwrap();
230
231        tree.add(&[(Path::new("debian")), (Path::new("debian/control"))])
232            .unwrap();
233
234        let editor = super::edit_control(&tree, Path::new("")).unwrap();
235
236        editor.commit();
237    }
238
239    #[test]
240    fn test_edit_source_ensure_build_depends() {
241        let td = tempfile::tempdir().unwrap();
242        let tree = create_standalone_workingtree(td.path(), &ControlDirFormat::default()).unwrap();
243        // Write dummy debian/control
244        tree.mkdir(Path::new("debian")).unwrap();
245        tree.put_file_bytes_non_atomic(
246            Path::new("debian/control"),
247            br#"
248Source: example
249Maintainer: Alice <alice@example.com>
250Build-Depends: libc6
251
252Package: example
253Architecture: any
254Description: Example package
255"#,
256        )
257        .unwrap();
258        tree.add(&[Path::new("debian/control")]).unwrap();
259
260        let mut editor = super::edit_control(&tree, Path::new("")).unwrap();
261        let mut source = editor.source().unwrap();
262        source.ensure_build_dep(
263            debian_control::lossless::relations::Entry::from_str("libssl-dev").unwrap(),
264        );
265        std::mem::drop(source);
266        editor.commit();
267
268        let text = tree.get_file_text(Path::new("debian/control")).unwrap();
269        assert_eq!(
270            std::str::from_utf8(&text).unwrap(),
271            r#"
272Source: example
273Maintainer: Alice <alice@example.com>
274Build-Depends: libc6, libssl-dev
275
276Package: example
277Architecture: any
278Description: Example package
279"#
280        );
281    }
282}