use crate::Cultures;
use crate::Error;
use crate::Result;
use crate::WixArch;
use crate::BINARY_FOLDER_NAME;
use crate::CARGO;
use crate::EXE_FILE_EXTENSION;
use crate::MSIEXEC;
use crate::MSI_FILE_EXTENSION;
use crate::WIX;
use crate::WIX_COMPILER;
use crate::WIX_LINKER;
use crate::WIX_OBJECT_FILE_EXTENSION;
use crate::WIX_PATH_KEY;
use crate::WIX_SOURCE_FILE_EXTENSION;
use log::{debug, info, trace, warn};
use semver::Version;
use std::convert::TryFrom;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::FromStr;
use cargo_metadata::Package;
use rustc_cfg::Cfg;
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct Builder<'a> {
bin_path: Option<&'a str>,
capture_output: bool,
compiler_args: Option<Vec<&'a str>>,
culture: Option<&'a str>,
debug_build: bool,
debug_name: bool,
includes: Option<Vec<&'a str>>,
input: Option<&'a str>,
linker_args: Option<Vec<&'a str>>,
locale: Option<&'a str>,
name: Option<&'a str>,
no_build: bool,
install: bool,
output: Option<&'a str>,
package: Option<&'a str>,
target: Option<&'a str>,
version: Option<&'a str>,
}
impl<'a> Builder<'a> {
pub fn new() -> Self {
Builder {
bin_path: None,
capture_output: true,
compiler_args: None,
culture: None,
debug_build: false,
debug_name: false,
includes: None,
input: None,
linker_args: None,
locale: None,
name: None,
no_build: false,
install: false,
output: None,
package: None,
target: None,
version: None,
}
}
pub fn bin_path(&mut self, b: Option<&'a str>) -> &mut Self {
self.bin_path = b;
self
}
pub fn capture_output(&mut self, c: bool) -> &mut Self {
self.capture_output = c;
self
}
pub fn compiler_args(&mut self, c: Option<Vec<&'a str>>) -> &mut Self {
self.compiler_args = c;
self
}
pub fn culture(&mut self, c: Option<&'a str>) -> &mut Self {
self.culture = c;
self
}
pub fn debug_build(&mut self, d: bool) -> &mut Self {
self.debug_build = d;
self
}
pub fn debug_name(&mut self, d: bool) -> &mut Self {
self.debug_name = d;
self
}
pub fn includes(&mut self, i: Option<Vec<&'a str>>) -> &mut Self {
self.includes = i;
self
}
pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
self.input = i;
self
}
pub fn linker_args(&mut self, l: Option<Vec<&'a str>>) -> &mut Self {
self.linker_args = l;
self
}
pub fn locale(&mut self, l: Option<&'a str>) -> &mut Self {
self.locale = l;
self
}
pub fn name(&mut self, p: Option<&'a str>) -> &mut Self {
self.name = p;
self
}
pub fn no_build(&mut self, n: bool) -> &mut Self {
self.no_build = n;
self
}
pub fn install(&mut self, n: bool) -> &mut Self {
self.install = n;
self
}
pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
self.output = o;
self
}
pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
self.package = p;
self
}
pub fn target(&mut self, v: Option<&'a str>) -> &mut Self {
self.target = v;
self
}
pub fn version(&mut self, v: Option<&'a str>) -> &mut Self {
self.version = v;
self
}
pub fn build(&mut self) -> Execution {
Execution {
bin_path: self.bin_path.map(PathBuf::from),
capture_output: self.capture_output,
compiler_args: self
.compiler_args
.as_ref()
.map(|c| c.iter().map(|s| (*s).to_string()).collect()),
culture: self.culture.map(String::from),
debug_build: self.debug_build,
debug_name: self.debug_name,
includes: self
.includes
.as_ref()
.map(|v| v.iter().map(&PathBuf::from).collect()),
input: self.input.map(PathBuf::from),
linker_args: self
.linker_args
.as_ref()
.map(|l| l.iter().map(|s| (*s).to_string()).collect()),
locale: self.locale.map(PathBuf::from),
name: self.name.map(String::from),
no_build: self.no_build,
install: self.install,
output: self.output.map(String::from),
package: self.package.map(String::from),
version: self.version.map(String::from),
target: self.target.map(String::from),
}
}
}
impl<'a> Default for Builder<'a> {
fn default() -> Self {
Builder::new()
}
}
#[derive(Debug)]
pub struct Execution {
bin_path: Option<PathBuf>,
capture_output: bool,
compiler_args: Option<Vec<String>>,
culture: Option<String>,
debug_build: bool,
debug_name: bool,
includes: Option<Vec<PathBuf>>,
input: Option<PathBuf>,
linker_args: Option<Vec<String>>,
locale: Option<PathBuf>,
name: Option<String>,
no_build: bool,
install: bool,
output: Option<String>,
package: Option<String>,
target: Option<String>,
version: Option<String>,
}
impl Execution {
#[allow(clippy::cognitive_complexity)]
pub fn run(self) -> Result<()> {
debug!("self.bin_path = {:?}", self.bin_path);
debug!("self.capture_output = {:?}", self.capture_output);
debug!("self.compiler_args = {:?}", self.compiler_args);
debug!("self.culture = {:?}", self.culture);
debug!("self.debug_build = {:?}", self.debug_build);
debug!("self.debug_name = {:?}", self.debug_name);
debug!("self.includes = {:?}", self.includes);
debug!("self.input = {:?}", self.input);
debug!("self.linker_args = {:?}", self.linker_args);
debug!("self.locale = {:?}", self.locale);
debug!("self.name = {:?}", self.name);
debug!("self.no_build = {:?}", self.no_build);
debug!("self.install = {:?}", self.install);
debug!("self.output = {:?}", self.output);
debug!("self.package = {:?}", self.package);
debug!("self.target = {:?}", self.target);
debug!("self.version = {:?}", self.version);
let manifest_path = super::cargo_toml_file(self.input.as_ref())?;
debug!("manifest_path = {:?}", manifest_path);
let manifest = super::manifest(self.input.as_ref())?;
debug!("target_directory = {:?}", manifest.target_directory);
let package = super::package(&manifest, self.package.as_deref())?;
debug!("package = {:?}", package);
let metadata = package.metadata.clone();
debug!("metadata = {:?}", metadata);
let name = self.name(&package);
debug!("name = {:?}", name);
let target_triple = self.target()?;
debug!("target_triple = {:?}", target_triple);
let version = self.version(&package)?;
debug!("version = {:?}", version);
let compiler_args = self.compiler_args(&metadata);
debug!("compiler_args = {:?}", compiler_args);
let culture = self.culture(&metadata)?;
debug!("culture = {:?}", culture);
let linker_args = self.linker_args(&metadata);
debug!("linker_args = {:?}", linker_args);
let locale = self.locale(&metadata)?;
debug!("locale = {:?}", locale);
let debug_build = self.debug_build(&metadata);
debug!("debug_build = {:?}", debug_build);
let profile = if debug_build { "debug" } else { "release" };
debug!("profile = {:?}", profile);
let debug_name = self.debug_name(&metadata);
debug!("debug_name = {:?}", debug_name);
let wxs_sources = self.wxs_sources(&package)?;
debug!("wxs_sources = {:?}", wxs_sources);
let wixobj_destination = self.wixobj_destination(&manifest.target_directory);
debug!("wixobj_destination = {:?}", wixobj_destination);
let no_build = self.no_build(&metadata);
debug!("no_build = {:?}", no_build);
let cfg = Cfg::of(&target_triple).map_err(|e| Error::Generic(e.to_string()))?;
let wix_arch = WixArch::try_from(&cfg)?;
debug!("wix_arch = {:?}", wix_arch);
if no_build {
warn!("Skipped building the binary");
} else {
info!("Building the binary");
let mut builder = Command::new(
env::var("CARGO")
.map(PathBuf::from)
.ok()
.unwrap_or_else(|| PathBuf::from(CARGO)),
);
debug!("builder = {:?}", builder);
if self.capture_output {
trace!("Capturing the '{}' output", CARGO);
builder.stdout(Stdio::null());
builder.stderr(Stdio::null());
}
builder.arg("build");
if !debug_build {
builder.arg("--release");
}
if self.target.is_some() {
builder.arg(format!("--target={}", target_triple));
}
if let Some(ref package) = self.package {
builder.arg(format!("--package={}", package));
}
builder.arg("--manifest-path").arg(&manifest_path);
debug!("command = {:?}", builder);
let status = builder.status()?;
if !status.success() {
return Err(Error::Command(
CARGO,
status.code().unwrap_or(100),
self.capture_output,
));
}
}
info!("Compiling the installer");
let mut compiler = self.compiler()?;
debug!("compiler = {:?}", compiler);
if self.capture_output {
trace!("Capturing the '{}' output", WIX_COMPILER);
compiler.stdout(Stdio::null());
compiler.stderr(Stdio::null());
}
compiler
.arg("-arch")
.arg(&wix_arch.to_string())
.arg("-ext")
.arg("WixUtilExtension");
if let Some(vendor) = &cfg.target_vendor {
compiler.arg(format!("-dTargetVendor={}", vendor));
}
compiler
.arg(format!("-dVersion={}", version))
.arg(format!("-dPlatform={}", wix_arch))
.arg(format!("-dProfile={}", profile))
.arg(format!("-dTargetEnv={}", cfg.target_env))
.arg(format!("-dTargetTriple={}", target_triple))
.arg(format!("-dCargoProfile={}", profile))
.arg({
let mut s = OsString::from("-dCargoTargetDir=");
s.push(&manifest.target_directory);
s
})
.arg({
let mut s = OsString::from("-dCargoTargetBinDir=");
let mut bin_path = manifest.target_directory.clone();
if let Some(target) = &self.target {
bin_path.push(target);
}
bin_path.push(profile);
s.push(&bin_path);
s
})
.arg("-o")
.arg(&wixobj_destination);
if let Some(args) = &compiler_args {
trace!("Appending compiler arguments");
compiler.args(args);
}
compiler.args(&wxs_sources);
debug!("command = {:?}", compiler);
let status = compiler.status().map_err(|err| {
if err.kind() == ErrorKind::NotFound {
Error::Generic(format!(
"The compiler application ({}) could not be found in the PATH environment \
variable. Please check the WiX Toolset (http://wixtoolset.org/) is \
installed and check the WiX Toolset's '{}' folder has been added to the PATH \
system environment variable, the {} system environment variable exists, or use \
the '-b,--bin-path' command line argument.",
WIX_COMPILER, BINARY_FOLDER_NAME, WIX_PATH_KEY
))
} else {
err.into()
}
})?;
if !status.success() {
return Err(Error::Command(
WIX_COMPILER,
status.code().unwrap_or(100),
self.capture_output,
));
}
let wixobj_sources = self.wixobj_sources(&wixobj_destination)?;
debug!("wixobj_sources = {:?}", wixobj_sources);
let installer_kind = InstallerKind::try_from(
wixobj_sources
.iter()
.map(WixObjKind::try_from)
.collect::<Result<Vec<WixObjKind>>>()?,
)?;
debug!("installer_kind = {:?}", installer_kind);
let installer_destination = self.installer_destination(
&name,
&version,
&cfg,
debug_name,
&installer_kind,
&package,
&manifest.target_directory,
);
debug!("installer_destination = {:?}", installer_destination);
info!("Linking the installer");
let mut linker = self.linker()?;
debug!("linker = {:?}", linker);
let base_path = manifest_path.parent().ok_or_else(|| {
Error::Generic(String::from("The base path for the linker is invalid"))
})?;
debug!("base_path = {:?}", base_path);
if self.capture_output {
trace!("Capturing the '{}' output", WIX_LINKER);
linker.stdout(Stdio::null());
linker.stderr(Stdio::null());
}
linker
.arg("-spdb")
.arg("-ext")
.arg("WixUIExtension")
.arg("-ext")
.arg("WixUtilExtension")
.arg(format!("-cultures:{}", culture))
.arg("-out")
.arg(&installer_destination)
.arg("-b")
.arg(base_path);
if let Some(l) = locale {
trace!("Using the a WiX localization file");
linker.arg("-loc").arg(l);
}
if let InstallerKind::Exe = installer_kind {
trace!("Adding the WixBalExtension for the bundle-based installer");
linker.arg("-ext").arg("WixBalExtension");
}
if let Some(args) = &linker_args {
trace!("Appending linker arguments");
linker.args(args);
}
linker.args(&wixobj_sources);
debug!("command = {:?}", linker);
let status = linker.status().map_err(|err| {
if err.kind() == ErrorKind::NotFound {
Error::Generic(format!(
"The linker application ({}) could not be found in the PATH environment \
variable. Please check the WiX Toolset (http://wixtoolset.org/) is \
installed and check the WiX Toolset's '{}' folder has been added to the PATH \
environment variable, the {} system environment variable exists, or use the \
'-b,--bin-path' command line argument.",
WIX_LINKER, BINARY_FOLDER_NAME, WIX_PATH_KEY
))
} else {
err.into()
}
})?;
if !status.success() {
return Err(Error::Command(
WIX_LINKER,
status.code().unwrap_or(100),
self.capture_output,
));
}
if self.install {
info!("Launching the installer");
let mut installer = Command::new(MSIEXEC);
installer.arg("/i").arg(&installer_destination);
let status = installer.status()?;
if !status.success() {
return Err(Error::Command(
MSIEXEC,
status.code().unwrap_or(100),
self.capture_output,
));
}
}
Ok(())
}
fn compiler(&self) -> Result<Command> {
if let Some(mut path) = self.bin_path.as_ref().map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset's '{}' folder for the compiler",
p.display(),
BINARY_FOLDER_NAME
);
p.push(WIX_COMPILER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); Err(Error::Generic(format!(
"The compiler application ('{}') does not exist at the '{}' path specified via \
the '-b,--bin-path' command line argument. Please check the path is correct and \
the compiler application exists at the path.",
WIX_COMPILER,
path.display()
)))
} else {
Ok(Command::new(path))
}
} else if let Some(mut path) = env::var_os(WIX_PATH_KEY).map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset's '{}' folder for the compiler",
p.display(),
BINARY_FOLDER_NAME
);
p.push(BINARY_FOLDER_NAME);
p.push(WIX_COMPILER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); Err(Error::Generic(format!(
"The compiler application ('{}') does not exist at the '{}' path specified \
via the {} environment variable. Please check the path is correct and the \
compiler application exists at the path.",
WIX_COMPILER,
path.display(),
WIX_PATH_KEY
)))
} else {
Ok(Command::new(path))
}
} else {
Ok(Command::new(WIX_COMPILER))
}
}
fn compiler_args(&self, metadata: &Value) -> Option<Vec<String>> {
self.compiler_args.to_owned().or_else(|| {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("compiler-args"))
.and_then(|i| i.as_array())
.map(|a| {
a.iter()
.map(|s| s.as_str().map(String::from).unwrap())
.collect::<Vec<String>>()
})
})
}
fn culture(&self, metadata: &Value) -> Result<Cultures> {
if let Some(culture) = &self.culture {
Cultures::from_str(culture)
} else if let Some(pkg_meta_wix_culture) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("culture"))
.and_then(|c| c.as_str())
{
Cultures::from_str(pkg_meta_wix_culture)
} else {
Ok(Cultures::EnUs)
}
}
fn debug_build(&self, metadata: &Value) -> bool {
if self.debug_build {
true
} else if let Some(pkg_meta_wix_debug_build) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("dbg-build"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_debug_build
} else {
false
}
}
fn debug_name(&self, metadata: &Value) -> bool {
if self.debug_name {
true
} else if let Some(pkg_meta_wix_debug_name) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("dbg-name"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_debug_name
} else {
false
}
}
#[allow(clippy::too_many_arguments)]
fn installer_destination(
&self,
name: &str,
version: &Version,
cfg: &Cfg,
debug_name: bool,
installer_kind: &InstallerKind,
package: &Package,
target_directory: &Path,
) -> PathBuf {
let filename = if debug_name {
format!(
"{}-{}-{}-debug.{}",
name, version, cfg.target_arch, installer_kind
)
} else {
format!(
"{}-{}-{}.{}",
name, version, cfg.target_arch, installer_kind
)
};
if let Some(ref path_str) = self.output {
trace!("Using the explicitly specified output path for the MSI destination");
let path = Path::new(path_str);
if path_str.ends_with('/') || path_str.ends_with('\\') || path.is_dir() {
path.join(filename)
} else {
path.to_owned()
}
} else if let Some(pkg_meta_wix_output) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("output"))
.and_then(|o| o.as_str())
{
trace!("Using the output path in the package's metadata for the MSI destination");
let path = Path::new(pkg_meta_wix_output);
if pkg_meta_wix_output.ends_with('/')
|| pkg_meta_wix_output.ends_with('\\')
|| path.is_dir()
{
path.join(filename)
} else {
path.to_owned()
}
} else {
trace!("Using the package's manifest (Cargo.toml) file path to specify the MSI destination");
target_directory.join(WIX).join(filename)
}
}
fn linker(&self) -> Result<Command> {
if let Some(mut path) = self.bin_path.as_ref().map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset '{}' folder for the linker",
p.display(),
BINARY_FOLDER_NAME
);
p.push(WIX_LINKER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); Err(Error::Generic(format!(
"The linker application ('{}') does not exist at the '{}' path specified via \
the '-b,--bin-path' command line argument. Please check the path is correct \
and the linker application exists at the path.",
WIX_LINKER,
path.display()
)))
} else {
Ok(Command::new(path))
}
} else if let Some(mut path) = env::var_os(WIX_PATH_KEY).map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset's '{}' folder for the linker",
p.display(),
BINARY_FOLDER_NAME
);
p.push(BINARY_FOLDER_NAME);
p.push(WIX_LINKER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); Err(Error::Generic(format!(
"The linker application ('{}') does not exist at the '{}' path specified \
via the {} environment variable. Please check the path is correct and the \
linker application exists at the path.",
WIX_LINKER,
path.display(),
WIX_PATH_KEY
)))
} else {
Ok(Command::new(path))
}
} else {
Ok(Command::new(WIX_LINKER))
}
}
fn linker_args(&self, metadata: &Value) -> Option<Vec<String>> {
self.linker_args.to_owned().or_else(|| {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("linker-args"))
.and_then(|i| i.as_array())
.map(|a| {
a.iter()
.map(|s| s.as_str().map(String::from).unwrap())
.collect::<Vec<String>>()
})
})
}
fn locale(&self, metadata: &Value) -> Result<Option<PathBuf>> {
if let Some(locale) = self.locale.as_ref().map(PathBuf::from) {
if locale.exists() {
Ok(Some(locale))
} else {
Err(Error::Generic(format!(
"The '{}' WiX localization file could not be found, or it does not exist. \
Please check the path is correct and the file exists.",
locale.display()
)))
}
} else if let Some(pkg_meta_wix_locale) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("locale"))
.and_then(|l| l.as_str())
.map(PathBuf::from)
{
Ok(Some(pkg_meta_wix_locale))
} else {
Ok(None)
}
}
fn name(&self, package: &Package) -> String {
if let Some(ref p) = self.name {
p.to_owned()
} else if let Some(pkg_meta_wix_name) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("name"))
.and_then(|n| n.as_str())
.map(String::from)
{
pkg_meta_wix_name
} else {
package.name.clone()
}
}
fn no_build(&self, metadata: &Value) -> bool {
if self.no_build {
true
} else if let Some(pkg_meta_wix_no_build) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("no-build"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_no_build
} else {
false
}
}
fn target(&self) -> Result<String> {
if let Some(ref t) = self.target {
Ok(t.to_owned())
} else {
let output = Command::new("rustc")
.args(["--version", "--verbose"])
.output()?;
for line in output.stdout.split(|b| *b == b'\n') {
let mut line_elt = line.splitn(2, |b| *b == b':');
let first = line_elt.next();
let second = line_elt.next();
if let (Some(b"host"), Some(host_triple)) = (first, second) {
let s = String::from_utf8(host_triple.to_vec()).map_err(|_| {
Error::Generic(
"Failed to parse output of the 'rustc --verbose \
--version' command: invalid UTF8"
.to_string(),
)
});
return Ok(s?.trim().to_string());
}
}
Err(Error::Generic(
"Failed to parse output of the 'rustc --verbose --version' \
command"
.to_string(),
))
}
}
fn wixobj_destination(&self, target_directory: &Path) -> PathBuf {
target_directory.join(WIX).join("")
}
fn wixobj_sources(&self, wixobj_dst: &Path) -> Result<Vec<PathBuf>> {
let wixobj_sources: Vec<PathBuf> = std::fs::read_dir(wixobj_dst)?
.filter(|r| r.is_ok())
.map(|r| r.unwrap().path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some(WIX_OBJECT_FILE_EXTENSION))
.collect();
if wixobj_sources.is_empty() {
Err(Error::Generic(String::from("No WiX object files found.")))
} else {
Ok(wixobj_sources)
}
}
fn wxs_sources(&self, package: &Package) -> Result<Vec<PathBuf>> {
let project_wix_dir = package
.manifest_path
.parent()
.ok_or_else(|| {
Error::Generic(format!(
"The '{}' path for the package's manifest file is invalid",
package.manifest_path.display()
))
})
.map(|d| PathBuf::from(d).join(WIX))?;
let mut wix_sources = {
if project_wix_dir.exists() {
std::fs::read_dir(project_wix_dir)?
.filter(|r| r.is_ok())
.map(|r| r.unwrap().path())
.filter(|p| {
p.extension().and_then(|s| s.to_str()) == Some(WIX_SOURCE_FILE_EXTENSION)
})
.collect()
} else {
Vec::new()
}
};
if let Some(paths) = self.includes.as_ref() {
for p in paths {
if p.exists() {
if p.is_dir() {
return Err(Error::Generic(format!(
"The '{}' path is not a file. Please check the path and ensure it is to \
a WiX Source (wxs) file.",
p.display()
)));
} else {
trace!("Using the '{}' WiX source file", p.display());
}
} else {
return Err(Error::Generic(format!(
"The '{0}' file does not exist. Consider using the 'cargo \
wix print WXS > {0}' command to create it.",
p.display()
)));
}
}
wix_sources.extend(paths.clone());
} else if let Some(pkg_meta_wix_sources) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("include"))
.and_then(|i| i.as_array())
.map(|a| {
a.iter()
.map(|s| s.as_str().map(PathBuf::from).unwrap())
.collect::<Vec<PathBuf>>()
})
{
for pkg_meta_wix_source in &pkg_meta_wix_sources {
if pkg_meta_wix_source.exists() {
if pkg_meta_wix_source.is_dir() {
return Err(Error::Generic(format!(
"The '{}' path is not a file. Please check the path and \
ensure it is to a WiX Source (wxs) file in the \
'package.metadata.wix' section of the package's manifest \
(Cargo.toml).",
pkg_meta_wix_source.display()
)));
} else {
trace!(
"Using the '{}' WiX source file from the \
'package.metadata.wix' section in the package's \
manifest.",
pkg_meta_wix_source.display()
);
}
} else {
return Err(Error::Generic(format!(
"The '{0}' file does not exist. Consider using the \
'cargo wix print WXS > {0} command to create it.",
pkg_meta_wix_source.display()
)));
}
}
wix_sources.extend(pkg_meta_wix_sources);
}
if wix_sources.is_empty() {
Err(Error::Generic(String::from(
"There are no WXS files to create an installer",
)))
} else {
Ok(wix_sources)
}
}
fn version(&self, package: &Package) -> Result<Version> {
if let Some(ref v) = self.version {
Version::parse(v).map_err(Error::from)
} else if let Some(pkg_meta_wix_version) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("version"))
.and_then(|v| v.as_str())
{
Version::parse(pkg_meta_wix_version).map_err(Error::from)
} else {
Ok(package.version.clone())
}
}
}
impl Default for Execution {
fn default() -> Self {
Builder::new().build()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum WixObjKind {
Bundle,
Fragment,
Product,
}
impl WixObjKind {
pub fn is_bundle(&self) -> bool {
match *self {
Self::Bundle => true,
Self::Fragment => false,
Self::Product => false,
}
}
}
impl FromStr for WixObjKind {
type Err = crate::Error;
fn from_str(value: &str) -> Result<Self> {
match &*value.to_lowercase() {
"bundle" => Ok(Self::Bundle),
"fragment" => Ok(Self::Fragment),
"product" => Ok(Self::Product),
v => Err(Self::Err::Generic(format!(
"Unknown '{}' tag name from a WiX Object (wixobj) file.",
v
))),
}
}
}
impl TryFrom<&PathBuf> for WixObjKind {
type Error = crate::Error;
fn try_from(path: &PathBuf) -> Result<Self> {
let file = std::fs::File::open(path)?;
let mut decoder = encoding_rs_io::DecodeReaderBytes::new(file);
let mut content = String::new();
decoder.read_to_string(&mut content)?;
Self::try_from(content.as_str())
}
}
impl TryFrom<&str> for WixObjKind {
type Error = crate::Error;
fn try_from(content: &str) -> Result<Self> {
let package = sxd_document::parser::parse(content)?;
let document = package.as_document();
let mut context = sxd_xpath::Context::new();
context.set_namespace("wix", "http://schemas.microsoft.com/wix/2006/objects");
let xpath = sxd_xpath::Factory::new()
.build("/wix:wixObject/wix:section/@type")
.unwrap()
.unwrap();
let value = xpath.evaluate(&context, document.root())?.string();
Self::from_str(&value)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum InstallerKind {
Exe,
Msi,
}
impl InstallerKind {
pub fn extension(&self) -> &'static str {
match *self {
Self::Exe => EXE_FILE_EXTENSION,
Self::Msi => MSI_FILE_EXTENSION,
}
}
}
impl FromStr for InstallerKind {
type Err = crate::Error;
fn from_str(value: &str) -> Result<Self> {
match &*value.to_lowercase() {
"exe" => Ok(Self::Exe),
"msi" => Ok(Self::Msi),
_ => Err(Self::Err::Generic(format!(
"Unknown '{}' file extension for an installer",
value
))),
}
}
}
impl fmt::Display for InstallerKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.extension())
}
}
impl TryFrom<Vec<WixObjKind>> for InstallerKind {
type Error = crate::Error;
fn try_from(v: Vec<WixObjKind>) -> Result<Self> {
v.iter()
.fold(None, |a, v| match v {
WixObjKind::Bundle => Some(Self::Exe),
WixObjKind::Product => a.or(Some(Self::Msi)),
_ => a,
})
.ok_or_else(|| {
Self::Error::Generic(String::from(
"Could not determine the installer kind based on the WiX \
object files. There needs to be at least one 'product' or \
'bundle' tag in the collective WiX source files (wxs).",
))
})
}
}
impl Default for InstallerKind {
fn default() -> Self {
InstallerKind::Msi
}
}
#[cfg(test)]
mod tests {
use super::*;
mod builder {
use super::*;
#[test]
fn defaults_are_correct() {
let actual = Builder::new();
assert!(actual.bin_path.is_none());
assert!(actual.capture_output);
assert!(actual.compiler_args.is_none());
assert!(actual.culture.is_none());
assert!(!actual.debug_build);
assert!(!actual.debug_name);
assert!(actual.includes.is_none());
assert!(actual.input.is_none());
assert!(actual.linker_args.is_none());
assert!(actual.locale.is_none());
assert!(actual.name.is_none());
assert!(!actual.no_build);
assert!(actual.output.is_none());
assert!(actual.version.is_none());
}
#[test]
fn bin_path_works() {
const EXPECTED: &str = "C:\\Wix Toolset\\bin";
let mut actual = Builder::new();
actual.bin_path(Some(EXPECTED));
assert_eq!(actual.bin_path, Some(EXPECTED));
}
#[test]
fn capture_output_works() {
let mut actual = Builder::new();
actual.capture_output(false);
assert!(!actual.capture_output);
}
#[test]
fn compiler_args_with_single_value_works() {
const EXPECTED: &str = "-nologo";
let mut actual = Builder::new();
actual.compiler_args(Some(vec![EXPECTED]));
assert_eq!(actual.compiler_args, Some(vec![EXPECTED]));
}
#[test]
fn compiler_args_with_multiple_values_works() {
let expected: Vec<&str> = vec!["-arch", "x86"];
let mut actual = Builder::new();
actual.compiler_args(Some(expected.clone()));
assert_eq!(actual.compiler_args, Some(expected));
}
#[test]
fn culture_works() {
const EXPECTED: &str = "FrFr";
let mut actual = Builder::new();
actual.culture(Some(EXPECTED));
assert_eq!(actual.culture, Some(EXPECTED));
}
#[test]
fn russian_culture_works() {
const EXPECTED: &str = "RuRu";
let mut actual = Builder::new();
actual.culture(Some(EXPECTED));
assert_eq!(actual.culture, Some(EXPECTED));
}
#[test]
fn debug_build_works() {
let mut actual = Builder::new();
actual.debug_build(true);
assert!(actual.debug_build);
}
#[test]
fn debug_name_works() {
let mut actual = Builder::new();
actual.debug_name(true);
assert!(actual.debug_name);
}
#[test]
fn includes_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\wix\\main.wxs";
let mut actual = Builder::new();
actual.includes(Some(vec![EXPECTED]));
assert_eq!(actual.includes, Some(vec![EXPECTED]));
}
#[test]
fn input_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\Cargo.toml";
let mut actual = Builder::new();
actual.input(Some(EXPECTED));
assert_eq!(actual.input, Some(EXPECTED));
}
#[test]
fn linker_args_with_single_value_works() {
const EXPECTED: &str = "-nologo";
let mut actual = Builder::new();
actual.linker_args(Some(vec![EXPECTED]));
assert_eq!(actual.linker_args, Some(vec![EXPECTED]));
}
#[test]
fn linker_args_with_multiple_values_works() {
let expected: Vec<&str> = vec!["-ext", "HelloExtension"];
let mut actual = Builder::new();
actual.linker_args(Some(expected.clone()));
assert_eq!(actual.linker_args, Some(expected));
}
#[test]
fn locale_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\wix\\main.wxl";
let mut actual = Builder::new();
actual.locale(Some(EXPECTED));
assert_eq!(actual.locale, Some(EXPECTED));
}
#[test]
fn name_works() {
const EXPECTED: &str = "Name";
let mut actual = Builder::new();
actual.name(Some(EXPECTED));
assert_eq!(actual.name, Some(EXPECTED));
}
#[test]
fn no_build_works() {
let mut actual = Builder::new();
actual.no_build(true);
assert!(actual.no_build);
}
#[test]
fn output_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\output";
let mut actual = Builder::new();
actual.output(Some(EXPECTED));
assert_eq!(actual.output, Some(EXPECTED));
}
#[test]
fn version_works() {
const EXPECTED: &str = "1.2.3";
let mut actual = Builder::new();
actual.version(Some(EXPECTED));
assert_eq!(actual.version, Some(EXPECTED));
}
#[test]
fn build_with_defaults_works() {
let mut b = Builder::new();
let default_execution = b.build();
assert!(default_execution.bin_path.is_none());
assert!(default_execution.capture_output);
assert!(default_execution.compiler_args.is_none());
assert!(default_execution.culture.is_none());
assert!(!default_execution.debug_build);
assert!(!default_execution.debug_name);
assert!(default_execution.includes.is_none());
assert!(default_execution.input.is_none());
assert!(default_execution.linker_args.is_none());
assert!(default_execution.locale.is_none());
assert!(default_execution.name.is_none());
assert!(!default_execution.no_build);
assert!(default_execution.output.is_none());
assert!(default_execution.version.is_none());
}
#[test]
fn build_with_all_works() {
const EXPECTED_BIN_PATH: &str = "C:\\Wix Toolset\\bin";
const EXPECTED_CULTURE: &str = "FrFr";
const EXPECTED_COMPILER_ARGS: &str = "-nologo";
const EXPECTED_INCLUDES: &str = "C:\\tmp\\hello_world\\wix\\main.wxs";
const EXPECTED_INPUT: &str = "C:\\tmp\\hello_world\\Cargo.toml";
const EXPECTED_LINKER_ARGS: &str = "-nologo";
const EXPECTED_LOCALE: &str = "C:\\tmp\\hello_world\\wix\\main.wxl";
const EXPECTED_NAME: &str = "Name";
const EXPECTED_OUTPUT: &str = "C:\\tmp\\hello_world\\output";
const EXPECTED_VERSION: &str = "1.2.3";
let mut b = Builder::new();
b.bin_path(Some(EXPECTED_BIN_PATH));
b.capture_output(false);
b.culture(Some(EXPECTED_CULTURE));
b.compiler_args(Some(vec![EXPECTED_COMPILER_ARGS]));
b.debug_build(true);
b.debug_name(true);
b.includes(Some(vec![EXPECTED_INCLUDES]));
b.input(Some(EXPECTED_INPUT));
b.linker_args(Some(vec![EXPECTED_LINKER_ARGS]));
b.locale(Some(EXPECTED_LOCALE));
b.name(Some(EXPECTED_NAME));
b.no_build(true);
b.output(Some(EXPECTED_OUTPUT));
b.version(Some(EXPECTED_VERSION));
let execution = b.build();
assert_eq!(
execution.bin_path,
Some(EXPECTED_BIN_PATH).map(PathBuf::from)
);
assert!(!execution.capture_output);
assert_eq!(
execution.compiler_args,
Some(vec![String::from(EXPECTED_COMPILER_ARGS)])
);
assert_eq!(execution.culture, Some(EXPECTED_CULTURE).map(String::from));
assert!(execution.debug_build);
assert!(execution.debug_name);
assert_eq!(
execution.includes,
Some(vec![PathBuf::from(EXPECTED_INCLUDES)])
);
assert_eq!(execution.input, Some(PathBuf::from(EXPECTED_INPUT)));
assert_eq!(
execution.linker_args,
Some(vec![String::from(EXPECTED_LINKER_ARGS)])
);
assert_eq!(execution.locale, Some(EXPECTED_LOCALE).map(PathBuf::from));
assert_eq!(execution.name, Some(EXPECTED_NAME).map(String::from));
assert!(execution.no_build);
assert_eq!(execution.output, Some(EXPECTED_OUTPUT).map(String::from));
assert_eq!(execution.version, Some(EXPECTED_VERSION).map(String::from));
}
}
mod execution {
use super::*;
#[test]
fn debug_build_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"dbg-build": true
}
}"#;
let execution = Execution::default();
let debug_build = execution.debug_build(&PKG_META_WIX.parse::<Value>().unwrap());
assert!(debug_build);
}
#[test]
fn debug_name_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"dbg-name": true
}
}"#;
let execution = Execution::default();
let debug_name = execution.debug_name(&PKG_META_WIX.parse::<Value>().unwrap());
assert!(debug_name);
}
#[test]
fn version_metadata_works() {
const PKG_META_WIX: &str = r#"
{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "Apache-2.0",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "",
"metadata": {
"wix": {
"version": "2.1.0"
}
}
}"#;
let execution = Execution::default();
let version = execution
.version(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(version, Version::parse("2.1.0").unwrap());
}
#[test]
fn name_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"metadata": {
"wix": {
"name": "Metadata"
}
},
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": ""
}"#;
let execution = Execution::default();
let name = execution.name(&serde_json::from_str(PKG_META_WIX).unwrap());
assert_eq!(name, "Metadata".to_owned());
}
#[test]
fn no_build_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"no-build": true
}
}"#;
let execution = Execution::default();
let no_build = execution.no_build(&PKG_META_WIX.parse::<Value>().unwrap());
assert!(no_build);
}
#[test]
fn culture_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"culture": "Fr-Fr"
}
}"#;
let execution = Execution::default();
let culture = execution
.culture(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(culture, Cultures::FrFr);
}
#[test]
fn locale_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"locale": "wix/French.wxl"
}
}"#;
let execution = Execution::default();
let locale = execution
.locale(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(locale, Some(PathBuf::from("wix/French.wxl")));
}
#[test]
fn output_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "XYZ",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "",
"metadata": {
"wix": {
"output": "target/wix/test.msi"
}
}
}"#;
let execution = Execution::default();
let output = execution.installer_destination(
"Different",
&"2.1.0".parse::<Version>().unwrap(),
&Cfg::of("x86_64-pc-windows-msvc").unwrap(),
false,
&InstallerKind::default(),
&serde_json::from_str(PKG_META_WIX).unwrap(),
Path::new("target/"),
);
assert_eq!(output, PathBuf::from("target/wix/test.msi"));
}
#[test]
fn include_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "XYZ",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "C:\\Cargo.toml",
"metadata": {
"wix": {
"include": ["Cargo.toml"]
}
}
}"#;
let execution = Execution::default();
let sources = execution
.wxs_sources(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(sources, vec![PathBuf::from("Cargo.toml")]);
}
#[test]
fn compiler_args_override_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"compiler-args": ["-nologo"]
}
}"#;
let mut builder = Builder::default();
builder.compiler_args(Some(vec!["-ws"]));
let execution = builder.build();
let args = execution.compiler_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(args, Some(vec![String::from("-ws")]));
}
#[test]
fn compiler_args_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"compiler-args": ["-nologo", "-ws"]
}
}"#;
let execution = Execution::default();
let args = execution.compiler_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(
args,
Some(vec![String::from("-nologo"), String::from("-ws")])
);
}
#[test]
fn linker_args_override_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"linker-args": ["-nologo"]
}
}"#;
let mut builder = Builder::default();
builder.linker_args(Some(vec!["-ws"]));
let execution = builder.build();
let args = execution.linker_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(args, Some(vec![String::from("-ws")]));
}
#[test]
fn linker_args_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"linker-args": ["-nologo", "-ws"]
}
}"#;
let execution = Execution::default();
let args = execution.linker_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(
args,
Some(vec![String::from("-nologo"), String::from("-ws")])
);
}
const EMPTY_PKG_META_WIX: &str = r#"{"wix": {}}"#;
#[test]
fn culture_works() {
let execution = Execution::default();
let culture = execution
.culture(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(culture, Cultures::EnUs);
}
#[test]
fn locale_works() {
let execution = Execution::default();
let locale = execution
.locale(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert!(locale.is_none());
}
#[test]
fn no_build_works() {
let execution = Execution::default();
let no_build = execution.no_build(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap());
assert!(!no_build);
}
#[test]
fn compiler_is_correct_with_defaults() {
let expected = Command::new(
env::var_os(WIX_PATH_KEY)
.map(|s| {
let mut p = PathBuf::from(s);
p.push(BINARY_FOLDER_NAME);
p.push(WIX_COMPILER);
p.set_extension(EXE_FILE_EXTENSION);
p
})
.unwrap(),
);
let e = Execution::default();
let actual = e.compiler().unwrap();
assert_eq!(format!("{:?}", actual), format!("{:?}", expected));
}
#[test]
fn wixobj_destination_works() {
let execution = Execution::default();
assert_eq!(
execution.wixobj_destination(Path::new("target")),
PathBuf::from("target").join("wix")
)
}
}
mod wixobj_kind {
use super::*;
const PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="product"></section>
</wixObject>"#;
const BUNDLE_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="bundle"></section>
</wixObject>"#;
const FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="fragment"></section>
</wixObject>"#;
const UNKNOWN_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="unknown"></section>
</wixObject>"#;
const BUNDLE_AND_PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="bundle"></section>
<section id="*" type="product"></section>
</wixObject>"#;
const PRODUCT_AND_FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="product"></section>
<section id="*" type="fragment"></section>
</wixObject>"#;
#[test]
fn try_from_product_object_works() {
assert_eq!(
WixObjKind::try_from(PRODUCT_WIXOBJ),
Ok(WixObjKind::Product)
);
}
#[test]
fn try_from_bundle_object_works() {
assert_eq!(WixObjKind::try_from(BUNDLE_WIXOBJ), Ok(WixObjKind::Bundle));
}
#[test]
fn try_from_fragment_object_works() {
assert_eq!(
WixObjKind::try_from(FRAGMENT_WIXOBJ),
Ok(WixObjKind::Fragment)
);
}
#[test]
fn try_from_bundle_and_product_object_works() {
assert_eq!(
WixObjKind::try_from(BUNDLE_AND_PRODUCT_WIXOBJ),
Ok(WixObjKind::Bundle)
);
}
#[test]
fn try_from_product_and_fragment_object_works() {
assert_eq!(
WixObjKind::try_from(PRODUCT_AND_FRAGMENT_WIXOBJ),
Ok(WixObjKind::Product)
);
}
#[test]
#[should_panic]
fn try_from_unknown_object_fails() {
WixObjKind::try_from(UNKNOWN_WIXOBJ).unwrap();
}
}
mod installer_kind {
use super::*;
#[test]
fn try_from_wixobj_single_product_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Product]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Bundle]),
Ok(InstallerKind::Exe)
)
}
#[test]
#[should_panic]
fn try_from_wixobj_single_fragment_fails() {
InstallerKind::try_from(vec![WixObjKind::Fragment]).unwrap();
}
#[test]
#[should_panic]
fn try_from_wixobj_multiple_fragments_fails() {
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Fragment,
])
.unwrap();
}
#[test]
fn try_from_wixobj_product_and_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Product, WixObjKind::Bundle]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_products_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_single_product_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product,
WixObjKind::Fragment
]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Bundle,
WixObjKind::Fragment
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_products_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product,
WixObjKind::Fragment,
WixObjKind::Product
]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_multiple_products_and_bundles_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_products_fragments_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
}
}