1use crate::editor::{Editor, EditorError, FsEditor, GeneratedFile};
3use crate::relations::{ensure_relation, is_relation_implied};
4use deb822_lossless::Paragraph;
5use debian_control::lossless::relations::Relations;
6use std::ops::{Deref, DerefMut};
7use std::path::{Path, PathBuf};
8
9pub fn format_description(summary: &str, long_description: Vec<&str>) -> String {
11 let mut ret = summary.to_string() + "\n";
12 for line in long_description {
13 ret.push(' ');
14 ret.push_str(line);
15 ret.push('\n');
16 }
17 ret
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Copy)]
21pub enum TemplateType {
23 Rules,
25
26 Gnome,
28
29 Postgresql,
31
32 Directory,
34
35 Cdbs,
37
38 Debcargo,
40}
41
42#[derive(Debug)]
44pub enum TemplateExpansionError {
45 Failed(String),
47 ExpandCommandMissing(String),
49 UnknownTemplating(PathBuf, Option<PathBuf>),
51 Conflict(ChangeConflict),
53}
54
55impl From<ChangeConflict> for TemplateExpansionError {
56 fn from(e: ChangeConflict) -> Self {
57 TemplateExpansionError::Conflict(e)
58 }
59}
60
61impl std::fmt::Display for TemplateExpansionError {
62 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
63 match self {
64 TemplateExpansionError::Failed(s) => write!(f, "Failed: {}", s),
65 TemplateExpansionError::ExpandCommandMissing(s) => {
66 write!(f, "Command not found: {}", s)
67 }
68 TemplateExpansionError::UnknownTemplating(p1, p2) => {
69 if let Some(p2) = p2 {
70 write!(
71 f,
72 "Unknown templating: {} -> {}",
73 p1.display(),
74 p2.display()
75 )
76 } else {
77 write!(f, "Unknown templating: {}", p1.display())
78 }
79 }
80 TemplateExpansionError::Conflict(c) => write!(f, "Conflict: {}", c),
81 }
82 }
83}
84
85impl std::error::Error for TemplateExpansionError {}
86
87pub fn dh_gnome_clean(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
102 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
103 if entry
104 .file_name()
105 .to_string_lossy()
106 .ends_with(".debhelper.log")
107 {
108 return Err(TemplateExpansionError::Failed(
109 "pre-existing .debhelper.log files".to_string(),
110 ));
111 }
112 }
113
114 if !path.join("debian/changelog").exists() {
115 return Err(TemplateExpansionError::Failed(
116 "no changelog file".to_string(),
117 ));
118 }
119
120 let result = std::process::Command::new("dh_gnome_clean")
121 .current_dir(path)
122 .output();
123
124 match result {
125 Ok(output) => {
126 if !output.status.success() {
127 let stderr = String::from_utf8_lossy(&output.stderr);
128 return Err(TemplateExpansionError::Failed(stderr.to_string()));
129 }
130 }
131 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
132 return Err(TemplateExpansionError::ExpandCommandMissing(
133 "dh_gnome_clean".to_string(),
134 ));
135 }
136 Err(e) => {
137 return Err(TemplateExpansionError::Failed(e.to_string()));
138 }
139 }
140
141 for entry in std::fs::read_dir(path.join("debian")).unwrap().flatten() {
142 if entry
143 .file_name()
144 .to_string_lossy()
145 .ends_with(".debhelper.log")
146 {
147 std::fs::remove_file(entry.path()).unwrap();
148 }
149 }
150
151 Ok(())
152}
153
154pub fn pg_buildext_updatecontrol(path: &std::path::Path) -> Result<(), TemplateExpansionError> {
164 let result = std::process::Command::new("pg_buildext")
165 .arg("updatecontrol")
166 .current_dir(path)
167 .output();
168
169 match result {
170 Ok(output) => {
171 if !output.status.success() {
172 let stderr = String::from_utf8_lossy(&output.stderr);
173 return Err(TemplateExpansionError::Failed(stderr.to_string()));
174 }
175 }
176 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
177 return Err(TemplateExpansionError::ExpandCommandMissing(
178 "pg_buildext".to_string(),
179 ));
180 }
181 Err(e) => {
182 return Err(TemplateExpansionError::Failed(e.to_string()));
183 }
184 }
185 Ok(())
186}
187
188fn expand_control_template(
198 template_path: &std::path::Path,
199 path: &std::path::Path,
200 template_type: TemplateType,
201) -> Result<(), TemplateExpansionError> {
202 let package_root = path.parent().unwrap().parent().unwrap();
203 match template_type {
204 TemplateType::Rules => {
205 let path_time = match std::fs::metadata(path) {
206 Ok(metadata) => Some(metadata.modified().unwrap()),
207 Err(e) if e.kind() == std::io::ErrorKind::NotFound => None,
208 Err(e) => panic!("Failed to get mtime of {}: {}", path.display(), e),
209 };
210 while let Ok(metadata) = std::fs::metadata(template_path) {
211 if Some(metadata.modified().unwrap()) == path_time {
212 filetime::set_file_mtime(template_path, filetime::FileTime::now()).unwrap();
214 } else {
215 break;
216 }
217 }
218 let result = std::process::Command::new("./debian/rules")
219 .arg("debian/control")
220 .current_dir(package_root)
221 .output();
222
223 match result {
224 Ok(output) => {
225 if !output.status.success() {
226 let stderr = String::from_utf8_lossy(&output.stderr);
227 Err(TemplateExpansionError::Failed(format!(
228 "Exit code {} running ./debian/rules debian/control: {}",
229 output.status, stderr
230 )))
231 } else {
232 Ok(())
233 }
234 }
235 Err(e) => Err(TemplateExpansionError::Failed(format!(
236 "Failed to run ./debian/rules debian/control: {}",
237 e
238 ))),
239 }
240 }
241 TemplateType::Gnome => dh_gnome_clean(package_root),
242 TemplateType::Postgresql => pg_buildext_updatecontrol(package_root),
243 TemplateType::Cdbs => unreachable!(),
244 TemplateType::Debcargo => unreachable!(),
245 TemplateType::Directory => Err(TemplateExpansionError::UnknownTemplating(
246 path.to_path_buf(),
247 Some(template_path.to_path_buf()),
248 )),
249 }
250}
251
252#[derive(Debug, Clone)]
253struct Deb822Changes(
254 std::collections::HashMap<(String, String), Vec<(String, Option<String>, Option<String>)>>,
255);
256
257impl Deb822Changes {
258 fn new() -> Self {
259 Self(std::collections::HashMap::new())
260 }
261
262 fn insert(
263 &mut self,
264 para_key: (String, String),
265 field: String,
266 old_value: Option<String>,
267 new_value: Option<String>,
268 ) {
269 self.0
270 .entry(para_key)
271 .or_default()
272 .push((field, old_value, new_value));
273 }
274
275 #[allow(dead_code)]
276 fn normalized(&self) -> Vec<((&str, &str), Vec<(&str, Option<&str>, Option<&str>)>)> {
277 let mut ret: Vec<_> = self
278 .0
279 .iter()
280 .map(|(k, v)| {
281 ((k.0.as_str(), k.1.as_str()), {
282 let mut v: Vec<_> = v
283 .iter()
284 .map(|(f, o, n)| (f.as_str(), o.as_deref(), n.as_deref()))
285 .collect();
286 v.sort();
287 v
288 })
289 })
290 .collect();
291 ret.sort_by_key(|(k, _)| *k);
292 ret
293 }
294}
295
296fn update_control_template(
307 template_path: &std::path::Path,
308 template_type: TemplateType,
309 path: &std::path::Path,
310 changes: Deb822Changes,
311 expand_template: bool,
312) -> Result<bool, TemplateExpansionError> {
313 if template_type == TemplateType::Directory {
314 return Err(TemplateExpansionError::UnknownTemplating(
316 path.to_path_buf(),
317 Some(template_path.to_path_buf()),
318 ));
319 }
320
321 let mut template_editor =
322 FsEditor::<deb822_lossless::Deb822>::new(template_path, true, false).unwrap();
323
324 let resolve_conflict = match template_type {
325 TemplateType::Cdbs => Some(resolve_cdbs_template as ResolveDeb822Conflict),
326 _ => None,
327 };
328
329 apply_changes(&mut template_editor, changes.clone(), resolve_conflict)?;
330
331 if !template_editor.has_changed() {
332 return Ok(false);
334 }
335
336 match template_editor.commit() {
337 Ok(_) => {}
338 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
339 }
340
341 if expand_template {
342 match template_type {
343 TemplateType::Cdbs => {
344 let mut editor =
345 FsEditor::<deb822_lossless::Deb822>::new(path, true, false).unwrap();
346 apply_changes(&mut editor, changes, None)?;
347 match editor.commit() {
348 Ok(_) => {}
349 Err(e) => return Err(TemplateExpansionError::Failed(e.to_string())),
350 }
351 }
352 _ => {
353 expand_control_template(template_path, path, template_type)?;
354 }
355 }
356 }
357
358 Ok(true)
359}
360
361#[derive(Debug, PartialEq, Eq)]
362pub struct ChangeConflict {
364 pub para_key: (String, String),
366 pub field: String,
368 pub actual_old_value: Option<String>,
370 pub template_old_value: Option<String>,
372 pub actual_new_value: Option<String>,
374}
375
376impl std::fmt::Display for ChangeConflict {
377 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
378 write!(
379 f,
380 "{}/{}: {} -> {} (template: {})",
381 self.para_key.0,
382 self.para_key.1,
383 self.actual_old_value.as_deref().unwrap_or(""),
384 self.actual_new_value.as_deref().unwrap_or(""),
385 self.template_old_value.as_deref().unwrap_or("")
386 )
387 }
388}
389
390impl std::error::Error for ChangeConflict {}
391
392type ResolveDeb822Conflict = fn(
393 para_key: (&str, &str),
394 field: &str,
395 actual_old_value: Option<&str>,
396 template_old_value: Option<&str>,
397 actual_new_value: Option<&str>,
398) -> Result<Option<String>, ChangeConflict>;
399
400fn resolve_cdbs_template(
401 para_key: (&str, &str),
402 field: &str,
403 actual_old_value: Option<&str>,
404 template_old_value: Option<&str>,
405 actual_new_value: Option<&str>,
406) -> Result<Option<String>, ChangeConflict> {
407 if para_key.0 == "Source"
408 && field == "Build-Depends"
409 && template_old_value.is_some()
410 && actual_old_value.is_some()
411 && actual_new_value.is_some()
412 {
413 if actual_new_value
414 .unwrap()
415 .contains(actual_old_value.unwrap())
416 {
417 return Ok(Some(
419 actual_new_value
420 .unwrap()
421 .replace(actual_old_value.unwrap(), template_old_value.unwrap()),
422 ));
423 } else {
424 let old_rels: Relations = actual_old_value.unwrap().parse().unwrap();
425 let new_rels: Relations = actual_new_value.unwrap().parse().unwrap();
426 let template_old_value = template_old_value.unwrap();
427 let (mut ret, errors) = Relations::parse_relaxed(template_old_value, true);
428 if !errors.is_empty() {
429 log::debug!("Errors parsing template value: {:?}", errors);
430 }
431 for v in new_rels.entries() {
432 if old_rels.entries().any(|r| is_relation_implied(&v, &r)) {
433 continue;
434 }
435 ensure_relation(&mut ret, v);
436 }
437 return Ok(Some(ret.to_string()));
438 }
439 }
440 Err(ChangeConflict {
441 para_key: (para_key.0.to_string(), para_key.1.to_string()),
442 field: field.to_string(),
443 actual_old_value: actual_old_value.map(|v| v.to_string()),
444 template_old_value: template_old_value.map(|v| v.to_string()),
445 actual_new_value: actual_new_value.map(|s| s.to_string()),
446 })
447}
448
449pub fn guess_template_type(
458 template_path: &std::path::Path,
459 debian_path: Option<&std::path::Path>,
460) -> Option<TemplateType> {
461 if let Some(debian_path) = debian_path {
463 match std::fs::read(debian_path.join("rules")) {
464 Ok(file) => {
465 for line in file.split(|&c| c == b'\n') {
466 if line.starts_with(b"debian/control:") {
467 return Some(TemplateType::Rules);
468 }
469 if line.starts_with(b"debian/%: debian/%.in") {
470 return Some(TemplateType::Rules);
471 }
472 if line.starts_with(b"include /usr/share/blends-dev/rules") {
473 return Some(TemplateType::Rules);
474 }
475 }
476 }
477 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
478 Err(e) => panic!(
479 "Failed to read {}: {}",
480 debian_path.join("rules").display(),
481 e
482 ),
483 }
484 }
485 match std::fs::read(template_path) {
486 Ok(template) => {
487 let template_str = std::str::from_utf8(&template).unwrap();
488 if template_str.contains("@GNOME_TEAM@") {
489 return Some(TemplateType::Gnome);
490 }
491 if template_str.contains("PGVERSION") {
492 return Some(TemplateType::Postgresql);
493 }
494 if template_str.contains("@cdbs@") {
495 return Some(TemplateType::Cdbs);
496 }
497
498 let control = debian_control::Control::read_relaxed(std::io::Cursor::new(&template))
499 .unwrap()
500 .0;
501
502 let build_depends = control.source().and_then(|s| s.build_depends());
503
504 if build_depends.iter().any(|d| {
505 d.entries()
506 .any(|e| e.relations().any(|r| r.name() == "gnome-pkg-tools"))
507 }) {
508 return Some(TemplateType::Gnome);
509 }
510
511 if build_depends.iter().any(|d| {
512 d.entries()
513 .any(|e| e.relations().any(|r| r.name() == "cdbs"))
514 }) {
515 return Some(TemplateType::Cdbs);
516 }
517 }
518 Err(_) if template_path.is_dir() => {
519 return Some(TemplateType::Directory);
520 }
521 Err(e) => panic!("Failed to read {}: {}", template_path.display(), e),
522 }
523 if let Some(debian_path) = debian_path {
524 if debian_path.join("debcargo.toml").exists() {
525 return Some(TemplateType::Debcargo);
526 }
527 }
528 None
529}
530
531fn apply_changes(
537 deb822: &mut deb822_lossless::Deb822,
538 mut changes: Deb822Changes,
539 resolve_conflict: Option<ResolveDeb822Conflict>,
540) -> Result<(), ChangeConflict> {
541 fn default_resolve_conflict(
542 para_key: (&str, &str),
543 field: &str,
544 actual_old_value: Option<&str>,
545 template_old_value: Option<&str>,
546 actual_new_value: Option<&str>,
547 ) -> Result<Option<String>, ChangeConflict> {
548 Err(ChangeConflict {
549 para_key: (para_key.0.to_string(), para_key.1.to_string()),
550 field: field.to_string(),
551 actual_old_value: actual_old_value.map(|v| v.to_string()),
552 template_old_value: template_old_value.map(|v| v.to_string()),
553 actual_new_value: actual_new_value.map(|s| s.to_string()),
554 })
555 }
556
557 let resolve_conflict = resolve_conflict.unwrap_or(default_resolve_conflict);
558
559 for mut paragraph in deb822.paragraphs() {
560 let items: Vec<_> = paragraph.items().collect();
561 for item in items {
562 for (key, old_value, mut new_value) in changes.0.remove(&item).unwrap_or_default() {
563 if paragraph.get(&key) != old_value {
564 new_value = resolve_conflict(
565 (&item.0, &item.1),
566 &key,
567 old_value.as_deref(),
568 paragraph.get(&key).as_deref(),
569 new_value.as_deref(),
570 )?;
571 }
572 if let Some(new_value) = new_value.as_ref() {
573 paragraph.set(&key, new_value);
574 } else {
575 paragraph.remove(&key);
576 }
577 }
578 }
579 }
580 for (key, p) in changes.0.drain() {
582 let mut paragraph = deb822.add_paragraph();
583 for (field, old_value, mut new_value) in p {
584 if old_value.is_some() {
585 new_value = resolve_conflict(
586 (&key.0, &key.1),
587 &field,
588 old_value.as_deref(),
589 paragraph.get(&field).as_deref(),
590 new_value.as_deref(),
591 )?;
592 }
593 if let Some(new_value) = new_value {
594 paragraph.set(&field, &new_value);
595 }
596 }
597 }
598 Ok(())
599}
600
601fn find_template_path(path: &Path) -> Option<PathBuf> {
602 for ext in &["in", "m4"] {
603 let template_path = path.with_extension(ext);
604 if template_path.exists() {
605 return Some(template_path);
606 }
607 }
608 None
609}
610
611pub struct TemplatedControlEditor {
627 primary: FsEditor<debian_control::Control>,
629 template: Option<Template>,
631 path: PathBuf,
633 template_only: bool,
635}
636
637impl Deref for TemplatedControlEditor {
638 type Target = debian_control::Control;
639
640 fn deref(&self) -> &Self::Target {
641 &self.primary
642 }
643}
644
645impl DerefMut for TemplatedControlEditor {
646 fn deref_mut(&mut self) -> &mut Self::Target {
647 &mut self.primary
648 }
649}
650
651impl TemplatedControlEditor {
652 pub fn create<P: AsRef<Path>>(
654 control_path: P,
655 ) -> Result<Self, EditorError<deb822_lossless::Error>> {
656 if control_path.as_ref().exists() {
657 return Err(EditorError::IoError(std::io::Error::new(
658 std::io::ErrorKind::AlreadyExists,
659 "Control file already exists",
660 )));
661 }
662 Self::new(control_path, true)
663 }
664
665 pub fn template_type(&self) -> Option<TemplateType> {
667 self.template.as_ref().map(|t| t.template_type)
668 }
669
670 pub fn normalize_field_spacing(&mut self) -> Result<(), EditorError<deb822_lossless::Error>> {
682 let Some(template) = &self.template else {
683 self.primary.as_mut_deb822().normalize_field_spacing();
685 return Ok(());
686 };
687
688 let is_deb822_template = matches!(
690 template.template_type,
691 TemplateType::Cdbs | TemplateType::Directory
692 );
693
694 if !is_deb822_template {
695 return Err(EditorError::GeneratedFile(
697 self.path.clone(),
698 GeneratedFile {
699 template_path: Some(template.template_path.clone()),
700 template_type: None,
701 },
702 ));
703 }
704
705 let mut template_editor =
707 FsEditor::<deb822_lossless::Deb822>::new(&template.template_path, true, false)?;
708 template_editor.normalize_field_spacing();
709 template_editor.commit()?;
710
711 self.primary.as_mut_deb822().normalize_field_spacing();
713 Ok(())
714 }
715
716 pub fn open<P: AsRef<Path>>(
718 control_path: P,
719 ) -> Result<Self, EditorError<deb822_lossless::Error>> {
720 Self::new(control_path, false)
721 }
722
723 pub fn new<P: AsRef<Path>>(
725 control_path: P,
726 allow_missing: bool,
727 ) -> Result<Self, EditorError<deb822_lossless::Error>> {
728 let path = control_path.as_ref();
729 let (template, template_only) = if !path.exists() {
730 if let Some(template) = Template::find(path) {
731 match template.expand() {
732 Ok(_) => {}
733 Err(e) => {
734 return Err(EditorError::TemplateError(
735 template.template_path,
736 e.to_string(),
737 ))
738 }
739 }
740 (Some(template), true)
741 } else if !allow_missing {
742 return Err(EditorError::IoError(std::io::Error::new(
743 std::io::ErrorKind::NotFound,
744 "No control file or template found",
745 )));
746 } else {
747 (None, false)
748 }
749 } else {
750 (Template::find(path), false)
751 };
752 let primary = FsEditor::<debian_control::Control>::new(path, false, false)?;
753 Ok(Self {
754 path: path.to_path_buf(),
755 primary,
756 template_only,
757 template,
758 })
759 }
760
761 fn changes(&self) -> Deb822Changes {
766 let orig = deb822_lossless::Deb822::read_relaxed(self.primary.orig_content().unwrap())
767 .unwrap()
768 .0;
769 let mut changes = Deb822Changes::new();
770
771 fn by_key(
772 ps: impl Iterator<Item = Paragraph>,
773 ) -> std::collections::HashMap<(String, String), Paragraph> {
774 let mut ret = std::collections::HashMap::new();
775 for p in ps {
776 if let Some(s) = p.get("Source") {
777 ret.insert(("Source".to_string(), s), p);
778 } else if let Some(s) = p.get("Package") {
779 ret.insert(("Package".to_string(), s), p);
780 } else {
781 let k = p.items().next().unwrap();
782 ret.insert(k, p);
783 }
784 }
785 ret
786 }
787
788 let orig_by_key = by_key(orig.paragraphs());
789 let new_by_key = by_key(self.as_deb822().paragraphs());
790 let keys = orig_by_key
791 .keys()
792 .chain(new_by_key.keys())
793 .collect::<std::collections::HashSet<_>>();
794 for key in keys {
795 let old = orig_by_key.get(key);
796 let new = new_by_key.get(key);
797 if old == new {
798 continue;
799 }
800 let fields = std::collections::HashSet::<String>::from_iter(
801 old.iter()
802 .flat_map(|p| p.keys())
803 .chain(new.iter().flat_map(|p| p.keys())),
804 );
805 for field in &fields {
806 let old_val = old.and_then(|x| x.get(field));
807 let new_val = new.and_then(|x| x.get(field));
808 if old_val != new_val {
809 changes.insert(key.clone(), field.to_string(), old_val, new_val);
810 }
811 }
812 }
813 changes
814 }
815
816 pub fn commit(&mut self) -> Result<Vec<PathBuf>, EditorError<deb822_lossless::Error>> {
818 let mut changed_files: Vec<PathBuf> = vec![];
819 if self.template_only {
820 match std::fs::remove_file(self.path.as_path()) {
822 Ok(_) => {}
823 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
824 Err(e) => return Err(EditorError::IoError(e)),
825 }
826
827 changed_files.push(self.path.clone());
828
829 let template = self
830 .template
831 .as_ref()
832 .expect("template_only implies template");
833
834 let changed = match template.update(self.changes(), false) {
836 Ok(changed) => changed,
837 Err(e) => {
838 return Err(EditorError::TemplateError(
839 template.template_path.clone(),
840 e.to_string(),
841 ))
842 }
843 };
844 if changed {
845 changed_files.push(template.template_path.clone());
846 }
847 } else {
848 match self.primary.commit() {
849 Ok(files) => {
850 changed_files.extend(files.iter().map(|p| p.to_path_buf()));
851 }
852 Err(EditorError::GeneratedFile(
853 p,
854 GeneratedFile {
855 template_path: tp,
856 template_type: tt,
857 },
858 )) => {
859 if tp.is_none() {
860 return Err(EditorError::GeneratedFile(
861 p,
862 GeneratedFile {
863 template_path: tp,
864 template_type: tt,
865 },
866 ));
867 }
868 let template = if let Some(template) = self.template.as_ref() {
869 template
870 } else {
871 return Err(EditorError::IoError(std::io::Error::new(
872 std::io::ErrorKind::NotFound,
873 "No control file or template found",
874 )));
875 };
876 let changes = self.changes();
877 let changed = match template.update(changes, true) {
878 Ok(changed) => changed,
879 Err(e) => {
880 return Err(EditorError::TemplateError(tp.unwrap(), e.to_string()))
881 }
882 };
883 changed_files = if changed {
884 vec![tp.as_ref().unwrap().to_path_buf(), p]
885 } else {
886 vec![]
887 };
888 }
889 Err(EditorError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => {
890 let template = if let Some(p) = self.template.as_ref() {
891 p
892 } else {
893 return Err(EditorError::IoError(std::io::Error::new(
894 std::io::ErrorKind::NotFound,
895 "No control file or template found",
896 )));
897 };
898 let changed = match template.update(self.changes(), !self.template_only) {
899 Ok(changed) => changed,
900 Err(e) => {
901 return Err(EditorError::TemplateError(
902 template.template_path.clone(),
903 e.to_string(),
904 ))
905 }
906 };
907 if changed {
908 changed_files.push(template.template_path.clone());
909 changed_files.push(self.path.clone());
910 }
911 }
912 Err(e) => return Err(e),
913 }
914 }
915
916 Ok(changed_files)
917 }
918}
919
920struct Template {
921 path: PathBuf,
922 template_path: PathBuf,
923 template_type: TemplateType,
924}
925
926impl Template {
927 fn find(path: &Path) -> Option<Self> {
928 let template_path = find_template_path(path)?;
929 let template_type = guess_template_type(&template_path, Some(path.parent().unwrap()))?;
930 Some(Self {
931 path: path.to_path_buf(),
932 template_path,
933 template_type,
934 })
935 }
936
937 fn expand(&self) -> Result<(), TemplateExpansionError> {
938 expand_control_template(&self.template_path, &self.path, self.template_type)
939 }
940
941 fn update(&self, changes: Deb822Changes, expand: bool) -> Result<bool, TemplateExpansionError> {
942 update_control_template(
943 &self.template_path,
944 self.template_type,
945 &self.path,
946 changes,
947 expand,
948 )
949 }
950}
951
952impl Editor<debian_control::Control> for TemplatedControlEditor {
953 fn orig_content(&self) -> Option<&[u8]> {
954 self.primary.orig_content()
955 }
956
957 fn updated_content(&self) -> Option<Vec<u8>> {
958 self.primary.updated_content()
959 }
960
961 fn rewritten_content(&self) -> Option<&[u8]> {
962 self.primary.rewritten_content()
963 }
964
965 fn is_generated(&self) -> bool {
966 self.primary.is_generated()
967 }
968
969 fn commit(&mut self) -> Result<Vec<std::path::PathBuf>, EditorError<deb822_lossless::Error>> {
970 TemplatedControlEditor::commit(self)
971 }
972
973 fn revert(&mut self) -> Result<(), EditorError<deb822_lossless::Error>> {
974 self.primary.revert()
975 }
976}
977
978#[cfg(test)]
979mod tests {
980 use super::*;
981
982 #[test]
983 fn test_format_description() {
984 let summary = "Summary";
985 let long_description = vec!["Long", "Description"];
986 let expected = "Summary\n Long\n Description\n";
987 assert_eq!(format_description(summary, long_description), expected);
988 }
989
990 #[test]
991 fn test_resolve_cdbs_conflicts() {
992 let val = resolve_cdbs_template(
993 ("Source", "libnetsds-perl"),
994 "Build-Depends",
995 Some("debhelper (>= 6), foo"),
996 Some("@cdbs@, debhelper (>= 9)"),
997 Some("debhelper (>= 10), foo"),
998 )
999 .unwrap();
1000
1001 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
1002
1003 let val = resolve_cdbs_template(
1004 ("Source", "libnetsds-perl"),
1005 "Build-Depends",
1006 Some("debhelper (>= 6), foo"),
1007 Some("@cdbs@, foo"),
1008 Some("debhelper (>= 10), foo"),
1009 )
1010 .unwrap();
1011 assert_eq!(val, Some("@cdbs@, debhelper (>= 10), foo".to_string()));
1012 let val = resolve_cdbs_template(
1013 ("Source", "libnetsds-perl"),
1014 "Build-Depends",
1015 Some("debhelper (>= 6), foo"),
1016 Some("@cdbs@, debhelper (>= 9)"),
1017 Some("debhelper (>= 10), foo"),
1018 )
1019 .unwrap();
1020 assert_eq!(val, Some("@cdbs@, debhelper (>= 10)".to_string()));
1021 }
1022
1023 #[test]
1024 fn test_normalize_field_spacing_without_template() {
1025 let td = tempfile::tempdir().unwrap();
1027 let control_path = td.path().join("debian").join("control");
1028 std::fs::create_dir_all(control_path.parent().unwrap()).unwrap();
1029 std::fs::write(
1030 &control_path,
1031 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1032 )
1033 .unwrap();
1034
1035 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1036
1037 let original = editor.as_deb822().to_string();
1039 assert_eq!(
1040 original,
1041 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1042 );
1043
1044 editor.normalize_field_spacing().unwrap();
1046
1047 let normalized = editor.as_deb822().to_string();
1049 assert_eq!(
1050 normalized,
1051 "Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1052 );
1053 }
1054
1055 #[test]
1056 fn test_normalize_field_spacing_with_non_deb822_template() {
1057 let td = tempfile::tempdir().unwrap();
1059 let debian_path = td.path().join("debian");
1060 std::fs::create_dir_all(&debian_path).unwrap();
1061
1062 let control_in_path = debian_path.join("control.in");
1063 let control_path = debian_path.join("control");
1064 let rules_path = debian_path.join("rules");
1065
1066 std::fs::write(
1068 &control_in_path,
1069 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1070 )
1071 .unwrap();
1072
1073 std::fs::write(
1075 &rules_path,
1076 b"#!/usr/bin/make -f\n\ndebian/control: debian/control.in\n\tcp $< $@\n",
1077 )
1078 .unwrap();
1079
1080 std::fs::write(
1082 &control_path,
1083 b"Source: test\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1084 )
1085 .unwrap();
1086
1087 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1088 assert_eq!(editor.template_type(), Some(TemplateType::Rules));
1089
1090 let result = editor.normalize_field_spacing();
1092 assert!(result.is_err());
1093
1094 match result {
1096 Err(EditorError::GeneratedFile(_, _)) => {
1097 }
1099 _ => panic!("Expected GeneratedFile error"),
1100 }
1101 }
1102
1103 #[test]
1104 fn test_normalize_field_spacing_with_cdbs_template() {
1105 let td = tempfile::tempdir().unwrap();
1109 let debian_path = td.path().join("debian");
1110 std::fs::create_dir_all(&debian_path).unwrap();
1111
1112 let control_in_path = debian_path.join("control.in");
1113 let control_path = debian_path.join("control");
1114
1115 std::fs::write(
1117 &control_in_path,
1118 b"Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n",
1119 )
1120 .unwrap();
1121
1122 std::fs::write(
1124 &control_path,
1125 b"Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n",
1126 )
1127 .unwrap();
1128
1129 let mut editor = TemplatedControlEditor::open(&control_path).unwrap();
1130 assert_eq!(editor.template_type(), Some(TemplateType::Cdbs));
1131
1132 let original_template = std::fs::read_to_string(&control_in_path).unwrap();
1134 assert_eq!(
1135 original_template,
1136 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1137 );
1138
1139 editor.normalize_field_spacing().unwrap();
1141
1142 let template_content = std::fs::read_to_string(&control_in_path).unwrap();
1144 assert_eq!(
1145 template_content,
1146 "Source: test\nBuild-Depends: @cdbs@\nRecommends: ${cdbs:Recommends}\n\nPackage: test\nArchitecture: all\n"
1147 );
1148
1149 let control_content = editor.as_deb822().to_string();
1151 assert_eq!(
1152 control_content,
1153 "Source: test\nBuild-Depends: debhelper\nRecommends: foo\n\nPackage: test\nArchitecture: all\n"
1154 );
1155 }
1156
1157 mod guess_template_type {
1158
1159 #[test]
1160 fn test_rules_generates_control() {
1161 let td = tempfile::tempdir().unwrap();
1162 std::fs::create_dir(td.path().join("debian")).unwrap();
1163 std::fs::write(
1164 td.path().join("debian/rules"),
1165 r#"%:
1166 dh $@
1167
1168debian/control: debian/control.in
1169 cp $@ $<
1170"#,
1171 )
1172 .unwrap();
1173 assert_eq!(
1174 super::guess_template_type(
1175 &td.path().join("debian/control.in"),
1176 Some(&td.path().join("debian"))
1177 ),
1178 Some(super::TemplateType::Rules)
1179 );
1180 }
1181
1182 #[test]
1183 fn test_rules_generates_control_percent() {
1184 let td = tempfile::tempdir().unwrap();
1185 std::fs::create_dir(td.path().join("debian")).unwrap();
1186 std::fs::write(
1187 td.path().join("debian/rules"),
1188 r#"%:
1189 dh $@
1190
1191debian/%: debian/%.in
1192 cp $@ $<
1193"#,
1194 )
1195 .unwrap();
1196 assert_eq!(
1197 super::guess_template_type(
1198 &td.path().join("debian/control.in"),
1199 Some(&td.path().join("debian"))
1200 ),
1201 Some(super::TemplateType::Rules)
1202 );
1203 }
1204
1205 #[test]
1206 fn test_rules_generates_control_blends() {
1207 let td = tempfile::tempdir().unwrap();
1208 std::fs::create_dir(td.path().join("debian")).unwrap();
1209 std::fs::write(
1210 td.path().join("debian/rules"),
1211 r#"%:
1212 dh $@
1213
1214include /usr/share/blends-dev/rules
1215"#,
1216 )
1217 .unwrap();
1218 assert_eq!(
1219 super::guess_template_type(
1220 &td.path().join("debian/control.stub"),
1221 Some(&td.path().join("debian"))
1222 ),
1223 Some(super::TemplateType::Rules)
1224 );
1225 }
1226
1227 #[test]
1228 fn test_empty_template() {
1229 let td = tempfile::tempdir().unwrap();
1230 std::fs::create_dir(td.path().join("debian")).unwrap();
1231 std::fs::write(td.path().join("debian/control.in"), "").unwrap();
1233
1234 assert_eq!(
1235 None,
1236 super::guess_template_type(
1237 &td.path().join("debian/control.in"),
1238 Some(&td.path().join("debian"))
1239 )
1240 );
1241 }
1242
1243 #[test]
1244 fn test_build_depends_cdbs() {
1245 let td = tempfile::tempdir().unwrap();
1246 std::fs::create_dir(td.path().join("debian")).unwrap();
1247 std::fs::write(
1248 td.path().join("debian/control.in"),
1249 r#"Source: blah
1250Build-Depends: cdbs
1251Vcs-Git: file://
1252
1253Package: bar
1254"#,
1255 )
1256 .unwrap();
1257 assert_eq!(
1258 Some(super::TemplateType::Cdbs),
1259 super::guess_template_type(
1260 &td.path().join("debian/control.in"),
1261 Some(&td.path().join("debian"))
1262 )
1263 );
1264 }
1265
1266 #[test]
1267 fn test_no_build_depends() {
1268 let td = tempfile::tempdir().unwrap();
1269 std::fs::create_dir(td.path().join("debian")).unwrap();
1270 std::fs::write(
1271 td.path().join("debian/control.in"),
1272 r#"Source: blah
1273Vcs-Git: file://
1274
1275Package: bar
1276"#,
1277 )
1278 .unwrap();
1279 assert_eq!(
1280 None,
1281 super::guess_template_type(
1282 &td.path().join("debian/control.in"),
1283 Some(&td.path().join("debian"))
1284 )
1285 );
1286 }
1287
1288 #[test]
1289 fn test_gnome() {
1290 let td = tempfile::tempdir().unwrap();
1291 std::fs::create_dir(td.path().join("debian")).unwrap();
1292 std::fs::write(
1293 td.path().join("debian/control.in"),
1294 r#"Foo @GNOME_TEAM@
1295"#,
1296 )
1297 .unwrap();
1298 assert_eq!(
1299 Some(super::TemplateType::Gnome),
1300 super::guess_template_type(
1301 &td.path().join("debian/control.in"),
1302 Some(&td.path().join("debian"))
1303 )
1304 );
1305 }
1306
1307 #[test]
1308 fn test_gnome_build_depends() {
1309 let td = tempfile::tempdir().unwrap();
1310 std::fs::create_dir(td.path().join("debian")).unwrap();
1311 std::fs::write(
1312 td.path().join("debian/control.in"),
1313 r#"Source: blah
1314Build-Depends: gnome-pkg-tools, libc6-dev
1315"#,
1316 )
1317 .unwrap();
1318 assert_eq!(
1319 Some(super::TemplateType::Gnome),
1320 super::guess_template_type(
1321 &td.path().join("debian/control.in"),
1322 Some(&td.path().join("debian"))
1323 )
1324 );
1325 }
1326
1327 #[test]
1328 fn test_cdbs() {
1329 let td = tempfile::tempdir().unwrap();
1330 std::fs::create_dir(td.path().join("debian")).unwrap();
1331 std::fs::write(
1332 td.path().join("debian/control.in"),
1333 r#"Source: blah
1334Build-Depends: debhelper, cdbs
1335"#,
1336 )
1337 .unwrap();
1338 assert_eq!(
1339 Some(super::TemplateType::Cdbs),
1340 super::guess_template_type(
1341 &td.path().join("debian/control.in"),
1342 Some(&td.path().join("debian"))
1343 )
1344 );
1345 }
1346
1347 #[test]
1348 fn test_multiple_paragraphs() {
1349 let td = tempfile::tempdir().unwrap();
1350 std::fs::create_dir(td.path().join("debian")).unwrap();
1351 std::fs::write(
1352 td.path().join("debian/control.in"),
1353 r#"Source: blah
1354Build-Depends: debhelper, cdbs
1355
1356Package: foo
1357"#,
1358 )
1359 .unwrap();
1360 assert_eq!(
1361 Some(super::TemplateType::Cdbs),
1362 super::guess_template_type(
1363 &td.path().join("debian/control.in"),
1364 Some(&td.path().join("debian"))
1365 )
1366 );
1367 }
1368
1369 #[test]
1370 fn test_directory() {
1371 let td = tempfile::tempdir().unwrap();
1372 std::fs::create_dir(td.path().join("debian")).unwrap();
1373 std::fs::create_dir(td.path().join("debian/control.in")).unwrap();
1374 assert_eq!(
1375 Some(super::TemplateType::Directory),
1376 super::guess_template_type(
1377 &td.path().join("debian/control.in"),
1378 Some(&td.path().join("debian"))
1379 )
1380 );
1381 }
1382
1383 #[test]
1384 fn test_debcargo() {
1385 let td = tempfile::tempdir().unwrap();
1386 std::fs::create_dir(td.path().join("debian")).unwrap();
1387 std::fs::write(
1388 td.path().join("debian/control.in"),
1389 r#"Source: blah
1390Build-Depends: bar
1391"#,
1392 )
1393 .unwrap();
1394 std::fs::write(
1395 td.path().join("debian/debcargo.toml"),
1396 r#"maintainer = Joe Example <joe@example.com>
1397"#,
1398 )
1399 .unwrap();
1400 assert_eq!(
1401 Some(super::TemplateType::Debcargo),
1402 super::guess_template_type(
1403 &td.path().join("debian/control.in"),
1404 Some(&td.path().join("debian"))
1405 )
1406 );
1407 }
1408 }
1409
1410 #[test]
1411 fn test_postgresql() {
1412 let td = tempfile::tempdir().unwrap();
1413 std::fs::create_dir(td.path().join("debian")).unwrap();
1414 std::fs::write(
1415 td.path().join("debian/control.in"),
1416 r#"Source: blah
1417Build-Depends: bar, postgresql
1418
1419Package: foo-PGVERSION
1420"#,
1421 )
1422 .unwrap();
1423 assert_eq!(
1424 Some(super::TemplateType::Postgresql),
1425 super::guess_template_type(
1426 &td.path().join("debian/control.in"),
1427 Some(&td.path().join("debian"))
1428 )
1429 );
1430 }
1431
1432 #[test]
1433 fn test_apply_changes() {
1434 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1435Build-Depends: debhelper (>= 6), foo
1436
1437Package: bar
1438"#
1439 .parse()
1440 .unwrap();
1441
1442 let mut changes = Deb822Changes(std::collections::HashMap::new());
1443 changes.0.insert(
1444 ("Source".to_string(), "blah".to_string()),
1445 vec![(
1446 "Build-Depends".to_string(),
1447 Some("debhelper (>= 6), foo".to_string()),
1448 Some("debhelper (>= 10), foo".to_string()),
1449 )],
1450 );
1451
1452 super::apply_changes(&mut deb822, changes, None).unwrap();
1453
1454 assert_eq!(
1455 deb822.to_string(),
1456 r#"Source: blah
1457Build-Depends: debhelper (>= 10), foo
1458
1459Package: bar
1460"#
1461 );
1462 }
1463
1464 #[test]
1465 fn test_apply_changes_new_paragraph() {
1466 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1467Build-Depends: debhelper (>= 6), foo
1468
1469Package: bar
1470"#
1471 .parse()
1472 .unwrap();
1473
1474 let mut changes = Deb822Changes(std::collections::HashMap::new());
1475 changes.0.insert(
1476 ("Source".to_string(), "blah".to_string()),
1477 vec![(
1478 "Build-Depends".to_string(),
1479 Some("debhelper (>= 6), foo".to_string()),
1480 Some("debhelper (>= 10), foo".to_string()),
1481 )],
1482 );
1483 changes.0.insert(
1484 ("Package".to_string(), "blah2".to_string()),
1485 vec![
1486 ("Package".to_string(), None, Some("blah2".to_string())),
1487 (
1488 "Description".to_string(),
1489 None,
1490 Some("Some package".to_string()),
1491 ),
1492 ],
1493 );
1494
1495 super::apply_changes(&mut deb822, changes, None).unwrap();
1496
1497 assert_eq!(
1498 deb822.to_string(),
1499 r#"Source: blah
1500Build-Depends: debhelper (>= 10), foo
1501
1502Package: bar
1503
1504Package: blah2
1505Description: Some package
1506"#
1507 );
1508 }
1509
1510 #[test]
1511 fn test_apply_changes_conflict() {
1512 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1513Build-Depends: debhelper (>= 6), foo
1514
1515Package: bar
1516"#
1517 .parse()
1518 .unwrap();
1519
1520 let mut changes = Deb822Changes(std::collections::HashMap::new());
1521 changes.0.insert(
1522 ("Source".to_string(), "blah".to_string()),
1523 vec![(
1524 "Build-Depends".to_string(),
1525 Some("debhelper (>= 7), foo".to_string()),
1526 Some("debhelper (>= 10), foo".to_string()),
1527 )],
1528 );
1529
1530 let result = super::apply_changes(&mut deb822, changes, None);
1531 assert!(result.is_err());
1532 let err = result.unwrap_err();
1533 assert_eq!(
1534 err,
1535 ChangeConflict {
1536 para_key: ("Source".to_string(), "blah".to_string()),
1537 field: "Build-Depends".to_string(),
1538 actual_old_value: Some("debhelper (>= 7), foo".to_string()),
1539 template_old_value: Some("debhelper (>= 6), foo".to_string()),
1540 actual_new_value: Some("debhelper (>= 10), foo".to_string()),
1541 }
1542 );
1543 }
1544
1545 #[test]
1546 fn test_apply_changes_resolve_conflict() {
1547 let mut deb822: deb822_lossless::Deb822 = r#"Source: blah
1548Build-Depends: debhelper (>= 6), foo
1549
1550Package: bar
1551"#
1552 .parse()
1553 .unwrap();
1554
1555 let mut changes = Deb822Changes(std::collections::HashMap::new());
1556 changes.0.insert(
1557 ("Source".to_string(), "blah".to_string()),
1558 vec![(
1559 "Build-Depends".to_string(),
1560 Some("debhelper (>= 7), foo".to_string()),
1561 Some("debhelper (>= 10), foo".to_string()),
1562 )],
1563 );
1564
1565 let result = super::apply_changes(&mut deb822, changes, Some(|_, _, _, _, _| Ok(None)));
1566 assert!(result.is_ok());
1567 assert_eq!(
1568 deb822.to_string(),
1569 r#"Source: blah
1570
1571Package: bar
1572"#
1573 );
1574 }
1575
1576 mod control_editor {
1577 #[test]
1578 fn test_do_not_edit() {
1579 let td = tempfile::tempdir().unwrap();
1580 std::fs::create_dir(td.path().join("debian")).unwrap();
1581 std::fs::write(
1582 td.path().join("debian/control"),
1583 r#"# DO NOT EDIT
1584# This file was generated by blah
1585
1586Source: blah
1587Testsuite: autopkgtest
1588
1589"#,
1590 )
1591 .unwrap();
1592 let mut editor =
1593 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1594 editor.source().unwrap().set_name("foo");
1595 let changes = editor.changes();
1596 assert_eq!(
1597 changes.normalized(),
1598 vec![
1599 (
1600 ("Source", "blah"),
1601 vec![
1602 ("Source", Some("blah"), None),
1603 ("Testsuite", Some("autopkgtest"), None),
1604 ]
1605 ),
1606 (
1607 ("Source", "foo"),
1608 vec![
1609 ("Source", None, Some("foo")),
1610 ("Testsuite", None, Some("autopkgtest"))
1611 ]
1612 )
1613 ]
1614 );
1615 assert!(matches!(
1616 editor.commit().unwrap_err(),
1617 super::EditorError::GeneratedFile(_, _)
1618 ));
1619 }
1620
1621 #[test]
1622 fn test_add_binary() {
1623 let td = tempfile::tempdir().unwrap();
1624 std::fs::create_dir(td.path().join("debian")).unwrap();
1625 std::fs::write(
1626 td.path().join("debian/control"),
1627 r#"Source: blah
1628Testsuite: autopkgtest
1629
1630Package: blah
1631Description: Some description
1632 And there are more lines
1633 And more lines
1634"#,
1635 )
1636 .unwrap();
1637 let mut editor =
1638 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1639 let mut binary = editor.add_binary("foo");
1640 binary.set_description(Some("A new package foo"));
1641 let paths = editor.commit().unwrap();
1642 assert_eq!(paths.len(), 1);
1643 }
1644
1645 #[test]
1646 fn test_list_binaries() {
1647 let td = tempfile::tempdir().unwrap();
1648 std::fs::create_dir(td.path().join("debian")).unwrap();
1649 std::fs::write(
1650 td.path().join("debian/control"),
1651 r#"Source: blah
1652Testsuite: autopkgtest
1653
1654Package: blah
1655Description: Some description
1656 And there are more lines
1657 And more lines
1658"#,
1659 )
1660 .unwrap();
1661 let mut editor =
1662 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1663 let binaries = editor.binaries().collect::<Vec<_>>();
1664 assert_eq!(binaries.len(), 1);
1665 assert_eq!(binaries[0].name().as_deref(), Some("blah"));
1666 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1667 }
1668
1669 #[test]
1670 fn test_no_source() {
1671 let td = tempfile::tempdir().unwrap();
1672 std::fs::create_dir(td.path().join("debian")).unwrap();
1673 std::fs::write(
1674 td.path().join("debian/control"),
1675 r#"Package: blah
1676Testsuite: autopkgtest
1677
1678Package: bar
1679"#,
1680 )
1681 .unwrap();
1682 let editor =
1683 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1684 assert!(editor.source().is_none());
1685 }
1686
1687 #[test]
1688 fn test_create() {
1689 let td = tempfile::tempdir().unwrap();
1690 std::fs::create_dir(td.path().join("debian")).unwrap();
1691 let mut editor =
1692 super::TemplatedControlEditor::create(td.path().join("debian/control")).unwrap();
1693 editor.add_source("foo");
1694 assert_eq!(
1695 r#"Source: foo
1696"#,
1697 editor.as_deb822().to_string()
1698 );
1699 }
1700
1701 #[test]
1702 fn test_do_not_edit_no_change() {
1703 let td = tempfile::tempdir().unwrap();
1704 std::fs::create_dir(td.path().join("debian")).unwrap();
1705 std::fs::write(
1706 td.path().join("debian/control"),
1707 r#"# DO NOT EDIT
1708# This file was generated by blah
1709
1710Source: blah
1711Testsuite: autopkgtest
1712
1713"#,
1714 )
1715 .unwrap();
1716 let mut editor =
1717 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1718 assert_eq!(editor.commit().unwrap(), Vec::<&std::path::Path>::new());
1719 }
1720
1721 #[test]
1722 fn test_unpreservable() {
1723 let td = tempfile::tempdir().unwrap();
1724 std::fs::create_dir(td.path().join("debian")).unwrap();
1725 std::fs::write(
1726 td.path().join("debian/control"),
1727 r#"Source: blah
1728# A comment
1729Testsuite: autopkgtest
1730
1731"#,
1732 )
1733 .unwrap();
1734 let mut editor =
1735 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1736 editor
1737 .source()
1738 .unwrap()
1739 .as_mut_deb822()
1740 .set("NewField", "New Field");
1741
1742 editor.commit().unwrap();
1743
1744 assert_eq!(
1745 r#"Source: blah
1746# A comment
1747Testsuite: autopkgtest
1748NewField: New Field
1749
1750"#,
1751 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1752 );
1753 }
1754
1755 #[test]
1756 fn test_modify_source() {
1757 let td = tempfile::tempdir().unwrap();
1758 std::fs::create_dir(td.path().join("debian")).unwrap();
1759 std::fs::write(
1760 td.path().join("debian/control"),
1761 r#"Source: blah
1762Testsuite: autopkgtest
1763"#,
1764 )
1765 .unwrap();
1766
1767 let mut editor =
1768 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1769 editor
1770 .source()
1771 .unwrap()
1772 .as_mut_deb822()
1773 .set("XS-Vcs-Git", "git://github.com/example/example");
1774
1775 editor.commit().unwrap();
1776
1777 assert_eq!(
1778 r#"Source: blah
1779Testsuite: autopkgtest
1780XS-Vcs-Git: git://github.com/example/example
1781"#,
1782 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1783 );
1784 }
1785
1786 #[test]
1867 fn test_modify_binary() {
1868 let td = tempfile::tempdir().unwrap();
1869 std::fs::create_dir(td.path().join("debian")).unwrap();
1870 std::fs::write(
1871 td.path().join("debian/control"),
1872 r#"Source: blah
1873Testsuite: autopkgtest
1874
1875Package: libblah
1876Section: extra
1877"#,
1878 )
1879 .unwrap();
1880
1881 let mut editor =
1882 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1883 let mut binary = editor
1884 .binaries()
1885 .find(|b| b.name().as_deref() == Some("libblah"))
1886 .unwrap();
1887 binary.set_architecture(Some("all"));
1888
1889 editor.commit().unwrap();
1890
1891 assert_eq!(
1892 r#"Source: blah
1893Testsuite: autopkgtest
1894
1895Package: libblah
1896Architecture: all
1897Section: extra
1898"#,
1899 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1900 );
1901 }
1902
1903 #[test]
1904 fn test_doesnt_strip_whitespace() {
1905 let td = tempfile::tempdir().unwrap();
1906 std::fs::create_dir(td.path().join("debian")).unwrap();
1907 std::fs::write(
1908 td.path().join("debian/control"),
1909 r#"Source: blah
1910Testsuite: autopkgtest
1911
1912"#,
1913 )
1914 .unwrap();
1915 let mut editor =
1916 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1917 editor.commit().unwrap();
1918
1919 assert_eq!(
1920 r#"Source: blah
1921Testsuite: autopkgtest
1922
1923"#,
1924 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
1925 );
1926 }
1927
1928 #[cfg(unix)]
1929 #[test]
1930 fn test_update_template() {
1931 use std::os::unix::fs::PermissionsExt;
1932 let td = tempfile::tempdir().unwrap();
1933 std::fs::create_dir(td.path().join("debian")).unwrap();
1934 std::fs::write(
1935 td.path().join("debian/control"),
1936 r#"# DO NOT EDIT
1937# This file was generated by blah
1938
1939Source: blah
1940Testsuite: autopkgtest
1941Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
1942
1943"#,
1944 )
1945 .unwrap();
1946 std::fs::write(
1947 td.path().join("debian/control.in"),
1948 r#"Source: blah
1949Testsuite: autopkgtest
1950Uploaders: @lintian-brush-test@
1951
1952"#,
1953 )
1954 .unwrap();
1955 std::fs::write(
1956 td.path().join("debian/rules"),
1957 r#"#!/usr/bin/make -f
1958
1959debian/control: debian/control.in
1960 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
1961"#,
1962 )
1963 .unwrap();
1964 std::fs::set_permissions(
1966 td.path().join("debian/rules"),
1967 std::fs::Permissions::from_mode(0o755),
1968 )
1969 .unwrap();
1970
1971 let mut editor =
1972 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
1973 editor
1974 .source()
1975 .unwrap()
1976 .as_mut_deb822()
1977 .set("Testsuite", "autopkgtest8");
1978
1979 assert_eq!(
1980 editor.commit().unwrap(),
1981 vec![
1982 td.path().join("debian/control.in"),
1983 td.path().join("debian/control")
1984 ]
1985 );
1986
1987 assert_eq!(
1988 r#"Source: blah
1989Testsuite: autopkgtest8
1990Uploaders: @lintian-brush-test@
1991
1992"#,
1993 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
1994 );
1995
1996 assert_eq!(
1997 r#"Source: blah
1998Testsuite: autopkgtest8
1999Uploaders: testvalue
2000
2001"#,
2002 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2003 );
2004 }
2005
2006 #[cfg(unix)]
2007 #[test]
2008 fn test_update_template_only() {
2009 use std::os::unix::fs::PermissionsExt;
2010 let td = tempfile::tempdir().unwrap();
2011 std::fs::create_dir(td.path().join("debian")).unwrap();
2012 std::fs::write(
2013 td.path().join("debian/control.in"),
2014 r#"Source: blah
2015Testsuite: autopkgtest
2016Uploaders: @lintian-brush-test@
2017
2018"#,
2019 )
2020 .unwrap();
2021 std::fs::write(
2022 td.path().join("debian/rules"),
2023 r#"#!/usr/bin/make -f
2024
2025debian/control: debian/control.in
2026 sed -e 's/@lintian-brush-test@/testvalue/' < $< > $@
2027"#,
2028 )
2029 .unwrap();
2030
2031 std::fs::set_permissions(
2032 td.path().join("debian/rules"),
2033 std::fs::Permissions::from_mode(0o755),
2034 )
2035 .unwrap();
2036
2037 let mut editor =
2038 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2039 editor
2040 .source()
2041 .unwrap()
2042 .as_mut_deb822()
2043 .set("Testsuite", "autopkgtest8");
2044
2045 editor.commit().unwrap();
2046
2047 assert_eq!(
2048 r#"Source: blah
2049Testsuite: autopkgtest8
2050Uploaders: @lintian-brush-test@
2051
2052"#,
2053 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2054 );
2055
2056 assert!(!td.path().join("debian/control").exists());
2057 }
2058
2059 #[cfg(unix)]
2060 #[test]
2061 fn test_update_template_invalid_tokens() {
2062 use std::os::unix::fs::PermissionsExt;
2063 let td = tempfile::tempdir().unwrap();
2064 std::fs::create_dir(td.path().join("debian")).unwrap();
2065 std::fs::write(
2066 td.path().join("debian/control"),
2067 r#"# DO NOT EDIT
2068# This file was generated by blah
2069
2070Source: blah
2071Testsuite: autopkgtest
2072Uploaders: Jelmer Vernooij <jelmer@jelmer.uk>
2073"#,
2074 )
2075 .unwrap();
2076 std::fs::write(
2077 td.path().join("debian/control.in"),
2078 r#"Source: blah
2079Testsuite: autopkgtest
2080@OTHERSTUFF@
2081"#,
2082 )
2083 .unwrap();
2084
2085 std::fs::write(
2086 td.path().join("debian/rules"),
2087 r#"#!/usr/bin/make -f
2088
2089debian/control: debian/control.in
2090 sed -e 's/@OTHERSTUFF@/Vcs-Git: example.com/' < $< > $@
2091"#,
2092 )
2093 .unwrap();
2094
2095 std::fs::set_permissions(
2096 td.path().join("debian/rules"),
2097 std::fs::Permissions::from_mode(0o755),
2098 )
2099 .unwrap();
2100
2101 let mut editor =
2102 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2103 editor
2104 .source()
2105 .unwrap()
2106 .as_mut_deb822()
2107 .set("Testsuite", "autopkgtest8");
2108 editor.commit().unwrap();
2109
2110 assert_eq!(
2111 r#"Source: blah
2112Testsuite: autopkgtest8
2113@OTHERSTUFF@
2114"#,
2115 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2116 );
2117
2118 assert_eq!(
2119 r#"Source: blah
2120Testsuite: autopkgtest8
2121Vcs-Git: example.com
2122"#,
2123 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2124 );
2125 }
2126
2127 #[test]
2128 fn test_update_cdbs_template() {
2129 let td = tempfile::tempdir().unwrap();
2130 std::fs::create_dir(td.path().join("debian")).unwrap();
2131
2132 std::fs::write(
2133 td.path().join("debian/control"),
2134 r#"Source: blah
2135Testsuite: autopkgtest
2136Build-Depends: some-foo, libc6
2137
2138"#,
2139 )
2140 .unwrap();
2141
2142 std::fs::write(
2143 td.path().join("debian/control.in"),
2144 r#"Source: blah
2145Testsuite: autopkgtest
2146Build-Depends: @cdbs@, libc6
2147
2148"#,
2149 )
2150 .unwrap();
2151
2152 let mut editor =
2153 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2154
2155 editor
2156 .source()
2157 .unwrap()
2158 .as_mut_deb822()
2159 .set("Build-Depends", "some-foo, libc6, some-bar");
2160
2161 assert_eq!(
2162 editor
2163 .source()
2164 .unwrap()
2165 .build_depends()
2166 .unwrap()
2167 .to_string(),
2168 "some-foo, libc6, some-bar".to_string()
2169 );
2170
2171 assert_eq!(Some(super::TemplateType::Cdbs), editor.template_type());
2172
2173 assert_eq!(
2174 editor.commit().unwrap(),
2175 vec![
2176 td.path().join("debian/control.in"),
2177 td.path().join("debian/control")
2178 ]
2179 );
2180
2181 assert_eq!(
2182 r#"Source: blah
2183Testsuite: autopkgtest
2184Build-Depends: @cdbs@, libc6, some-bar
2185
2186"#,
2187 std::fs::read_to_string(td.path().join("debian/control.in")).unwrap()
2188 );
2189
2190 assert_eq!(
2191 r#"Source: blah
2192Testsuite: autopkgtest
2193Build-Depends: some-foo, libc6, some-bar
2194
2195"#,
2196 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2197 );
2198 }
2199
2200 #[test]
2201 #[ignore = "Not implemented yet"]
2202 fn test_description_stays_last() {
2203 let td = tempfile::tempdir().unwrap();
2204 std::fs::create_dir(td.path().join("debian")).unwrap();
2205 std::fs::write(
2206 td.path().join("debian/control"),
2207 r#"Source: blah
2208Testsuite: autopkgtest
2209
2210Package: libblah
2211Section: extra
2212Description: foo
2213 bar
2214
2215"#,
2216 )
2217 .unwrap();
2218
2219 let mut editor =
2220 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2221 editor
2222 .binaries()
2223 .find(|b| b.name().as_deref() == Some("libblah"))
2224 .unwrap()
2225 .set_architecture(Some("all"));
2226
2227 editor.commit().unwrap();
2228
2229 assert_eq!(
2230 r#"Source: blah
2231Testsuite: autopkgtest
2232
2233Package: libblah
2234Section: extra
2235Architecture: all
2236Description: foo
2237 bar
2238"#,
2239 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2240 );
2241 }
2242
2243 #[test]
2244 fn test_no_new_heading_whitespace() {
2245 let td = tempfile::tempdir().unwrap();
2246 std::fs::create_dir(td.path().join("debian")).unwrap();
2247 std::fs::write(
2248 td.path().join("debian/control"),
2249 r#"Source: blah
2250Build-Depends:
2251 debhelper-compat (= 11),
2252 uuid-dev
2253
2254"#,
2255 )
2256 .unwrap();
2257
2258 let mut editor =
2259 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2260 editor
2261 .source()
2262 .unwrap()
2263 .as_mut_deb822()
2264 .set("Build-Depends", "debhelper-compat (= 12),\nuuid-dev");
2265
2266 editor.commit().unwrap();
2267
2268 assert_eq!(
2269 r#"Source: blah
2270Build-Depends:
2271 debhelper-compat (= 12),
2272 uuid-dev
2273
2274"#,
2275 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2276 );
2277 }
2278
2279 #[test]
2280 fn test_control_clone_behavior() {
2281 use std::io::Cursor;
2282
2283 let content = b"Source: test\nMaintainer: Joe <joe@example.com>\n";
2284 let (control1, _) =
2285 debian_control::Control::read_relaxed(Cursor::new(content)).unwrap();
2286 let control2 = control1.clone();
2287
2288 println!(
2289 "control1 name before: {:?}",
2290 control1.source().unwrap().name()
2291 );
2292 println!(
2293 "control2 name before: {:?}",
2294 control2.source().unwrap().name()
2295 );
2296
2297 control2.source().unwrap().set_name("changed");
2299
2300 println!(
2301 "control1 name after: {:?}",
2302 control1.source().unwrap().name()
2303 );
2304 println!(
2305 "control2 name after: {:?}",
2306 control2.source().unwrap().name()
2307 );
2308
2309 }
2312
2313 #[test]
2314 fn test_revert_before_commit() {
2315 use crate::editor::Editor;
2316
2317 let td = tempfile::tempdir().unwrap();
2318 std::fs::create_dir(td.path().join("debian")).unwrap();
2319 std::fs::write(
2320 td.path().join("debian/control"),
2321 r#"Source: blah
2322Maintainer: Joe Developer <joe@example.com>
2323
2324Package: blah
2325Architecture: any
2326Description: Some package
2327"#,
2328 )
2329 .unwrap();
2330
2331 let mut editor =
2332 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2333
2334 assert_eq!(editor.source().unwrap().name(), Some("blah".to_string()));
2336 assert!(!editor.has_changed());
2337
2338 editor.source().unwrap().set_name("newname");
2340 assert_eq!(editor.source().unwrap().name(), Some("newname".to_string()));
2341 assert!(editor.has_changed());
2342
2343 editor.revert().unwrap();
2345 assert_eq!(editor.source().unwrap().name(), Some("blah".to_string()));
2346 assert!(!editor.has_changed());
2347
2348 assert_eq!(
2350 r#"Source: blah
2351Maintainer: Joe Developer <joe@example.com>
2352
2353Package: blah
2354Architecture: any
2355Description: Some package
2356"#,
2357 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2358 );
2359 }
2360
2361 #[test]
2362 fn test_revert_after_commit() {
2363 use crate::editor::Editor;
2364
2365 let td = tempfile::tempdir().unwrap();
2366 std::fs::create_dir(td.path().join("debian")).unwrap();
2367 std::fs::write(
2368 td.path().join("debian/control"),
2369 r#"Source: blah
2370Maintainer: Joe Developer <joe@example.com>
2371
2372Package: blah
2373Architecture: any
2374Description: Some package
2375"#,
2376 )
2377 .unwrap();
2378
2379 let mut editor =
2380 super::TemplatedControlEditor::open(td.path().join("debian/control")).unwrap();
2381
2382 editor.source().unwrap().set_name("newname");
2384 assert!(editor.has_changed());
2385 editor.commit().unwrap();
2386 assert!(!editor.has_changed());
2387 assert_eq!(editor.source().unwrap().name(), Some("newname".to_string()));
2388
2389 let content = std::fs::read_to_string(td.path().join("debian/control")).unwrap();
2391 assert_eq!(
2392 r#"Source: newname
2393Maintainer: Joe Developer <joe@example.com>
2394
2395Package: blah
2396Architecture: any
2397Description: Some package
2398"#,
2399 content
2400 );
2401
2402 editor.source().unwrap().set_name("thirdname");
2404 assert_eq!(
2405 editor.source().unwrap().name(),
2406 Some("thirdname".to_string())
2407 );
2408 assert!(editor.has_changed());
2409
2410 editor.revert().unwrap();
2412 assert_eq!(editor.source().unwrap().name(), Some("newname".to_string()));
2413 assert!(!editor.has_changed());
2414
2415 assert_eq!(
2417 r#"Source: newname
2418Maintainer: Joe Developer <joe@example.com>
2419
2420Package: blah
2421Architecture: any
2422Description: Some package
2423"#,
2424 std::fs::read_to_string(td.path().join("debian/control")).unwrap()
2425 );
2426 }
2427 }
2428}