rust.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. use std::{fs::File, io::Read, path::PathBuf, process::Command, str::FromStr};
  2. use serde::Deserialize;
  3. use crate::helpers::{app_paths::tauri_dir, config::Config};
  4. use tauri_bundler::{
  5. AppCategory, BundleBinary, BundleSettings, DebianSettings, MacOSSettings, PackageSettings,
  6. };
  7. /// The `workspace` section of the app configuration (read from Cargo.toml).
  8. #[derive(Clone, Debug, Deserialize)]
  9. struct WorkspaceSettings {
  10. /// the workspace members.
  11. members: Option<Vec<String>>,
  12. }
  13. #[derive(Clone, Debug, Deserialize)]
  14. struct BinarySettings {
  15. name: String,
  16. path: Option<String>,
  17. }
  18. /// The package settings.
  19. #[derive(Debug, Clone, Deserialize)]
  20. pub struct CargoPackageSettings {
  21. /// the package's name.
  22. pub name: String,
  23. /// the package's version.
  24. pub version: String,
  25. /// the package's description.
  26. pub description: String,
  27. /// the package's homepage.
  28. pub homepage: Option<String>,
  29. /// the package's authors.
  30. pub authors: Option<Vec<String>>,
  31. /// the default binary to run.
  32. pub default_run: Option<String>,
  33. }
  34. /// The Cargo settings (Cargo.toml root descriptor).
  35. #[derive(Clone, Debug, Deserialize)]
  36. struct CargoSettings {
  37. /// the package settings.
  38. ///
  39. /// it's optional because ancestor workspace Cargo.toml files may not have package info.
  40. package: Option<CargoPackageSettings>,
  41. /// the workspace settings.
  42. ///
  43. /// it's present if the read Cargo.toml belongs to a workspace root.
  44. workspace: Option<WorkspaceSettings>,
  45. /// the binary targets configuration.
  46. bin: Option<Vec<BinarySettings>>,
  47. }
  48. impl CargoSettings {
  49. /// Try to load a set of CargoSettings from a "Cargo.toml" file in the specified directory.
  50. fn load(dir: &PathBuf) -> crate::Result<Self> {
  51. let toml_path = dir.join("Cargo.toml");
  52. let mut toml_str = String::new();
  53. let mut toml_file = File::open(toml_path)?;
  54. toml_file.read_to_string(&mut toml_str)?;
  55. toml::from_str(&toml_str).map_err(Into::into)
  56. }
  57. }
  58. #[derive(Deserialize)]
  59. struct CargoBuildConfig {
  60. #[serde(rename = "target-dir")]
  61. target_dir: Option<String>,
  62. }
  63. #[derive(Deserialize)]
  64. struct CargoConfig {
  65. build: Option<CargoBuildConfig>,
  66. }
  67. pub fn build_project(debug: bool) -> crate::Result<()> {
  68. let mut args = vec!["build", "--features=custom-protocol"];
  69. if !debug {
  70. args.push("--release");
  71. }
  72. let status = Command::new("cargo").args(args).status()?;
  73. if !status.success() {
  74. return Err(anyhow::anyhow!(format!(
  75. "Result of `cargo build` operation was unsuccessful: {}",
  76. status
  77. )));
  78. }
  79. Ok(())
  80. }
  81. pub struct AppSettings {
  82. cargo_settings: CargoSettings,
  83. cargo_package_settings: CargoPackageSettings,
  84. package_settings: PackageSettings,
  85. }
  86. impl AppSettings {
  87. pub fn new(config: &Config) -> crate::Result<Self> {
  88. let cargo_settings = CargoSettings::load(&tauri_dir())?;
  89. let cargo_package_settings = match &cargo_settings.package {
  90. Some(package_info) => package_info.clone(),
  91. None => {
  92. return Err(anyhow::anyhow!(
  93. "No package info in the config file".to_owned(),
  94. ))
  95. }
  96. };
  97. let package_settings = PackageSettings {
  98. product_name: config
  99. .package
  100. .product_name
  101. .clone()
  102. .unwrap_or_else(|| cargo_package_settings.name.clone()),
  103. version: config
  104. .package
  105. .version
  106. .clone()
  107. .unwrap_or_else(|| cargo_package_settings.version.clone()),
  108. description: cargo_package_settings.description.clone(),
  109. homepage: cargo_package_settings.homepage.clone(),
  110. authors: cargo_package_settings.authors.clone(),
  111. default_run: cargo_package_settings.default_run.clone(),
  112. };
  113. Ok(Self {
  114. cargo_settings,
  115. cargo_package_settings,
  116. package_settings,
  117. })
  118. }
  119. pub fn cargo_package_settings(&self) -> &CargoPackageSettings {
  120. &self.cargo_package_settings
  121. }
  122. pub fn get_bundle_settings(&self, config: &Config) -> crate::Result<BundleSettings> {
  123. tauri_config_to_bundle_settings(config.tauri.bundle.clone())
  124. }
  125. pub fn get_out_dir(&self, debug: bool) -> crate::Result<PathBuf> {
  126. let tauri_dir = tauri_dir();
  127. let workspace_dir = get_workspace_dir(&tauri_dir);
  128. get_target_dir(&workspace_dir, None, !debug)
  129. }
  130. pub fn get_package_settings(&self) -> PackageSettings {
  131. self.package_settings.clone()
  132. }
  133. pub fn get_binaries(&self, config: &Config) -> crate::Result<Vec<BundleBinary>> {
  134. let mut binaries: Vec<BundleBinary> = vec![];
  135. if let Some(bin) = &self.cargo_settings.bin {
  136. let default_run = self
  137. .package_settings
  138. .default_run
  139. .clone()
  140. .unwrap_or_else(|| "".to_string());
  141. for binary in bin {
  142. binaries.push(
  143. if binary.name.as_str() == self.cargo_package_settings.name
  144. || binary.name.as_str() == default_run
  145. {
  146. BundleBinary::new(
  147. config
  148. .package
  149. .product_name
  150. .clone()
  151. .unwrap_or_else(|| binary.name.clone()),
  152. true,
  153. )
  154. } else {
  155. BundleBinary::new(binary.name.clone(), false)
  156. }
  157. .set_src_path(binary.path.clone()),
  158. )
  159. }
  160. }
  161. let mut bins_path = tauri_dir();
  162. bins_path.push("src/bin");
  163. if let Ok(fs_bins) = std::fs::read_dir(bins_path) {
  164. for entry in fs_bins {
  165. let path = entry?.path();
  166. if let Some(name) = path.file_stem() {
  167. let bin_exists = binaries.iter().any(|bin| {
  168. bin.name() == name || path.ends_with(bin.src_path().as_ref().unwrap_or(&"".to_string()))
  169. });
  170. if !bin_exists {
  171. binaries.push(BundleBinary::new(name.to_string_lossy().to_string(), false))
  172. }
  173. }
  174. }
  175. }
  176. if let Some(default_run) = self.package_settings.default_run.as_ref() {
  177. match binaries.iter_mut().find(|bin| bin.name() == default_run) {
  178. Some(bin) => {
  179. if let Some(product_name) = config.package.product_name.clone() {
  180. bin.set_name(product_name);
  181. }
  182. }
  183. None => {
  184. binaries.push(BundleBinary::new(
  185. config
  186. .package
  187. .product_name
  188. .clone()
  189. .unwrap_or_else(|| default_run.to_string()),
  190. true,
  191. ));
  192. }
  193. }
  194. }
  195. match binaries.len() {
  196. 0 => binaries.push(BundleBinary::new(
  197. self.package_settings.product_name.clone(),
  198. true,
  199. )),
  200. 1 => binaries.get_mut(0).unwrap().set_main(true),
  201. _ => {}
  202. }
  203. Ok(binaries)
  204. }
  205. }
  206. /// This function determines where 'target' dir is and suffixes it with 'release' or 'debug'
  207. /// to determine where the compiled binary will be located.
  208. fn get_target_dir(
  209. project_root_dir: &PathBuf,
  210. target: Option<String>,
  211. is_release: bool,
  212. ) -> crate::Result<PathBuf> {
  213. let mut path: PathBuf = match std::env::var_os("CARGO_TARGET_DIR") {
  214. Some(target_dir) => target_dir.into(),
  215. None => {
  216. let mut root_dir = project_root_dir.clone();
  217. let target_path: Option<PathBuf> = loop {
  218. // cargo reads configs under .cargo/config.toml or .cargo/config
  219. let mut cargo_config_path = root_dir.join(".cargo/config");
  220. if !cargo_config_path.exists() {
  221. cargo_config_path = root_dir.join(".cargo/config.toml");
  222. }
  223. // if the path exists, parse it
  224. if cargo_config_path.exists() {
  225. let mut config_str = String::new();
  226. let mut config_file = File::open(cargo_config_path)?;
  227. config_file.read_to_string(&mut config_str)?;
  228. let config: CargoConfig = toml::from_str(&config_str)?;
  229. if let Some(build) = config.build {
  230. if let Some(target_dir) = build.target_dir {
  231. break Some(target_dir.into());
  232. }
  233. }
  234. }
  235. if !root_dir.pop() {
  236. break None;
  237. }
  238. };
  239. target_path.unwrap_or_else(|| project_root_dir.join("target"))
  240. }
  241. };
  242. if let Some(ref triple) = target {
  243. path.push(triple);
  244. }
  245. path.push(if is_release { "release" } else { "debug" });
  246. Ok(path)
  247. }
  248. /// Walks up the file system, looking for a Cargo.toml file
  249. /// If one is found before reaching the root, then the current_dir's package belongs to that parent workspace if it's listed on [workspace.members].
  250. ///
  251. /// If this package is part of a workspace, returns the path to the workspace directory
  252. /// Otherwise returns the current directory.
  253. pub fn get_workspace_dir(current_dir: &PathBuf) -> PathBuf {
  254. let mut dir = current_dir.clone();
  255. let project_path = current_dir.clone();
  256. while dir.pop() {
  257. if let Ok(cargo_settings) = CargoSettings::load(&dir) {
  258. if let Some(workspace_settings) = cargo_settings.workspace {
  259. if let Some(members) = workspace_settings.members {
  260. if members
  261. .iter()
  262. .any(|member| dir.join(member) == project_path)
  263. {
  264. return dir;
  265. }
  266. }
  267. }
  268. }
  269. }
  270. // Nothing found walking up the file system, return the starting directory
  271. current_dir.clone()
  272. }
  273. fn tauri_config_to_bundle_settings(
  274. config: crate::helpers::config::BundleConfig,
  275. ) -> crate::Result<BundleSettings> {
  276. Ok(BundleSettings {
  277. identifier: config.identifier,
  278. icon: config.icon,
  279. resources: config.resources,
  280. copyright: config.copyright,
  281. category: match config.category {
  282. Some(category) => Some(AppCategory::from_str(&category).map_err(|e| match e {
  283. Some(e) => anyhow::anyhow!("invalid category, did you mean `{}`?", e),
  284. None => anyhow::anyhow!("invalid category"),
  285. })?),
  286. None => None,
  287. },
  288. short_description: config.short_description,
  289. long_description: config.long_description,
  290. external_bin: config.external_bin,
  291. deb: DebianSettings {
  292. depends: config.deb.depends,
  293. use_bootstrapper: Some(config.deb.use_bootstrapper),
  294. },
  295. macos: MacOSSettings {
  296. frameworks: config.macos.frameworks,
  297. minimum_system_version: config.macos.minimum_system_version,
  298. license: config.macos.license,
  299. use_bootstrapper: Some(config.macos.use_bootstrapper),
  300. exception_domain: config.macos.exception_domain,
  301. },
  302. ..Default::default()
  303. })
  304. }