1use crate::config::{is_glob_pattern, PackageConfig};
2use crate::error::{CDResult, CargoDebError};
3use crate::listener::Listener;
4use crate::parse::manifest::CargoDebAssetArrayOrTable;
5use crate::util::compress::gzipped;
6use crate::util::read_file_to_bytes;
7use rayon::prelude::*;
8use std::borrow::Cow;
9use std::env::consts::DLL_SUFFIX;
10use std::path::{Path, PathBuf};
11use std::{fmt, fs};
12
13
14#[derive(Debug, Clone)]
15pub enum AssetSource {
16 Path(PathBuf),
18 Symlink(SymlinkKind),
20 Data(Vec<u8>),
22}
23
24#[derive(Debug, Clone)]
25pub enum SymlinkKind {
26 Copied {
29 source_path: PathBuf
30 },
31 Created {
33 target_path: PathBuf,
34 link_name: PathBuf,
35 }
36
37}
38
39impl AssetSource {
40 #[must_use]
42 pub fn from_path(path: impl Into<PathBuf>, preserve_symlinks: bool) -> Self {
43 let path = path.into();
44 if preserve_symlinks || !path.exists() { if let Ok(md) = fs::symlink_metadata(&path) {
46 if md.is_symlink() {
47 return Self::Symlink(SymlinkKind::Copied { source_path: path });
48 }
49 }
50 }
51 Self::Path(path)
52 }
53
54 #[must_use]
55 pub fn source_path(&self) -> Option<&Path> {
56 match self {
57 Self::Symlink(SymlinkKind ::Copied { source_path:p })
58 | Self::Path(p) => Some(p),
59 Self::Symlink(SymlinkKind::Created { .. })
60 | Self::Data(_) => None,
61 }
62 }
63
64 #[must_use]
65 pub fn into_path(self) -> Option<PathBuf> {
66 match self {
67 Self::Symlink(SymlinkKind ::Copied { source_path:p })
68 | Self::Path(p) => Some(p),
69 Self::Symlink(SymlinkKind::Created { .. })
70 | Self::Data(_) => None,
71 }
72 }
73
74 #[must_use]
75 pub fn archive_as_symlink_only(&self) -> bool {
76 matches!(self, Self::Symlink(_))
77 }
78
79 #[must_use]
80 pub fn file_size(&self) -> Option<u64> {
81 match *self {
82 Self::Path(ref p) => fs::metadata(p).ok().map(|m| m.len()),
83 Self::Data(ref d) => Some(d.len() as u64),
84 Self::Symlink(_) => None,
85 }
86 }
87
88 pub fn data(&self) -> CDResult<Cow<'_, [u8]>> {
89 Ok(match self {
90 Self::Path(p) => {
91 let data = read_file_to_bytes(p)
92 .map_err(|e| CargoDebError::IoFile("Unable to read asset to add to archive", e, p.clone()))?;
93 Cow::Owned(data)
94 },
95 Self::Data(d) => Cow::Borrowed(d),
96 Self::Symlink(SymlinkKind::Copied { source_path:p }) => {
97 let data = read_file_to_bytes(p)
98 .map_err(|e| CargoDebError::IoFile("Symlink unexpectedly used to read file data", e, p.clone()))?;
99 Cow::Owned(data)
100 },
101 AssetSource::Symlink(SymlinkKind::Created { target_path, link_name:_ }) => {
102 return Err(CargoDebError::CannotReadVirtualSymlink(target_path.to_owned()))
103 },
104 })
105 }
106
107 pub(crate) fn magic_bytes(&self) -> Option<[u8; 4]> {
108 match self {
109 Self::Path(p) | Self::Symlink(SymlinkKind::Copied { source_path:p }) => {
110 let mut buf = [0; 4];
111 use std::io::Read;
112 let mut file = std::fs::File::open(p).ok()?;
113 file.read_exact(&mut buf[..]).ok()?;
114 Some(buf)
115 },
116 Self::Data(d) => {
117 d.get(..4).and_then(|b| b.try_into().ok())
118 },
119 Self::Symlink(SymlinkKind::Created { .. }) => {
120 None
121 }
122 }
123 }
124}
125
126#[derive(Debug, Clone)]
127pub(crate) struct Assets {
128 pub unresolved: Vec<UnresolvedAsset>,
129 pub resolved: Vec<Asset>,
130}
131
132#[derive(Debug, Clone, serde::Deserialize)]
133#[serde(try_from = "CargoDebAssetArrayOrTable")]
134pub(crate) enum RawAssetOrAuto {
135 Auto,
136 RawAsset(RawAsset),
137}
138
139impl RawAssetOrAuto {
140 pub fn asset(self) -> Option<RawAsset> {
141 match self {
142 Self::RawAsset(a) => Some(a),
143 Self::Auto => None,
144 }
145 }
146}
147
148impl From<RawAsset> for RawAssetOrAuto {
149 fn from(r: RawAsset) -> Self {
150 Self::RawAsset(r)
151 }
152}
153
154#[derive(Debug, Clone, serde::Deserialize)]
155#[serde(try_from = "RawAssetOrAuto")]
156pub(crate) enum RawAsset {
157 Asset{
158 source_path: PathBuf,
159 target_path: PathBuf,
160 chmod: Option<u32>,
161 preserve_symlinks: Option<bool>,
162 },
163 Symlink {
164 target_path: PathBuf,
165 link_name: PathBuf
166 }
167}
168
169impl RawAsset {
170 pub(crate) fn source_path(&self) -> Option<&PathBuf> {
171 match self {
172 RawAsset::Asset { source_path, .. } => Some(source_path),
173 RawAsset::Symlink { .. } => None,
174 }
175 }
176
177 pub(crate) fn target_path(&self) -> &PathBuf {
178 match self {
179 RawAsset::Asset { target_path, .. }
180 | RawAsset::Symlink { target_path,.. } => target_path,
181 }
182 }
183
184 pub(crate) fn chmod(&self) -> Option<u32> {
185 match self {
186 RawAsset::Asset { chmod, .. } => *chmod,
187 RawAsset::Symlink { .. } => None ,
188 }
189 }
190
191 #[cfg(test)]
192 pub(crate) fn link_name(&self) -> Option<&PathBuf>{
193 match self {
194 RawAsset::Asset { .. } => None,
195 RawAsset::Symlink { link_name ,..} => Some(link_name),
196 }
197 }
198}
199
200impl TryFrom<RawAssetOrAuto> for RawAsset {
201 type Error = &'static str;
202
203 fn try_from(maybe_auto: RawAssetOrAuto) -> Result<Self, Self::Error> {
204 maybe_auto.asset().ok_or("$auto is not allowed here")
205 }
206}
207
208impl Assets {
209 pub(crate) const fn new(unresolved: Vec<UnresolvedAsset>, resolved: Vec<Asset>) -> Self {
210 Self {
211 unresolved,
212 resolved,
213 }
214 }
215
216 pub(crate) fn iter(&self) -> impl Iterator<Item = &AssetCommon> {
217 self.resolved.iter().map(|u| &u.c).chain(self.unresolved.iter().map(UnresolvedAsset::common))
218 }
219}
220
221#[derive(Debug, Copy, Clone, Eq, PartialEq)]
222pub enum AssetKind {
223 Any,
224 CargoExampleBinary,
225 SeparateDebugSymbols,
226}
227
228#[derive(Debug, Copy, Clone, Eq, PartialEq)]
229pub enum IsBuilt {
230 No,
231 SamePackage,
232 Workspace,
234}
235
236fn get_file_mode(path: &Path) -> CDResult<u32> {
237
238 #[cfg(not(unix))]
239 {
240 Err(CargoDebError::ImplicitFileModeFromPathNotSupported(path.to_path_buf()))
241 }
242
243 #[cfg(unix)]
244 {
245 let metadata = fs::metadata(path)
246 .map_err(|e| CargoDebError::IoFile(
247 "Unable to read file metadata for permissions",
248 e,
249 path.to_owned()
250 ))?;
251 use std::os::unix::fs::PermissionsExt;
252 Ok(metadata.permissions().mode() & 0o7777)
253 }
254}
255
256#[derive(Debug, Clone)]
257pub enum UnresolvedAsset {
258 Asset {
259 source_path: PathBuf,
260 preserve_symlinks: bool,
261 c: AssetCommon,
262 },
263 Symlink {
264 link_name: PathBuf,
265 c: AssetCommon,
266 }
267}
268
269impl UnresolvedAsset {
270 pub(crate) fn new_asset(source_path: PathBuf, target_path: PathBuf, chmod: Option<u32>, is_built: IsBuilt, asset_kind: AssetKind, preserve_symlinks: bool) -> Self {
271 Self::Asset {
272 source_path,
273 preserve_symlinks,
274 c: AssetCommon { target_path, chmod, asset_kind, is_built },
275 }
276 }
277 pub(crate) fn new_symlink( target_path: PathBuf, link_name: PathBuf) -> Self {
278 Self::Symlink {
279 link_name,
280 c: AssetCommon { target_path, chmod: None, asset_kind: AssetKind::Any, is_built: IsBuilt::No },
281 }
282 }
283
284 pub fn common(&self) ->&AssetCommon {
285 match self {
286 UnresolvedAsset::Asset {c, .. }
287 | UnresolvedAsset::Symlink {c, .. } => c,
288 }
289 }
290
291
292 pub fn resolve(&self) -> CDResult<Vec<Asset>> {
294 let (source_path, &preserve_symlinks, &AssetCommon { ref target_path, chmod, is_built, asset_kind }) = match self {
295 UnresolvedAsset::Symlink { link_name, c } => {
296 let target_path = Asset::normalized_target_path(c.target_path.to_owned(), Some(link_name));
297
298 return Ok(vec![Asset{
299 source: AssetSource::Symlink(SymlinkKind::Created { target_path: target_path.clone(), link_name: link_name.to_owned() }),
300 processed_from: Some(ProcessedFrom { original_path: None, action: "symlink" }), c: AssetCommon { target_path, ..c.clone() }
301 }])
302 },
303 UnresolvedAsset::Asset { source_path, preserve_symlinks, c } => {
304 (source_path, preserve_symlinks, c)
305 },
306 };
307
308 let source_prefix_len = is_glob_pattern(source_path.as_os_str()).then(|| {
309 let file_name_is_glob = source_path
310 .file_name()
311 .is_some_and(is_glob_pattern);
312
313 if file_name_is_glob {
314 let glob_component_pos = source_path
316 .parent()
317 .and_then(|parent| parent.iter().position(is_glob_pattern));
318 glob_component_pos.unwrap_or_else(|| {
319 source_path
320 .iter()
321 .count()
322 })
323 } else {
324 source_path
326 .iter()
327 .count()
328 .saturating_sub(1)
329 }
330 });
331
332 let matched_assets = glob::glob(source_path.to_str().ok_or("utf8 path")?)?
333 .map(|entry| {
335 let source_file = entry?;
336 Ok(if source_file.is_dir() { None } else { Some(source_file) })
337 })
338 .filter_map(|res: Result<Option<PathBuf>, glob::GlobError>| {
339 Some(res.transpose()?.map_err(CargoDebError::from).and_then(|source_file| {
340 let target_file = if let Some(source_prefix_len) = source_prefix_len {
341 target_path.join(
342 source_file
343 .iter()
344 .skip(source_prefix_len)
345 .collect::<PathBuf>())
346 } else {
347 target_path.clone()
348 };
349 let file_chmod = match chmod {
351 Some(chmod) => chmod,
352 None => get_file_mode(&source_file)?,
353 };
354 log::debug!("asset {} -> {} {} {:o}", source_file.display(), target_file.display(), if is_built != IsBuilt::No {"copy"} else {"build"}, file_chmod);
355
356 let asset = Asset::new(
357 AssetSource::from_path(source_file, preserve_symlinks),
358 target_file,
359 Some(file_chmod),
360 is_built,
361 asset_kind,
362 );
363 if source_prefix_len.is_some() {
364 Ok(asset.processed("glob", None))
365 } else {
366 Ok(asset)
367 }
368 }))
369 })
370 .collect::<CDResult<Vec<_>>>()
371 .map_err(|e| e.context(format_args!("Error while glob searching {}", source_path.display())))?;
372
373 if matched_assets.is_empty() {
374 return Err(CargoDebError::AssetFileNotFound(
375 source_path.clone(),
376 Asset::normalized_target_path(target_path.clone(), Some(source_path)),
377 source_prefix_len.is_some(), is_built != IsBuilt::No));
378 }
379 Ok(matched_assets)
380 }
381
382 pub(crate) fn source_path(&self) -> Option<&Path> {
383 match self {
384 UnresolvedAsset::Asset { source_path, .. } => Some(source_path),
385 UnresolvedAsset::Symlink { .. } => None,
386 }
387 }
388}
389
390#[derive(Debug, Clone)]
391pub struct AssetCommon {
392 pub target_path: PathBuf,
393 pub chmod: Option<u32>,
394 pub(crate) asset_kind: AssetKind,
395 is_built: IsBuilt,
396}
397
398pub(crate) struct AssetFmt<'a> {
399 c: &'a AssetCommon,
400 cwd: &'a Path,
401 source: Option<&'a Path>,
402 processed_from: Option<&'a ProcessedFrom>,
403}
404
405impl<'a> AssetFmt<'a> {
406 pub fn new(asset: &'a Asset, cwd: &'a Path) -> Self {
407 Self {
408 c: &asset.c,
409 source: asset.source.source_path(),
410 processed_from: asset.processed_from.as_ref(),
411 cwd,
412 }
413 }
414
415 pub fn unresolved(asset: &'a UnresolvedAsset, cwd: &'a Path) -> Self {
416 match asset {
417 UnresolvedAsset::Asset { source_path, preserve_symlinks:_, c } => {
418 Self {
419 c,
420 source: Some(source_path),
421 processed_from: None,
422 cwd,
423 }
424 },
425 UnresolvedAsset::Symlink { .. } => {
426 unreachable!()
427 },
428 }
429 }
430}
431
432impl fmt::Display for AssetFmt<'_> {
433 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
434 let mut src = self.source;
435 let action = self.processed_from.map(|proc| {
436 src = proc.original_path.as_deref().or(src);
437 proc.action
438 });
439 if let Some(src) = src {
440 write!(f, "{} ", src.strip_prefix(self.cwd).unwrap_or(src).display())?;
441 }
442 if let Some(action) = action {
443 write!(f, "({action}{}) ", if self.c.is_built() { "; built" } else { "" })?;
444 } else if self.c.is_built() {
445 write!(f, "(built) ")?;
446 }
447 write!(f, "-> {}", self.c.target_path.display())?;
448 Ok(())
449 }
450}
451
452#[derive(Debug, Clone)]
453pub struct Asset {
454 pub source: AssetSource,
455 pub processed_from: Option<ProcessedFrom>,
457 pub c: AssetCommon,
458}
459
460#[derive(Debug, Clone)]
461pub struct ProcessedFrom {
462 pub original_path: Option<PathBuf>,
463 pub action: &'static str,
464}
465
466impl Asset {
467 #[must_use]
468 pub fn normalized_target_path(mut target_path: PathBuf, source_path: Option<&Path>) -> PathBuf {
469 if target_path.to_string_lossy().ends_with('/') {
472 let file_name = source_path.and_then(|p| p.file_name()).expect("source must be a file");
473 target_path = target_path.join(file_name);
474 }
475
476 if target_path.is_absolute() || target_path.has_root() {
477 target_path = target_path.strip_prefix("/").expect("no root dir").to_owned();
478 }
479 target_path
480 }
481
482 #[must_use]
483 pub fn new(source: AssetSource, target_path: PathBuf, chmod: Option<u32>, is_built: IsBuilt, asset_kind: AssetKind) -> Self {
484 let target_path = Self::normalized_target_path(target_path, source.source_path());
485 Self {
486 source,
487 processed_from: None,
488 c: AssetCommon { target_path, chmod, asset_kind, is_built },
489 }
490 }
491
492 #[must_use]
493 pub fn processed(mut self, action: &'static str, original_path: impl Into<Option<PathBuf>>) -> Self {
494 debug_assert!(self.processed_from.is_none());
495 self.processed_from = Some(ProcessedFrom {
496 original_path: original_path.into(),
497 action,
498 });
499 self
500 }
501
502 pub(crate) fn is_binary_executable(&self) -> bool {
503 self.c.is_executable()
504 && self.c.target_path.extension().is_none_or(|ext| ext != "sh")
505 && (self.c.is_built() || self.smells_like_elf())
506 }
507
508 fn smells_like_elf(&self) -> bool {
509 self.source.magic_bytes().is_some_and(|b| b == [0x7F, b'E', b'L', b'F'])
510 }
511}
512
513impl AssetCommon {
514 pub(crate) const fn is_executable(&self) -> bool {
515 if let Some(chmod) = self.chmod {
516 0 != chmod & 0o111
517 } else {
518 false
519 }
520 }
521
522 pub(crate) fn is_dynamic_library(&self) -> bool {
523 is_dynamic_library_filename(&self.target_path)
524 }
525
526 pub(crate) fn is_built(&self) -> bool {
527 self.is_built != IsBuilt::No
528 }
529
530 #[must_use]
533 pub(crate) fn default_debug_target_path(&self, lib_dir_base: &Path) -> PathBuf {
534 let relative = self.target_path.strip_prefix(Path::new("/"))
536 .unwrap_or(self.target_path.as_path());
537
538 let mut path = Path::new("/").join(lib_dir_base);
540 path.push("debug");
541 path.push(debug_filename(relative));
542 path
543 }
544
545 pub(crate) fn is_same_package(&self) -> bool {
546 self.is_built == IsBuilt::SamePackage
547 }
548}
549
550fn debug_filename(path: &Path) -> PathBuf {
552 let mut debug_filename = path.as_os_str().to_os_string();
553 debug_filename.push(".debug");
554 debug_filename.into()
555}
556
557pub(crate) fn is_dynamic_library_filename(path: &Path) -> bool {
558 path.file_name()
559 .and_then(|f| f.to_str())
560 .is_some_and(|f| f.ends_with(DLL_SUFFIX))
561}
562
563pub fn compressed_assets(package_deb: &PackageConfig, listener: &dyn Listener) -> CDResult<Vec<(usize, Asset)>> {
570 fn needs_compression(path: &str) -> bool {
571 !path.ends_with(".gz") &&
572 (path.starts_with("usr/share/man/") ||
573 (path.starts_with("usr/share/doc/") && (path.ends_with("/NEWS") || path.ends_with("/changelog"))) ||
574 (path.starts_with("usr/share/info/") && path.ends_with(".info")))
575 }
576
577 package_deb.assets.resolved.iter().enumerate()
578 .filter(|(_, asset)| {
579 asset.c.target_path.starts_with("usr") && !asset.c.is_built() && needs_compression(&asset.c.target_path.to_string_lossy())
580 })
581 .par_bridge()
582 .map(|(idx, orig_asset)| {
583 let mut file_name = orig_asset.c.target_path.file_name().map(|f| f.to_string_lossy().into_owned()).unwrap_or_default();
584 file_name.push_str(".gz");
585 let new_path = orig_asset.c.target_path.with_file_name(file_name);
586 listener.progress("Compressing", format!("'{}'", new_path.display()));
587 let gzdata = gzipped(&orig_asset.source.data()?)
588 .map_err(|e| CargoDebError::Io(e).context("error while gzipping asset"))?;
589 CDResult::Ok((idx, Asset::new(
590 crate::assets::AssetSource::Data(gzdata),
591 new_path,
592 orig_asset.c.chmod,
593 IsBuilt::No,
594 AssetKind::Any,
595 ).processed("compressed",
596 orig_asset.source.source_path().unwrap_or(&orig_asset.c.target_path).to_path_buf()
597 )))
598 }).collect()
599}
600
601pub fn apply_compressed_assets(package_deb: &mut PackageConfig, new_assets: Vec<(usize, Asset)>) {
602 for (idx, asset) in new_assets {
603 package_deb.assets.resolved[idx] = asset;
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use crate::config::{BuildEnvironment, BuildOptions, DebConfigOverrides, DebugSymbolOptions};
611 use crate::parse::manifest::SystemdUnitsConfig;
612 use crate::util::tests::add_test_fs_paths;
613
614 #[test]
615 fn assets() {
616 let a = Asset::new(
617 AssetSource::Path(PathBuf::from("target/release/bar")),
618 PathBuf::from("baz/"),
619 Some(0o644),
620 IsBuilt::SamePackage,
621 AssetKind::Any,
622 );
623 assert_eq!("baz/bar", a.c.target_path.to_str().unwrap());
624 assert!(a.c.is_built != IsBuilt::No);
625
626 let a = Asset::new(
627 AssetSource::Path(PathBuf::from("foo/bar")),
628 PathBuf::from("/baz/quz"),
629 Some(0o644),
630 IsBuilt::No,
631 AssetKind::Any,
632 );
633 assert_eq!("baz/quz", a.c.target_path.to_str().unwrap());
634 assert!(a.c.is_built == IsBuilt::No);
635 }
636
637 #[test]
638 #[cfg(unix)]
639 fn resolve_without_permissions_reads_from_filesystem() {
640 let source_path = PathBuf::from("test-resources/testroot/src/main.rs");
642 assert!(source_path.exists(), "test file must exist");
643
644 let asset = UnresolvedAsset::Asset {
645 source_path: source_path.clone(),
646 preserve_symlinks: false,
647 c: AssetCommon {
648 target_path: PathBuf::from("usr/share/test/"),
649 chmod: None, asset_kind: AssetKind::Any,
651 is_built: IsBuilt::No,
652 },
653 };
654
655 let resolved = asset.resolve().unwrap();
656 assert_eq!(resolved.len(), 1);
657
658 let resolved_asset = &resolved[0];
659 assert!(resolved_asset.c.chmod.is_some(), "chmod should be read from filesystem when not specified in manifest");
661
662 use std::os::unix::fs::PermissionsExt;
664 let expected_mode = fs::metadata(&source_path).unwrap().permissions().mode() & 0o7777;
665 assert_eq!(resolved_asset.c.chmod.unwrap(), expected_mode);
666 }
667
668 #[test]
669 fn resolve_with_explicit_permissions_ignores_filesystem() {
670 let source_path = PathBuf::from("test-resources/testroot/src/main.rs");
672 assert!(source_path.exists(), "test file must exist");
673
674 let asset = UnresolvedAsset::Asset {
675 source_path: source_path.clone(),
676 preserve_symlinks: false,
677 c: AssetCommon {
678 target_path: PathBuf::from("usr/share/test/"),
679 chmod: Some(0o755), asset_kind: AssetKind::Any,
681 is_built: IsBuilt::No,
682 },
683 };
684
685 let resolved = asset.resolve().unwrap();
686 assert_eq!(resolved.len(), 1);
687 assert_eq!(resolved[0].c.chmod, Some(0o755), "explicit chmod should be preserved");
688 }
689
690
691 #[test]
692 fn resolve_created_symlink() {
693 let asset = UnresolvedAsset::Symlink {
694 link_name: PathBuf::from("../some.service"),
695 c: AssetCommon {
696 target_path: PathBuf::from("usr/lib/systemd/system/multi-user.target.wants/"),
697 chmod: None,
698 asset_kind: AssetKind::Any,
699 is_built: IsBuilt::No,
700 },
701 };
702
703 let resolved = asset.resolve().unwrap();
704 assert_eq!(resolved.len(), 1);
705 assert_eq!(&resolved[0].c.target_path, "usr/lib/systemd/system/multi-user.target.wants/some.service");
706 }
707
708 #[test]
709 fn assets_globs() {
710 for (glob, paths) in [
711 ("test-resources/testroot/src/*", &["bar/main.rs"][..]),
712 ("test-resources/testroot/*/main.rs", &["bar/main.rs"]),
713 ("test-resources/testroot/*/*", &["bar/src/main.rs", "bar/testchild/Cargo.toml"]),
714 ("test-resources/*/src/*", &["bar/testroot/src/main.rs"]),
715 ("test-resources/*/src/main.rs", &["bar/main.rs"]),
716 ("test-resources/*/*/main.rs", &["bar/main.rs"]),
717 ("test-resources/testroot/**/src/*", &["bar/src/main.rs", "bar/testchild/src/main.rs"]),
718 ("test-resources/testroot/**/*.rs", &["bar/src/main.rs", "bar/testchild/src/main.rs"]),
719 ] {
720 let asset = UnresolvedAsset::Asset {
721 source_path: PathBuf::from(glob),
722 preserve_symlinks: false,
723 c: AssetCommon {
724 target_path: PathBuf::from("bar/"),
725 chmod: Some(0o644),
726 asset_kind: AssetKind::Any,
727 is_built: IsBuilt::SamePackage,
728 },
729 };
730 let assets = asset
731 .resolve()
732 .unwrap()
733 .into_iter()
734 .map(|asset| asset.c.target_path.to_string_lossy().to_string())
735 .collect::<Vec<_>>();
736 if assets != paths {
737 panic!("Glob: `{glob}`:\n Expected: {paths:?}\n Got: {assets:?}");
738 }
739 }
740 }
741
742 #[test]
745 fn test_debug_filename() {
746 let path = Path::new("/my/test/file");
747 assert_eq!(debug_filename(path), Path::new("/my/test/file.debug"));
748 }
749
750 #[test]
753 fn test_debug_target_ok() {
754 let a = Asset::new(
755 AssetSource::Path(PathBuf::from("target/release/bar")),
756 PathBuf::from("/usr/bin/baz/"),
757 Some(0o644),
758 IsBuilt::SamePackage,
759 AssetKind::Any,
760 );
761 let debug_target = a.c.default_debug_target_path("usr/lib".as_ref());
762 assert_eq!(debug_target, Path::new("/usr/lib/debug/usr/bin/baz/bar.debug"));
763 }
764
765 #[test]
768 fn test_debug_target_ok_relative() {
769 let a = Asset::new(
770 AssetSource::Path(PathBuf::from("target/release/bar")),
771 PathBuf::from("baz/"),
772 Some(0o644),
773 IsBuilt::Workspace,
774 AssetKind::Any,
775 );
776 let debug_target = a.c.default_debug_target_path("usr/lib".as_ref());
777 assert_eq!(debug_target, Path::new("/usr/lib/debug/baz/bar.debug"));
778 }
779
780 fn to_canon_static_str(s: &str) -> &'static str {
781 let cwd = std::env::current_dir().unwrap();
782 let abs_path = cwd.join(s);
783 let abs_path_string = abs_path.to_string_lossy().into_owned();
784 Box::leak(abs_path_string.into_boxed_str())
785 }
786
787 #[test]
788 fn add_systemd_assets_with_no_config_does_nothing() {
789 let mut mock_listener = crate::listener::MockListener::new();
790 mock_listener.expect_progress().return_const(());
791
792 let _g = add_test_fs_paths(&[to_canon_static_str("cargo-deb.service")]);
794
795 let (_config, mut package_debs) = BuildEnvironment::from_manifest(BuildOptions {
796 manifest_path: Some(Path::new("Cargo.toml")),
797 debug: DebugSymbolOptions {
798 #[cfg(feature = "default_enable_dbgsym")]
799 generate_dbgsym_package: Some(false),
800 #[cfg(feature = "default_enable_separate_debug_symbols")]
801 separate_debug_symbols: Some(false),
802 ..Default::default()
803 },
804 ..Default::default()
805 }, &mock_listener).unwrap();
806 let package_deb = package_debs.pop().unwrap();
807
808 let num_unit_assets = package_deb.assets.resolved.iter()
809 .filter(|a| a.c.target_path.starts_with("usr/lib/systemd/system/"))
810 .count();
811
812 assert_eq!(0, num_unit_assets);
813 }
814
815 #[test]
816 fn add_systemd_assets_with_config_adds_unit_assets() {
817 let mut mock_listener = crate::listener::MockListener::new();
818 mock_listener.expect_progress().return_const(());
819
820 let _g = add_test_fs_paths(&[to_canon_static_str("cargo-deb.service")]);
822
823 let (_config, mut package_debs) = BuildEnvironment::from_manifest(BuildOptions {
824 manifest_path: Some(Path::new("Cargo.toml")),
825 debug: DebugSymbolOptions {
826 #[cfg(feature = "default_enable_dbgsym")]
827 generate_dbgsym_package: Some(false),
828 #[cfg(feature = "default_enable_separate_debug_symbols")]
829 separate_debug_symbols: Some(false),
830 ..Default::default()
831 },
832 overrides: DebConfigOverrides {
833 systemd_units: Some(vec![SystemdUnitsConfig::default()]),
834 maintainer_scripts_rel_path: Some(PathBuf::new()),
835 ..Default::default()
836 },
837 ..Default::default()
838 }, &mock_listener).unwrap();
839 let package_deb = package_debs.pop().unwrap();
840
841 let num_unit_assets = package_deb.assets.resolved
842 .iter()
843 .filter(|a| a.c.target_path.starts_with("usr/lib/systemd/system/"))
844 .count();
845
846 assert_eq!(1, num_unit_assets);
847 }
848}