rpm.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>
  2. // Copyright 2019-2024 Tauri Programme within The Commons Conservancy
  3. // SPDX-License-Identifier: Apache-2.0
  4. // SPDX-License-Identifier: MIT
  5. use crate::Settings;
  6. use anyhow::Context;
  7. use rpm::{self, signature::pgp, Dependency, FileMode, FileOptions};
  8. use std::{
  9. env,
  10. fs::{self, File},
  11. path::{Path, PathBuf},
  12. };
  13. use super::freedesktop;
  14. /// Bundles the project.
  15. /// Returns a vector of PathBuf that shows where the RPM was created.
  16. pub fn bundle_project(settings: &Settings) -> crate::Result<Vec<PathBuf>> {
  17. let product_name = settings.product_name();
  18. let version = settings.version_string();
  19. let release = settings.rpm().release.as_str();
  20. let epoch = settings.rpm().epoch;
  21. let arch = match settings.binary_arch() {
  22. "x86" => "i386",
  23. "arm" => "armhfp",
  24. other => other,
  25. };
  26. let summary = settings.short_description().trim();
  27. let package_base_name = format!("{product_name}-{version}-{release}.{arch}");
  28. let package_name = format!("{package_base_name}.rpm");
  29. let base_dir = settings.project_out_directory().join("bundle/rpm");
  30. let package_dir = base_dir.join(&package_base_name);
  31. if package_dir.exists() {
  32. fs::remove_dir_all(&package_dir)
  33. .with_context(|| format!("Failed to remove old {package_base_name}"))?;
  34. }
  35. fs::create_dir_all(&package_dir)?;
  36. let package_path = base_dir.join(&package_name);
  37. log::info!(action = "Bundling"; "{} ({})", package_name, package_path.display());
  38. let license = settings.license().unwrap_or_default();
  39. let name = heck::AsKebabCase(settings.product_name()).to_string();
  40. let mut builder = rpm::PackageBuilder::new(&name, version, &license, arch, summary)
  41. .epoch(epoch)
  42. .release(release)
  43. // This matches .deb compression. On a 240MB source binary the bundle will be 100KB larger than rpm's default while reducing build times by ~25%.
  44. .compression(rpm::CompressionWithLevel::Gzip(6));
  45. if let Some(description) = settings.long_description() {
  46. builder = builder.description(description);
  47. }
  48. if let Some(homepage) = settings.homepage_url() {
  49. builder = builder.url(homepage);
  50. }
  51. // Add requirements
  52. for dep in settings.rpm().depends.as_ref().cloned().unwrap_or_default() {
  53. builder = builder.requires(Dependency::any(dep));
  54. }
  55. // Add provides
  56. for dep in settings
  57. .rpm()
  58. .provides
  59. .as_ref()
  60. .cloned()
  61. .unwrap_or_default()
  62. {
  63. builder = builder.provides(Dependency::any(dep));
  64. }
  65. // Add conflicts
  66. for dep in settings
  67. .rpm()
  68. .conflicts
  69. .as_ref()
  70. .cloned()
  71. .unwrap_or_default()
  72. {
  73. builder = builder.conflicts(Dependency::any(dep));
  74. }
  75. // Add obsoletes
  76. for dep in settings
  77. .rpm()
  78. .obsoletes
  79. .as_ref()
  80. .cloned()
  81. .unwrap_or_default()
  82. {
  83. builder = builder.obsoletes(Dependency::any(dep));
  84. }
  85. // Add binaries
  86. for bin in settings.binaries() {
  87. let src = settings.binary_path(bin);
  88. let dest = Path::new("/usr/bin").join(bin.name());
  89. builder = builder.with_file(src, FileOptions::new(dest.to_string_lossy()))?;
  90. }
  91. // Add external binaries
  92. for src in settings.external_binaries() {
  93. let src = src?;
  94. let dest = Path::new("/usr/bin").join(
  95. src
  96. .file_name()
  97. .expect("failed to extract external binary filename")
  98. .to_string_lossy()
  99. .replace(&format!("-{}", settings.target()), ""),
  100. );
  101. builder = builder.with_file(&src, FileOptions::new(dest.to_string_lossy()))?;
  102. }
  103. // Add scripts
  104. if let Some(script_path) = &settings.rpm().pre_install_script {
  105. let script = fs::read_to_string(script_path)?;
  106. builder = builder.pre_install_script(script);
  107. }
  108. if let Some(script_path) = &settings.rpm().post_install_script {
  109. let script = fs::read_to_string(script_path)?;
  110. builder = builder.post_install_script(script);
  111. }
  112. if let Some(script_path) = &settings.rpm().pre_remove_script {
  113. let script = fs::read_to_string(script_path)?;
  114. builder = builder.pre_uninstall_script(script);
  115. }
  116. if let Some(script_path) = &settings.rpm().post_remove_script {
  117. let script = fs::read_to_string(script_path)?;
  118. builder = builder.post_uninstall_script(script);
  119. }
  120. // Add resources
  121. if settings.resource_files().count() > 0 {
  122. let resource_dir = Path::new("/usr/lib").join(settings.main_binary_name());
  123. // Create an empty file, needed to add a directory to the RPM package
  124. // (cf https://github.com/rpm-rs/rpm/issues/177)
  125. let empty_file_path = &package_dir.join("empty");
  126. File::create(empty_file_path)?;
  127. // Then add the resource directory `/usr/lib/<binary_name>` to the package.
  128. builder = builder.with_file(
  129. empty_file_path,
  130. FileOptions::new(resource_dir.to_string_lossy()).mode(FileMode::Dir { permissions: 0o755 }),
  131. )?;
  132. // Then add the resources files in that directory
  133. for src in settings.resource_files() {
  134. let src = src?;
  135. let dest = resource_dir.join(tauri_utils::resources::resource_relpath(&src));
  136. builder = builder.with_file(&src, FileOptions::new(dest.to_string_lossy()))?;
  137. }
  138. }
  139. // Add Desktop entry file
  140. let (desktop_src_path, desktop_dest_path) =
  141. freedesktop::generate_desktop_file(settings, &settings.rpm().desktop_template, &package_dir)?;
  142. builder = builder.with_file(
  143. desktop_src_path,
  144. FileOptions::new(desktop_dest_path.to_string_lossy()),
  145. )?;
  146. // Add icons
  147. for (icon, src) in &freedesktop::list_icon_files(settings, &PathBuf::from("/"))? {
  148. builder = builder.with_file(src, FileOptions::new(icon.path.to_string_lossy()))?;
  149. }
  150. // Add custom files
  151. for (rpm_path, src_path) in settings.rpm().files.iter() {
  152. if src_path.is_file() {
  153. builder = builder.with_file(src_path, FileOptions::new(rpm_path.to_string_lossy()))?;
  154. } else {
  155. for entry in walkdir::WalkDir::new(src_path) {
  156. let entry_path = entry?.into_path();
  157. if entry_path.is_file() {
  158. let dest_path = rpm_path.join(entry_path.strip_prefix(src_path).unwrap());
  159. builder =
  160. builder.with_file(&entry_path, FileOptions::new(dest_path.to_string_lossy()))?;
  161. }
  162. }
  163. }
  164. }
  165. let pkg = if let Ok(raw_secret_key) = env::var("TAURI_SIGNING_RPM_KEY") {
  166. let mut signer = pgp::Signer::load_from_asc(&raw_secret_key)?;
  167. if let Ok(passphrase) = env::var("TAURI_SIGNING_RPM_KEY_PASSPHRASE") {
  168. signer = signer.with_key_passphrase(passphrase);
  169. }
  170. builder.build_and_sign(signer)?
  171. } else {
  172. builder.build()?
  173. };
  174. let mut f = fs::File::create(&package_path)?;
  175. pkg.write(&mut f)?;
  176. Ok(vec![package_path])
  177. }