use axoasset::{toml_edit, LocalAsset};
use camino::{Utf8Path, Utf8PathBuf};
use cargo_dist_schema::TripleName;
use tracing::info;
use wix::print::{wxs::WxsRenders, RenderOutput};
use crate::{backend::diff_files, config, errors::*, DistGraph};
const METADATA_WIX: &str = "wix";
const WIX_GUID_KEYS: &[&str] = &["upgrade-guid", "path-guid"];
const WIX_LICENSE_KEYS: &[&str] = &["license", "eula"];
#[derive(Debug, Clone)]
pub struct MsiInstallerInfo {
pub pkg_spec: String,
pub target: TripleName,
pub file_path: Utf8PathBuf,
pub package_dir: Utf8PathBuf,
pub wxs_path: Utf8PathBuf,
pub manifest_path: Utf8PathBuf,
}
impl MsiInstallerInfo {
pub fn build(&self, dist: &DistGraph) -> DistResult<()> {
info!("building an msi: {}", self.file_path);
let mut b = wix::create::Builder::new();
b.input(Some(self.manifest_path.as_str()));
b.package(Some(&self.pkg_spec));
b.no_build(true);
b.profile(Some("dist"));
b.target(Some(self.target.as_str()));
b.output(Some(self.file_path.as_str()));
b.target_bin_dir(Some(self.package_dir.as_str()));
b.capture_output(true);
let exec = b.build();
exec.run().map_err(|e| DistError::Wix {
msi: self.file_path.file_name().unwrap().to_owned(),
details: e,
})?;
assert!(self.file_path.exists());
dist.signer.sign(&self.file_path)?;
Ok(())
}
pub fn generate_wxs_string(&self) -> DistResult<WxsRenders> {
let mut b = wix::print::wxs::Builder::new();
b.input(Some(self.manifest_path.as_str()));
b.package(Some(&self.pkg_spec));
let output = self
.manifest_path
.parent()
.unwrap()
.join("wix")
.join("main.wxs");
b.output(Some(output.as_str()));
let exec = b.build();
let renders = exec.render().map_err(|e| DistError::WixInit {
package: self.pkg_spec.clone(),
details: e,
})?;
Ok(renders)
}
pub fn check_config(&self) -> DistResult<()> {
self.check_wix_guids()?;
self.check_wxs()?;
Ok(())
}
pub fn write_config_to_disk(&self) -> DistResult<()> {
self.write_wix_guids_to_disk()?;
self.write_wxs_to_disk()?;
Ok(())
}
fn write_wxs_to_disk(&self) -> DistResult<()> {
let file = &self.wxs_path;
let rendered = self.generate_wxs_string()?;
let WxsRenders { wxs, license, eula } = rendered;
write_render(Some(wxs))?;
write_render(license)?;
write_render(eula)?;
eprintln!("generated msi definition to {}", file);
Ok(())
}
fn check_wxs(&self) -> DistResult<()> {
let rendered = self.generate_wxs_string()?;
let WxsRenders { wxs, license, eula } = rendered;
diff_render(Some(wxs))?;
diff_render(license)?;
diff_render(eula)?;
Ok(())
}
fn check_wix_guids(&self) -> DistResult<()> {
let mut package_toml = config::load_toml(&self.manifest_path)?;
if update_wix_metadata(&mut package_toml) {
Err(DistError::MissingWixGuids {
manifest_path: self.manifest_path.clone(),
keys: WIX_GUID_KEYS,
})
} else {
Ok(())
}
}
fn write_wix_guids_to_disk(&self) -> DistResult<()> {
let mut package_toml = config::load_toml(&self.manifest_path)?;
if update_wix_metadata(&mut package_toml) {
config::write_toml(&self.manifest_path, package_toml)?;
}
Ok(())
}
}
fn write_render(render: Option<RenderOutput>) -> DistResult<()> {
let Some(render) = render else {
return Ok(());
};
let path = render.path.expect("no path!?");
let path = Utf8Path::from_path(&path).expect("non utf8 path");
LocalAsset::write_new_all(&render.rendered, path)?;
Ok(())
}
fn diff_render(render: Option<RenderOutput>) -> DistResult<()> {
let Some(render) = render else {
return Ok(());
};
let path = render.path.expect("no path!?");
let path = Utf8Path::from_path(&path).expect("non utf8 path");
diff_files(path, &render.rendered)?;
Ok(())
}
fn update_wix_metadata(package_toml: &mut toml_edit::DocumentMut) -> bool {
let metadata = config::get_toml_metadata(package_toml, false);
let wix_metadata = &mut metadata[METADATA_WIX];
if !wix_metadata.is_table() {
*wix_metadata = toml_edit::table();
}
let table = wix_metadata.as_table_mut().unwrap();
let mut modified = false;
for key in WIX_GUID_KEYS {
if !table.contains_key(key) {
modified = true;
let val = uuid::Uuid::new_v4()
.as_hyphenated()
.to_string()
.to_uppercase();
table.insert(key, toml_edit::value(val));
}
}
for key in WIX_LICENSE_KEYS {
if !table.contains_key(key) {
modified = true;
table.insert(key, toml_edit::value(false));
}
}
modified
}