1use crate::assets::{RawAsset, RawAssetOrAuto};
2use crate::config::BuildProfile;
3use crate::error::{CDResult, CargoDebError};
4use crate::listener::Listener;
5use crate::CargoLockingFlags;
6use cargo_toml::{DebugSetting, StripSetting};
7use log::debug;
8use serde::de::DeserializeOwned;
9use serde::Deserialize;
10use std::borrow::Cow;
11use std::collections::{BTreeMap, HashMap};
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::process::Command;
15
16#[derive(Clone, Debug, Deserialize, Default)]
28#[serde(rename_all = "kebab-case", deny_unknown_fields)]
29pub(crate) struct SystemdUnitsConfig {
30 pub unit_scripts: Option<PathBuf>,
31 pub unit_name: Option<String>,
32 pub enable: Option<bool>,
33 pub start: Option<bool>,
34 pub restart_after_upgrade: Option<bool>,
35 pub stop_on_upgrade: Option<bool>,
36}
37
38#[derive(PartialEq, Copy, Clone, Debug)]
39pub(crate) enum ManifestDebugFlags {
40 FullyStrippedByCargo,
42 SymbolsDisabled,
44 SymbolsPackedExternally,
45 SomeSymbolsAdded,
46 FullSymbolsAdded,
47 Default,
49}
50
51pub(crate) fn find_profile<'a>(manifest: &'a cargo_toml::Manifest<CargoPackageMetadata>, selected_profile: &str) -> Option<&'a cargo_toml::Profile> {
52 if selected_profile == "release" {
53 manifest.profile.release.as_ref()
54 } else {
55 manifest.profile.custom.get(selected_profile)
56 }
57}
58
59fn from_toml_value<T: DeserializeOwned>(toml: &str) -> Option<T> {
60 toml::de::ValueDeserializer::parse(toml).and_then(|deserializer| T::deserialize(deserializer)).ok().or_else(|| {
62 toml::de::ValueDeserializer::parse(&format!("\"{toml}\"")).and_then(|deserializer| T::deserialize(deserializer))
63 .inspect_err(|e| log::warn!("error parsing profile override: {toml}\n{e}")).ok()
64 })
65}
66
67pub(crate) fn debug_flags(manifest_profile: Option<&cargo_toml::Profile>, profile_override: &BuildProfile) -> ManifestDebugFlags {
68 let profile_uppercase = profile_override.profile_name().to_ascii_uppercase();
69 let cargo_var = |name| {
70 let name = format!("CARGO_PROFILE_{profile_uppercase}_{name}");
71 std::env::var(&name).ok().inspect(|v| log::debug!("{name} = {v}"))
72 };
73
74 let strip = cargo_var("STRIP").and_then(|var| from_toml_value::<StripSetting>(&var))
75 .or(manifest_profile.and_then(|p| p.strip)).inspect(|v| log::debug!("strip={v:?}"));
76 if strip == Some(StripSetting::Symbols) {
77 return ManifestDebugFlags::FullyStrippedByCargo;
78 }
79
80 let debug = profile_override.override_debug.clone().inspect(|o| log::debug!("override={o}")).or_else(|| cargo_var("DEBUG"))
81 .and_then(|var| from_toml_value::<DebugSetting>(&var))
82 .or(manifest_profile.and_then(|p| p.debug)).inspect(|v| log::debug!("debug={v:?}"));
83 match debug {
84 None => ManifestDebugFlags::Default,
85 Some(DebugSetting::None) => ManifestDebugFlags::SymbolsDisabled,
86 Some(_) if manifest_profile.and_then(|p| p.split_debuginfo.as_deref()).is_some_and(|p| p != "off") => ManifestDebugFlags::SymbolsPackedExternally,
87 Some(DebugSetting::Full) if strip != Some(StripSetting::Debuginfo) => ManifestDebugFlags::FullSymbolsAdded,
88 Some(_) => ManifestDebugFlags::SomeSymbolsAdded,
89 }
90}
91
92pub(crate) fn manifest_version_string<'a>(package: &'a cargo_toml::Package<CargoPackageMetadata>, revision: Option<&str>) -> Cow<'a, str> {
94 let mut version = Cow::Borrowed(package.version());
95
96 if let Some((semver_main, semver_pre)) = version.split_once('-') {
100 let pre_ascii = semver_pre.as_bytes();
101 if pre_ascii.iter().any(|c| !c.is_ascii_digit()) && pre_ascii.iter().any(u8::is_ascii_digit) {
102 version = Cow::Owned(format!("{semver_main}~{semver_pre}"));
103 }
104 }
105
106 let revision = revision.unwrap_or("1");
107 if !revision.is_empty() && revision != "0" {
108 let v = version.to_mut();
109 v.push('-');
110 v.push_str(revision);
111 }
112 version
113}
114
115#[derive(Clone, Debug, Deserialize, Default)]
116pub(crate) struct CargoPackageMetadata {
117 pub deb: Option<CargoDeb>,
118}
119
120#[derive(Clone, Debug, Deserialize)]
121#[serde(untagged)]
122pub(crate) enum LicenseFile {
123 String(String),
124 Vec(Vec<String>),
125}
126
127#[derive(Deserialize, Clone, Debug)]
128#[serde(untagged)]
129pub(crate) enum SystemUnitsSingleOrMultiple {
130 Single(SystemdUnitsConfig),
131 Multi(Vec<SystemdUnitsConfig>),
132}
133
134#[derive(Clone, Debug, Deserialize)]
135#[serde(untagged)]
136pub(crate) enum DependencyList {
137 String(String),
138 Vec(Vec<String>),
139}
140
141impl DependencyList {
142 pub(crate) fn to_depends_string(&self) -> String {
143 match self {
144 Self::String(s) => s.to_owned(),
145 Self::Vec(vals) => vals.join(", "),
146 }
147 }
148}
149
150pub(crate) type RawAssetList = Vec<RawAssetOrAuto>;
152
153#[derive(Default)]
154pub(crate) struct MergeMap<'a> {
155 by_path: BTreeMap<&'a PathBuf, &'a RawAsset>,
156 has_auto: bool,
157}
158
159#[derive(Deserialize)]
160#[serde(untagged)]
161pub(crate) enum CargoDebAssetArrayOrTable {
162 Table(CargoDebAsset),
163 Symlink(CargoDebSymlink),
164 Array(Vec<String>),
165 Auto(String),
166 Invalid(toml::Value),
167}
168
169#[derive(Clone, Debug, Deserialize, Default)]
170pub(crate) struct CargoDebAsset {
171 pub source: String,
172 pub dest: String,
173 pub mode: Option<String>,
174 pub preserve_symlinks: Option<bool>,
175}
176
177#[derive(Clone, Debug, Deserialize, Default)]
178pub(crate) struct CargoDebSymlink {
179 pub dest: String,
180 pub link_name: String,
181}
182
183#[derive(Clone, Debug, Deserialize, Default)]
184#[serde(rename_all = "kebab-case", deny_unknown_fields)]
185pub(crate) struct CargoDeb {
186 pub name: Option<String>,
187 pub maintainer: Option<String>,
188 pub copyright: Option<String>,
189 pub license_file: Option<LicenseFile>,
190 pub changelog: Option<String>,
191 pub depends: Option<DependencyList>,
192 pub pre_depends: Option<DependencyList>,
193 pub recommends: Option<DependencyList>,
194 pub suggests: Option<DependencyList>,
195 pub enhances: Option<DependencyList>,
196 pub conflicts: Option<DependencyList>,
197 pub breaks: Option<DependencyList>,
198 pub replaces: Option<DependencyList>,
199 pub provides: Option<DependencyList>,
200 pub extended_description: Option<String>,
201 pub extended_description_file: Option<String>,
202 pub section: Option<String>,
203 pub priority: Option<String>,
204 pub revision: Option<String>,
205 pub conf_files: Option<Vec<String>>,
206 pub assets: Option<RawAssetList>,
207 pub merge_assets: Option<MergeAssets>,
208 pub triggers_file: Option<String>,
209 pub maintainer_scripts: Option<String>,
210 pub features: Option<Vec<String>>,
211 pub default_features: Option<bool>,
212 pub separate_debug_symbols: Option<bool>,
213 pub dbgsym: Option<bool>,
214 pub compress_debug_symbols: Option<bool>,
215 pub preserve_symlinks: Option<bool>,
216 pub systemd_units: Option<SystemUnitsSingleOrMultiple>,
217 pub variants: Option<HashMap<String, Self>>,
218
219 pub profile: Option<String>,
221}
222
223#[derive(Clone, Debug, Deserialize, Default)]
225#[serde(deny_unknown_fields)]
226pub(crate) struct MergeAssets {
227 pub append: Option<RawAssetList>,
229 pub by: Option<MergeByKey>,
231}
232
233#[derive(Clone, Debug, Deserialize)]
235pub(crate) enum MergeByKey {
236 #[serde(rename = "src")]
237 Src(RawAssetList),
238 #[serde(rename = "dest")]
239 Dest(RawAssetList),
240}
241
242impl MergeByKey {
243 fn merge(self, parent: &RawAssetList) -> RawAssetList {
245 let mut merge_map = MergeMap::default();
246 for asset in parent {
247 match asset {
248 RawAssetOrAuto::Auto => { merge_map.has_auto = true; },
249 RawAssetOrAuto::RawAsset(asset) => self.prep_parent_item(&mut merge_map, asset),
250 }
251 }
252
253 self.merge_with(merge_map)
254 }
255
256 fn prep_parent_item<'a>(&'a self, merge_map: &mut MergeMap<'a>, asset: &'a RawAsset) {
259
260 match asset {
261 RawAsset::Asset { source_path:src, target_path:dest, .. } => {
262 match &self {
263 Self::Src(_) => {
264 merge_map.by_path.insert(src, asset);
265 },
266 Self::Dest(_) => {
267 merge_map.by_path.insert(dest, asset);
268 },
269 }
270 },
271 RawAsset::Symlink { target_path:dest, .. } => {
272 match self {
273 MergeByKey::Src(_) => {
274 },
276 MergeByKey::Dest(_) => {
277 merge_map.by_path.insert(dest, asset);
278 },
279 }
280 },
281 }
282 }
283
284 fn merge_with<'a>(&'a self, mut merge_map: MergeMap<'a>) -> RawAssetList {
287 let (assets, merge_fn): (_, fn(&mut MergeMap<'a>, &'a RawAsset)) = match self {
288 Self::Src(assets) => (
289 assets,
290 |parent, asset| {
291 if let Some(src) = asset.source_path() {
293 if let Some(replaced_asset) = parent.by_path.insert(src, asset) {
294 debug!("Replacing {:?} w/ {:?}", (replaced_asset.target_path(), replaced_asset.chmod()), (asset.target_path(), asset.chmod()));
295 }
296 }
297 },
298 ),
299 Self::Dest(assets) => (
300 assets,
301 |parent, asset| {
302 if let Some(replaces_asset) = parent.by_path.insert(asset.target_path(), asset) {
303 debug!("Replacing {:?} w/ {:?}", (replaces_asset.source_path(), replaces_asset.chmod()), (asset.source_path(), asset.chmod()));
304 }
305
306 },
307 ),
308 };
309
310 for asset in assets {
311 match asset {
312 RawAssetOrAuto::RawAsset(asset) => {
313 merge_fn(&mut merge_map, asset);
314 },
315 RawAssetOrAuto::Auto => merge_map.has_auto = true,
316 }
317 }
318
319 merge_map.by_path.into_values()
320 .cloned()
321 .map(RawAssetOrAuto::RawAsset)
322 .chain(merge_map.has_auto.then_some(RawAssetOrAuto::Auto))
323 .collect()
324 }
325}
326
327impl CargoDeb {
328 pub(crate) fn inherit_from(self, parent: Self, listener: &dyn Listener) -> Self {
333 let mut assets = self.assets.or(parent.assets);
334
335 if let Some(merge_assets) = self.merge_assets {
336 let old_assets = assets.get_or_insert_with(|| {
337 listener.warning("variant has merge-assets, but not assets to merge".into());
338 vec![]
339 });
340 if let Some(mut append) = merge_assets.append {
341 old_assets.append(&mut append);
342 }
343
344 if let Some(strategy) = merge_assets.by {
345 assets = Some(strategy.merge(old_assets));
346 }
347 }
348
349 Self {
350 name: self.name.or(parent.name),
351 maintainer: self.maintainer.or(parent.maintainer),
352 copyright: self.copyright.or(parent.copyright),
353 license_file: self.license_file.or(parent.license_file),
354 changelog: self.changelog.or(parent.changelog),
355 depends: self.depends.or(parent.depends),
356 pre_depends: self.pre_depends.or(parent.pre_depends),
357 recommends: self.recommends.or(parent.recommends),
358 suggests: self.suggests.or(parent.suggests),
359 enhances: self.enhances.or(parent.enhances),
360 conflicts: self.conflicts.or(parent.conflicts),
361 breaks: self.breaks.or(parent.breaks),
362 replaces: self.replaces.or(parent.replaces),
363 provides: self.provides.or(parent.provides),
364 extended_description: self.extended_description.or(parent.extended_description),
365 extended_description_file: self.extended_description_file.or(parent.extended_description_file),
366 section: self.section.or(parent.section),
367 priority: self.priority.or(parent.priority),
368 revision: self.revision.or(parent.revision),
369 conf_files: self.conf_files.or(parent.conf_files),
370 assets,
371 merge_assets: None,
372 triggers_file: self.triggers_file.or(parent.triggers_file),
373 maintainer_scripts: self.maintainer_scripts.or(parent.maintainer_scripts),
374 features: self.features.or(parent.features),
375 default_features: self.default_features.or(parent.default_features),
376 dbgsym: self.dbgsym.or(parent.dbgsym),
377 separate_debug_symbols: self.separate_debug_symbols.or(parent.separate_debug_symbols),
378 compress_debug_symbols: self.compress_debug_symbols.or(parent.compress_debug_symbols),
379 preserve_symlinks: self.preserve_symlinks.or(parent.preserve_symlinks),
380 systemd_units: self.systemd_units.or(parent.systemd_units),
381 variants: self.variants.or(parent.variants),
382 profile: self.profile.or(parent.profile),
383 }
384 }
385}
386
387#[derive(Deserialize)]
388struct CargoMetadata {
389 pub packages: Vec<CargoMetadataPackage>,
390 #[serde(default)]
391 pub workspace_members: Vec<String>,
392 #[serde(default)]
393 pub workspace_default_members: Vec<String>,
394 pub target_directory: String,
395 pub build_directory: Option<String>,
396 #[serde(default)]
397 pub workspace_root: String,
398}
399
400#[derive(Deserialize)]
401struct CargoMetadataPackage {
402 pub id: String,
403 pub name: String,
404 pub targets: Vec<CargoMetadataTarget>,
405 pub manifest_path: PathBuf,
406 pub metadata: Option<toml::Value>,
407}
408
409#[derive(Debug, Deserialize)]
410pub(crate) struct CargoMetadataTarget {
411 pub name: String,
412 pub kind: Vec<String>,
413 pub crate_types: Vec<String>,
414 pub src_path: PathBuf,
415}
416
417pub(crate) struct ManifestFound {
418 pub build_targets: Vec<CargoMetadataTarget>,
419 pub manifest_path: PathBuf,
420 pub workspace_root_manifest_path: PathBuf,
421 pub root_manifest: Option<cargo_toml::Manifest<CargoPackageMetadata>>,
422 pub target_dir: PathBuf,
423 pub build_dir: Option<PathBuf>,
424 pub manifest: cargo_toml::Manifest<CargoPackageMetadata>,
425}
426
427fn get_selected_package(metadata: &mut CargoMetadata, selected_package_name: Option<&str>) -> Result<CargoMetadataPackage, CargoDebError> {
428 let available_package_names = || {
429 metadata.packages.iter()
430 .filter(|p| metadata.workspace_members.iter().any(|w| w == &p.id))
431 .map(|p| p.name.as_str())
432 .collect::<Vec<_>>().join(", ")
433 };
434 let target_package_pos = if let Some(name) = selected_package_name {
435 let name_no_ver = name.split('@').next().unwrap_or_default();
436 metadata.packages.iter().position(|p| p.name == name_no_ver)
437 .ok_or_else(|| CargoDebError::PackageNotFoundInWorkspace(name.into(), available_package_names()))
438 } else {
439 pick_default_package_from_workspace(metadata)
440 .ok_or_else(|| CargoDebError::NoRootFoundInWorkspace(available_package_names()))
441 }?;
442 Ok(metadata.packages.swap_remove(target_package_pos))
443}
444
445fn pick_default_package_from_workspace(metadata: &CargoMetadata) -> Option<usize> {
446 if let [root_id] = metadata.workspace_default_members.as_slice() {
448 if let Some(pos) = metadata.packages.iter().position(move |p| &p.id == root_id) {
449 return Some(pos);
450 }
451 }
452
453 let root_manifest_path = Path::new(&metadata.workspace_root).join("Cargo.toml");
455 if let Some(pos) = metadata.packages.iter().position(move |p| p.manifest_path == root_manifest_path) {
456 return Some(pos);
457 }
458
459 let default_members = if !metadata.workspace_default_members.is_empty() {
461 &metadata.workspace_default_members[..]
462 } else {
463 &metadata.workspace_members
464 };
465 let mut packages_with_deb_meta = metadata.packages.iter().enumerate().filter_map(|(i, package)| {
466 if !package.metadata.as_ref()?.as_table()?.contains_key("deb") {
467 return None;
468 }
469 default_members.contains(&package.id).then_some(i)
470 });
471 let expected_single_id = packages_with_deb_meta.next()?;
472 packages_with_deb_meta.next().is_none().then_some(expected_single_id)
473}
474
475fn parse_manifest_only(manifest_path: &Path) -> Result<cargo_toml::Manifest<CargoPackageMetadata>, CargoDebError> {
476 let manifest_bytes = fs::read(manifest_path)
477 .map_err(|e| CargoDebError::IoFile("Unable to read manifest", e, manifest_path.to_owned()))?;
478
479 cargo_toml::Manifest::<CargoPackageMetadata>::from_slice_with_metadata(&manifest_bytes)
480 .map_err(|e| CargoDebError::TomlParsing(e, manifest_path.into()))
481}
482
483pub(crate) fn cargo_metadata(initial_manifest_path: Option<&Path>, selected_package_name: Option<&str>, cargo_locking_flags: CargoLockingFlags) -> Result<ManifestFound, CargoDebError> {
484 let mut metadata = run_cargo_metadata(initial_manifest_path, cargo_locking_flags)?;
485 let target_package = get_selected_package(&mut metadata, selected_package_name)?;
486
487 let target_dir = PathBuf::from(metadata.target_directory);
488 let workspace_root = PathBuf::from(metadata.workspace_root);
489 let build_dir = metadata.build_directory.map(PathBuf::from);
490
491 let manifest_path = Path::new(&target_package.manifest_path);
492 let mut manifest = parse_manifest_only(manifest_path)?;
493
494 let workspace_root_manifest_path = workspace_root.join("Cargo.toml");
495 let root_manifest = if manifest.workspace.is_none() && manifest_path != workspace_root_manifest_path {
496 parse_manifest_only(&workspace_root_manifest_path).inspect_err(|e| log::error!("{e}")).ok()
497 } else { None };
498
499 manifest.complete_from_path_and_workspace(manifest_path, root_manifest.as_ref().map(|ws| (ws, workspace_root.as_path())))
500 .map_err(move |e| CargoDebError::TomlParsing(e, manifest_path.to_path_buf()))?;
501
502 Ok(ManifestFound {
503 manifest_path: target_package.manifest_path,
504 workspace_root_manifest_path,
505 build_targets: target_package.targets,
506 root_manifest,
507 build_dir,
508 target_dir,
509 manifest,
510 })
511}
512
513fn run_cargo_metadata(manifest_rel_path: Option<&Path>, cargo_locking_flags: CargoLockingFlags) -> CDResult<CargoMetadata> {
516 let mut cmd = Command::new("cargo");
517 cmd.args(["metadata", "--format-version=1", "--no-deps"]);
518 cmd.args(cargo_locking_flags.flags());
519
520 if let Some(path) = manifest_rel_path {
521 cmd.args(["--manifest-path".as_ref(), path.as_os_str()]);
522 }
523
524 let output = cmd.output()
525 .map_err(|e| CargoDebError::CommandFailed(e, "cargo".into()))?;
526 if !output.status.success() {
527 return Err(CargoDebError::CommandError("cargo", "metadata".to_owned(), output.stderr));
528 }
529
530 Ok(serde_json::from_slice(&output.stdout)?)
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use crate::listener::NoOpListener;
537 use itertools::Itertools;
538
539
540 fn create_test_asset(src: impl Into<PathBuf>, target_path: impl Into<PathBuf>, perm: u32) -> RawAsset {
541 RawAsset::Asset {
542 source_path: src.into(), target_path: target_path.into(), chmod: Some(perm), preserve_symlinks: None,
543 }
544 }
545
546 fn create_test_symlink(target_path: impl Into<PathBuf>, link_name: impl Into<PathBuf>) -> RawAsset {
547 RawAsset::Symlink { target_path: target_path.into(), link_name: link_name.into() }
548 }
549
550 #[test]
551 fn test_merge_assets() {
552 let original_asset = create_test_asset(
554 "lib/test/empty.txt",
555 "/opt/test/empty.txt",
556 0o777
557 );
558
559 let merge_asset = create_test_asset(
560 "lib/test_variant/empty.txt",
561 "/opt/test/empty.txt",
562 0o655,
563 );
564
565 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
566 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Dest(vec![ merge_asset.into() ])) }), .. Default::default() };
567
568 let merged = variant.inherit_from(parent, &NoOpListener);
569 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
570 let merged_asset = merged.pop().expect("should have an asset");
571 assert_eq!("lib/test_variant/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
572 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
573 assert_eq!(Some(0o655), merged_asset.chmod(), "should have merged the chmod");
574
575 let original_asset = create_test_asset(
577 "lib/test/empty.txt",
578 "/opt/test/empty.txt",
579 0o777
580 );
581
582 let merge_asset = create_test_asset(
583 "lib/test/empty.txt",
584 "/opt/test_variant/empty.txt",
585 0o655,
586 );
587
588 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
589 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Src(vec![ merge_asset.into() ])) }), .. Default::default() };
590
591 let merged = variant.inherit_from(parent, &NoOpListener);
592 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
593 let merged_asset = merged.pop().expect("should have an asset");
594 assert_eq!("lib/test/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
595 assert_eq!("/opt/test_variant/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
596 assert_eq!(Some(0o655), merged_asset.chmod(), "should have merged the chmod");
597
598 let original_asset = create_test_asset(
600 "lib/test/empty.txt",
601 "/opt/test/empty.txt",
602 0o777
603 );
604
605 let merge_asset = create_test_asset(
606 "lib/test/empty.txt",
607 "/opt/test_variant/empty.txt",
608 0o655,
609 );
610
611 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
612 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: Some(vec![merge_asset.into()]), by: None }), .. Default::default() };
613
614 let merged = variant.inherit_from(parent, &NoOpListener);
615 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
616
617 let merged_asset = merged.pop().expect("should have an asset");
618 assert_eq!("lib/test/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
619 assert_eq!("/opt/test_variant/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
620 assert_eq!(Some(0o655), merged_asset.chmod(), "should have merged the chmod");
621
622 let merged_asset = merged.pop().expect("should have an asset");
623 assert_eq!("lib/test/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
624 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
625 assert_eq!(Some(0o777), merged_asset.chmod(), "should have merged the chmod");
626
627 let original_asset = create_test_asset(
629 "lib/test/empty.txt",
630 "/opt/test/empty.txt",
631 0o777,
632 );
633
634 let merge_asset = create_test_asset(
635 "lib/test_variant/empty.txt",
636 "/opt/test/empty.txt",
637 0o655,
638 );
639
640 let additional_asset = create_test_asset(
641 "lib/test/other-empty.txt",
642 "/opt/test/other-empty.txt",
643 0o655,
644 );
645
646 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
647 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Dest(vec![ merge_asset.clone().into() ])) }), assets: Some(vec![ merge_asset.into(), additional_asset.into() ]), .. Default::default() };
648
649 let merged = variant.inherit_from(parent, &NoOpListener);
650 let mut merged = merged.assets.expect("should have assets");
651 let merged_asset = merged.remove(0).asset().unwrap();
652 assert_eq!("lib/test_variant/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
653 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
654 assert_eq!(Some(0o655), merged_asset.chmod(), "should have merged the chmod");
655
656 let additional_asset = merged.remove(0).asset().unwrap();
657 assert_eq!("lib/test/other-empty.txt", additional_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
658 assert_eq!("/opt/test/other-empty.txt", additional_asset.target_path().as_os_str(), "should preserve dest location");
659 assert_eq!(Some(0o655), additional_asset.chmod(), "should have merged the chmod");
660 }
661
662
663 #[test]
664 fn test_merge_symlinks() {
665 let original_asset = create_test_asset(
669 "lib/test/empty.txt",
670 "/opt/test/empty.txt",
671 0o777
672 );
673
674 let merge_asset = create_test_symlink(
675 "/opt/test/empty.txt",
676 "lib/test_variant/empty.txt",
677 );
678
679 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
680 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Dest(vec![ merge_asset.into() ])) }), .. Default::default() };
681
682 let merged = variant.inherit_from(parent, &NoOpListener);
683 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
684 let merged_asset = merged.pop().expect("should have an asset");
685 assert!( merged_asset.source_path().is_none(), "should have removed the source location");
686 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
687 assert_eq!(Some(Path::new("lib/test_variant/empty.txt")), merged_asset.link_name().map(|ln|ln.as_path()), "should have merged the link_name");
688
689 let original_asset = create_test_symlink(
691 "/opt/test/empty.txt",
692 "lib/test_variant/empty.txt",
693 );
694
695 let merge_asset = create_test_symlink(
696 "/opt/test/empty.txt",
697 "lib/test_variant/non-empty.txt",
698 );
699
700 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
701 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Dest(vec![ merge_asset.into() ])) }), .. Default::default() };
702
703 let merged = variant.inherit_from(parent, &NoOpListener);
704 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
705 let merged_asset = merged.pop().expect("should have an asset");
706 assert!( merged_asset.source_path().is_none(), "should have kept no source location");
707 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
708 assert_eq!(Some(Path::new("lib/test_variant/non-empty.txt")), merged_asset.link_name().map(|ln|ln.as_path()), "should have merged the link_name");
709
710
711 let original_asset = create_test_symlink(
713 "/opt/test/empty.txt",
714 "lib/test_variant/empty.txt",
715 );
716
717 let merge_asset = create_test_asset(
718 "lib/test_variant/empty.txt",
719 "/opt/test/empty.txt",
720 0o655,
721 );
722
723 let parent = CargoDeb { assets: Some(vec![ original_asset.into() ]), .. Default::default() };
724 let variant = CargoDeb { merge_assets: Some(MergeAssets { append: None, by: Some(MergeByKey::Dest(vec![ merge_asset.into() ])) }), .. Default::default() };
725
726 let merged = variant.inherit_from(parent, &NoOpListener);
727 let mut merged = merged.assets.expect("should have assets").into_iter().filter_map(|a| a.asset()).collect_vec();
728 let merged_asset = merged.pop().expect("should have an asset");
729 assert_eq!("lib/test_variant/empty.txt", merged_asset.source_path().unwrap().as_os_str(), "should have merged the source location");
730 assert_eq!("/opt/test/empty.txt", merged_asset.target_path().as_os_str(), "should preserve dest location");
731 assert_eq!(Some(0o655), merged_asset.chmod(), "should have merged the chmod");
732 }
733}
734
735#[test]
736fn deb_ver() {
737 let mut c = cargo_toml::Package::new("test", "1.2.3-1");
738 assert_eq!("1.2.3-1-1", manifest_version_string(&c, None));
739 assert_eq!("1.2.3-1-2", manifest_version_string(&c, Some("2")));
740 assert_eq!("1.2.3-1", manifest_version_string(&c, Some("")));
741 c.version = cargo_toml::Inheritable::Set("1.2.0-beta.3".into());
742 assert_eq!("1.2.0~beta.3-1", manifest_version_string(&c, None));
743 assert_eq!("1.2.0~beta.3-4", manifest_version_string(&c, Some("4")));
744 assert_eq!("1.2.0~beta.3", manifest_version_string(&c, Some("")));
745 c.version = cargo_toml::Inheritable::Set("1.2.0-new".into());
746 assert_eq!("1.2.0-new-1", manifest_version_string(&c, None));
747 assert_eq!("1.2.0-new-11", manifest_version_string(&c, Some("11")));
748 assert_eq!("1.2.0-new", manifest_version_string(&c, Some("0")));
749}