wix.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. use super::common;
  2. use super::settings::Settings;
  3. use handlebars::Handlebars;
  4. use lazy_static::lazy_static;
  5. use sha2::Digest;
  6. use std::collections::BTreeMap;
  7. use std::fs::{create_dir_all, remove_dir_all, write, File};
  8. use std::io::{BufRead, BufReader, Cursor, Read, Write};
  9. use std::path::{Path, PathBuf};
  10. use std::process::{Command, Stdio};
  11. use uuid::Uuid;
  12. use zip::ZipArchive;
  13. // URLS for the WIX toolchain. Can be used for crossplatform compilation.
  14. pub const WIX_URL: &str =
  15. "https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip";
  16. pub const WIX_SHA256: &str = "37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d";
  17. // For Cross Platform Complilation.
  18. // const VC_REDIST_X86_URL: &str =
  19. // "https://download.visualstudio.microsoft.com/download/pr/c8edbb87-c7ec-4500-a461-71e8912d25e9/99ba493d660597490cbb8b3211d2cae4/vc_redist.x86.exe";
  20. // const VC_REDIST_X86_SHA256: &str =
  21. // "3a43e8a55a3f3e4b73d01872c16d47a19dd825756784f4580187309e7d1fcb74";
  22. // const VC_REDIST_X64_URL: &str =
  23. // "https://download.visualstudio.microsoft.com/download/pr/9e04d214-5a9d-4515-9960-3d71398d98c3/1e1e62ab57bbb4bf5199e8ce88f040be/vc_redist.x64.exe";
  24. // const VC_REDIST_X64_SHA256: &str =
  25. // "d6cd2445f68815fe02489fafe0127819e44851e26dfbe702612bc0d223cbbc2b";
  26. // A v4 UUID that was generated specifically for cargo-bundle, to be used as a
  27. // namespace for generating v5 UUIDs from bundle identifier strings.
  28. const UUID_NAMESPACE: [u8; 16] = [
  29. 0xfd, 0x85, 0x95, 0xa8, 0x17, 0xa3, 0x47, 0x4e, 0xa6, 0x16, 0x76, 0x14, 0x8d, 0xfa, 0x0c, 0x7b,
  30. ];
  31. // setup for the main.wxs template file using handlebars. Dynamically changes the template on compilation based on the application metadata.
  32. lazy_static! {
  33. static ref HANDLEBARS: Handlebars = {
  34. let mut handlebars = Handlebars::new();
  35. handlebars
  36. .register_template_string("main.wxs", include_str!("templates/main.wxs"))
  37. .unwrap();
  38. handlebars
  39. };
  40. }
  41. // Function used to download Wix and VC_REDIST. Checks SHA256 to verify the download.
  42. fn download_and_verify(url: &str, hash: &str) -> crate::Result<Vec<u8>> {
  43. common::print_info(format!("Downloading {}", url).as_str())?;
  44. let mut response = reqwest::get(url).or_else(|e| Err(e.to_string()))?;
  45. let mut data: Vec<u8> = Vec::new();
  46. response
  47. .read_to_end(&mut data)
  48. .or_else(|e| Err(e.to_string()))?;
  49. common::print_info("validating hash")?;
  50. let mut hasher = sha2::Sha256::new();
  51. hasher.input(&data);
  52. let url_hash = hasher.result().to_vec();
  53. let expected_hash = hex::decode(hash).or_else(|e| Err(e.to_string()))?;
  54. if expected_hash == url_hash {
  55. Ok(data)
  56. } else {
  57. Err(crate::Error::from("hash mismatch of downloaded file"))
  58. }
  59. }
  60. fn app_installer_dir(settings: &Settings) -> crate::Result<PathBuf> {
  61. let arch = match settings.binary_arch() {
  62. "x86_64" => "x86",
  63. "x64" => "x64",
  64. target => {
  65. return Err(crate::Error::from(format!(
  66. "Unsupported architecture: {}",
  67. target
  68. )))
  69. }
  70. };
  71. Ok(settings.project_out_directory().to_path_buf().join(format!(
  72. "{}.{}.msi",
  73. settings.bundle_name(),
  74. arch
  75. )))
  76. }
  77. // Extracts the zips from Wix and VC_REDIST into a useable path.
  78. fn extract_zip(data: &Vec<u8>, path: &Path) -> crate::Result<()> {
  79. let cursor = Cursor::new(data);
  80. let mut zipa = ZipArchive::new(cursor).or_else(|e| Err(e.to_string()))?;
  81. for i in 0..zipa.len() {
  82. let mut file = zipa.by_index(i).or_else(|e| Err(e.to_string()))?;
  83. let dest_path = path.join(file.name());
  84. let parent = dest_path.parent().unwrap();
  85. if !parent.exists() {
  86. create_dir_all(parent).or_else(|e| Err(e.to_string()))?;
  87. }
  88. let mut buff: Vec<u8> = Vec::new();
  89. file
  90. .read_to_end(&mut buff)
  91. .or_else(|e| Err(e.to_string()))?;
  92. let mut fileout = File::create(dest_path).unwrap();
  93. fileout.write_all(&buff).or_else(|e| Err(e.to_string()))?;
  94. }
  95. Ok(())
  96. }
  97. // Generates the UUID for the Wix template.
  98. fn generate_package_guid(settings: &Settings) -> Uuid {
  99. let namespace = Uuid::from_bytes(&UUID_NAMESPACE).unwrap();
  100. Uuid::new_v5(&namespace, &settings.bundle_identifier())
  101. }
  102. // Specifically goes and gets Wix and verifies the download via Sha256
  103. pub fn get_and_extract_wix(path: &Path) -> crate::Result<()> {
  104. common::print_info("Verifying wix package")?;
  105. let data = download_and_verify(WIX_URL, WIX_SHA256)?;
  106. common::print_info("extracting WIX")?;
  107. extract_zip(&data, path)
  108. }
  109. // For if bundler needs DLL files.
  110. // fn run_heat_exe(
  111. // wix_toolset_path: &Path,
  112. // build_path: &Path,
  113. // harvest_dir: &Path,
  114. // platform: &str,
  115. // ) -> Result<(), String> {
  116. // let mut args = vec!["dir"];
  117. // let harvest_str = harvest_dir.display().to_string();
  118. // args.push(&harvest_str);
  119. // args.push("-platform");
  120. // args.push(platform);
  121. // args.push("-cg");
  122. // args.push("AppFiles");
  123. // args.push("-dr");
  124. // args.push("APPLICATIONFOLDER");
  125. // args.push("-gg");
  126. // args.push("-srd");
  127. // args.push("-out");
  128. // args.push("appdir.wxs");
  129. // args.push("-var");
  130. // args.push("var.SourceDir");
  131. // let heat_exe = wix_toolset_path.join("heat.exe");
  132. // let mut cmd = Command::new(&heat_exe)
  133. // .args(&args)
  134. // .stdout(Stdio::piped())
  135. // .current_dir(build_path)
  136. // .spawn()
  137. // .expect("error running heat.exe");
  138. // {
  139. // let stdout = cmd.stdout.as_mut().unwrap();
  140. // let reader = BufReader::new(stdout);
  141. // for line in reader.lines() {
  142. // info!(logger, "{}", line.unwrap());
  143. // }
  144. // }
  145. // let status = cmd.wait().unwrap();
  146. // if status.success() {
  147. // Ok(())
  148. // } else {
  149. // Err("error running heat.exe".to_string())
  150. // }
  151. // }
  152. // Runs the Candle.exe executable for Wix. Candle parses the wxs file and generates the code for building the installer.
  153. fn run_candle(
  154. settings: &Settings,
  155. wix_toolset_path: &Path,
  156. build_path: &Path,
  157. wxs_file_name: &str,
  158. ) -> crate::Result<()> {
  159. let arch = match settings.binary_arch() {
  160. "x86_64" => "x64",
  161. "x86" => "x86",
  162. target => {
  163. return Err(crate::Error::from(format!(
  164. "unsupported target: {}",
  165. target
  166. )))
  167. }
  168. };
  169. let args = vec![
  170. "-arch".to_string(),
  171. arch.to_string(),
  172. wxs_file_name.to_string(),
  173. format!("-dSourceDir={}", settings.binary_path().display()),
  174. ];
  175. let candle_exe = wix_toolset_path.join("candle.exe");
  176. common::print_info(format!("running candle for {}", wxs_file_name).as_str())?;
  177. let mut cmd = Command::new(&candle_exe)
  178. .args(&args)
  179. .stdout(Stdio::piped())
  180. .current_dir(build_path)
  181. .spawn()
  182. .expect("error running candle.exe");
  183. {
  184. let stdout = cmd.stdout.as_mut().unwrap();
  185. let reader = BufReader::new(stdout);
  186. for line in reader.lines() {
  187. common::print_info(line.unwrap().as_str())?;
  188. }
  189. }
  190. let status = cmd.wait().unwrap();
  191. if status.success() {
  192. Ok(())
  193. } else {
  194. Err(crate::Error::from("error running candle.exe"))
  195. }
  196. }
  197. // Runs the Light.exe file. Light takes the generated code from Candle and produces an MSI Installer.
  198. fn run_light(
  199. wix_toolset_path: &Path,
  200. build_path: &Path,
  201. wixobjs: &[&str],
  202. output_path: &Path,
  203. ) -> crate::Result<PathBuf> {
  204. let light_exe = wix_toolset_path.join("light.exe");
  205. let mut args: Vec<String> = vec!["-o".to_string(), output_path.display().to_string()];
  206. for p in wixobjs {
  207. args.push(p.to_string());
  208. }
  209. common::print_info(format!("running light to produce {}", output_path.display()).as_str())?;
  210. let mut cmd = Command::new(&light_exe)
  211. .args(&args)
  212. .stdout(Stdio::piped())
  213. .current_dir(build_path)
  214. .spawn()
  215. .expect("error running light.exe");
  216. {
  217. let stdout = cmd.stdout.as_mut().unwrap();
  218. let reader = BufReader::new(stdout);
  219. for line in reader.lines() {
  220. common::print_info(line.unwrap().as_str())?;
  221. }
  222. }
  223. let status = cmd.wait().unwrap();
  224. if status.success() {
  225. Ok(output_path.to_path_buf())
  226. } else {
  227. Err(crate::Error::from("error running light.exe"))
  228. }
  229. }
  230. // fn get_icon_data() -> crate::Result<()> {
  231. // Ok(())
  232. // }
  233. // Entry point for bundling and creating the MSI installer. For now the only supported platform is Windows x64.
  234. pub fn build_wix_app_installer(
  235. settings: &Settings,
  236. wix_toolset_path: &Path,
  237. ) -> crate::Result<PathBuf> {
  238. let arch = match settings.binary_arch() {
  239. "x86_64" => "x64",
  240. "x86" => "x86",
  241. target => {
  242. return Err(crate::Error::from(format!(
  243. "unsupported target: {}",
  244. target
  245. )))
  246. }
  247. };
  248. // common::print_warning("Only x64 supported")?;
  249. // target only supports x64.
  250. common::print_info(format!("Target: {}", arch).as_str())?;
  251. let output_path = settings.project_out_directory().join("wix").join(arch);
  252. let mut data = BTreeMap::new();
  253. data.insert("product_name", settings.bundle_name());
  254. data.insert("version", settings.version_string());
  255. let manufacturer = settings.bundle_identifier().to_string();
  256. data.insert("manufacturer", manufacturer.as_str());
  257. let upgrade_code = Uuid::new_v5(
  258. &uuid::NAMESPACE_DNS,
  259. format!("{}.app.x64", &settings.binary_name()).as_str(),
  260. )
  261. .to_string();
  262. data.insert("upgrade_code", &upgrade_code.as_str());
  263. let path_guid = generate_package_guid(settings).to_string();
  264. data.insert("path_component_guid", &path_guid.as_str());
  265. let app_exe_name = settings.binary_name().to_string();
  266. data.insert("app_exe_name", &app_exe_name);
  267. let app_exe_source = settings.binary_path().display().to_string();
  268. data.insert("app_exe_source", &app_exe_source);
  269. let image_path = PathBuf::from("../../../../icons/icon.ico")
  270. .display()
  271. .to_string();
  272. data.insert("icon_path", &image_path);
  273. let temp = HANDLEBARS
  274. .render("main.wxs", &data)
  275. .or_else(|e| Err(e.to_string()))?;
  276. if output_path.exists() {
  277. remove_dir_all(&output_path).or_else(|e| Err(e.to_string()))?;
  278. }
  279. create_dir_all(&output_path).or_else(|e| Err(e.to_string()))?;
  280. let main_wxs_path = output_path.join("main.wxs");
  281. write(&main_wxs_path, temp).or_else(|e| Err(e.to_string()))?;
  282. let input_basenames = vec!["main"];
  283. for basename in &input_basenames {
  284. let wxs = format!("{}.wxs", basename);
  285. run_candle(settings, &wix_toolset_path, &output_path, &wxs)?;
  286. }
  287. let wixobjs = vec!["main.wixobj"];
  288. let target = run_light(
  289. &wix_toolset_path,
  290. &output_path,
  291. &wixobjs,
  292. &app_installer_dir(settings)?,
  293. )?;
  294. Ok(target)
  295. }