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