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