use crate::Cultures;
use crate::Error;
use crate::Platform;
use crate::Result;
use crate::BINARY_FOLDER_NAME;
use crate::CARGO;
use crate::EXE_FILE_EXTENSION;
use crate::MSI_FILE_EXTENSION;
use crate::TARGET_FOLDER_NAME;
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 semver::Version;
use std::convert::TryFrom;
use std::env;
use std::fmt;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::FromStr;
use toml::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,
output: 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,
output: 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 output(&mut self, o: Option<&'a str>) -> &mut Self {
self.output = o;
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,
output: self.output.map(String::from),
version: self.version.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,
output: 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.output = {:?}", self.output);
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())?;
let name = self.name(&manifest)?;
debug!("name = {:?}", name);
let version = self.version(&manifest)?;
debug!("version = {:?}", version);
let compiler_args = self.compiler_args(&manifest);
debug!("compiler_args = {:?}", compiler_args);
let culture = self.culture(&manifest)?;
debug!("culture = {:?}", culture);
let linker_args = self.linker_args(&manifest);
debug!("linker_args = {:?}", linker_args);
let locale = self.locale(&manifest)?;
debug!("locale = {:?}", locale);
let platform = self.platform();
debug!("platform = {:?}", platform);
let debug_build = self.debug_build(&manifest);
debug!("debug_build = {:?}", debug_build);
let debug_name = self.debug_name(&manifest);
debug!("debug_name = {:?}", debug_name);
let wxs_sources = self.wxs_sources(&manifest)?;
debug!("wxs_sources = {:?}", wxs_sources);
let wixobj_destination = self.wixobj_destination()?;
debug!("wixobj_destination = {:?}", wixobj_destination);
let no_build = self.no_build(&manifest);
debug!("no_build = {:?}", no_build);
if no_build {
warn!("Skipped building the release binary");
} else {
info!("Building the release binary");
let mut builder = Command::new(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");
}
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());
}
if debug_build {
compiler.arg("-dProfile=debug");
} else {
compiler.arg("-dProfile=release");
}
compiler
.arg(format!("-dVersion={}", version))
.arg(format!("-dPlatform={}", platform))
.arg("-ext")
.arg("WixUtilExtension")
.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,
platform,
debug_name,
&installer_kind,
&manifest,
)?;
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,
));
}
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 debug_build(&self, manifest: &Value) -> bool {
if self.debug_build {
true
} else if let Some(pkg_meta_wix_debug_build) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("dbg-build"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_debug_build
} else {
false
}
}
fn debug_name(&self, manifest: &Value) -> bool {
if self.debug_name {
true
} else if let Some(pkg_meta_wix_debug_name) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("dbg-name"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_debug_name
} else {
false
}
}
fn no_build(&self, manifest: &Value) -> bool {
if self.no_build {
true
} else if let Some(pkg_meta_wix_no_build) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("no-build"))
.and_then(|c| c.as_bool())
{
pkg_meta_wix_no_build
} else {
false
}
}
fn compiler_args(&self, manifest: &Value) -> Option<Vec<String>> {
manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.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>>()
})
.or_else(|| self.compiler_args.to_owned())
}
fn linker_args(&self, manifest: &Value) -> Option<Vec<String>> {
manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.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>>()
})
.or_else(|| self.linker_args.to_owned())
}
fn culture(&self, manifest: &Value) -> Result<Cultures> {
if let Some(culture) = &self.culture {
Cultures::from_str(culture)
} else if let Some(pkg_meta_wix_culture) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("culture"))
.and_then(|c| c.as_str())
{
Cultures::from_str(pkg_meta_wix_culture)
} else {
Ok(Cultures::EnUs)
}
}
fn locale(&self, manifest: &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) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.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 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 platform(&self) -> Platform {
if cfg!(target_arch = "x86_64") {
Platform::X64
} else {
Platform::X86
}
}
fn name(&self, manifest: &Value) -> Result<String> {
if let Some(ref p) = self.name {
Ok(p.to_owned())
} else if let Some(pkg_meta_wix_name) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("name"))
.and_then(|n| n.as_str())
.map(String::from)
{
Ok(pkg_meta_wix_name)
} else {
manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("name"))
.and_then(|n| n.as_str())
.map(String::from)
.ok_or(Error::Manifest("name"))
}
}
fn installer_destination(
&self,
name: &str,
version: &Version,
platform: Platform,
debug_name: bool,
installer_kind: &InstallerKind,
manifest: &Value,
) -> Result<PathBuf> {
let filename = if debug_name {
format!(
"{}-{}-{}-debug.{}",
name,
version,
platform.arch(),
installer_kind
)
} else {
format!(
"{}-{}-{}.{}",
name,
version,
platform.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() {
Ok(path.join(filename))
} else {
Ok(path.to_owned())
}
} else if let Some(pkg_meta_wix_output) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.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()
{
Ok(path.join(filename))
} else {
Ok(path.to_owned())
}
} else if let Some(manifest_path) = &self.input {
trace!("Using the package's manifest (Cargo.toml) file path to specify the MSI destination");
manifest_path
.parent()
.ok_or_else(|| {
Error::Generic(format!(
"The '{}' path for the package's manifest file is invalid",
manifest_path.display()
))
})
.map(|d| {
PathBuf::from(d)
.join(TARGET_FOLDER_NAME)
.join(WIX)
.join(filename)
})
} else {
trace!("Using the current working directory (CWD) to build the WiX object files destination");
Ok(PathBuf::from(TARGET_FOLDER_NAME).join(WIX).join(filename))
}
}
fn wixobj_destination(&self) -> Result<PathBuf> {
let mut dst = if let Some(manifest_path) = &self.input {
trace!(
"Using the package's manifest (Cargo.toml) file path to build \
the Wix object files destination"
);
manifest_path
.parent()
.ok_or_else(|| {
Error::Generic(format!(
"The '{}' path for the package's manifest file is invalid",
manifest_path.display()
))
})
.map(|d| PathBuf::from(d).join(TARGET_FOLDER_NAME))
} else {
trace!("Using the current working directory (CWD) to build the WiX object files destination");
Ok(PathBuf::from(TARGET_FOLDER_NAME))
}?;
dst.push(format!("{}\\", WIX));
Ok(dst)
}
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, manifest: &Value) -> Result<Vec<PathBuf>> {
let project_wix_dir = if let Some(manifest_path) = &self.input {
trace!("Using the package's manifest (Cargo.toml) file path to obtain all WXS files");
manifest_path
.parent()
.ok_or_else(|| {
Error::Generic(format!(
"The '{}' path for the package's manifest file is invalid",
manifest_path.display()
))
})
.map(|d| PathBuf::from(d).join(WIX))
} else {
trace!("Using the current working directory (CWD) to obtain all WXS files");
Ok(PathBuf::from(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) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.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, manifest: &Value) -> Result<Version> {
if let Some(ref v) = self.version {
Version::parse(v).map_err(Error::from)
} else if let Some(pkg_meta_wix_version) = manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("metadata"))
.and_then(|m| m.as_table())
.and_then(|t| t.get("wix"))
.and_then(|w| w.as_table())
.and_then(|t| t.get("version"))
.and_then(|v| v.as_str())
{
Version::parse(pkg_meta_wix_version).map_err(Error::from)
} else {
manifest
.get("package")
.and_then(|p| p.as_table())
.and_then(|t| t.get("version"))
.and_then(|v| v.as_str())
.ok_or(Error::Manifest("version"))
.and_then(|s| Version::parse(s).map_err(Error::from))
}
}
}
impl Default for Execution {
fn default() -> Self {
Builder::new().build()
}
}
#[derive(Debug, PartialEq)]
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)]
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_else(|| 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#"
[package.metadata.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#"
[package.metadata.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#"
[package]
version = "0.1.0"
[package.metadata.wix]
version = "2.1.0"
"#;
let execution = Execution::default();
let version = execution
.version(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(version, Version::parse("2.1.0").unwrap());
}
#[test]
fn name_metadata_works() {
const PKG_META_WIX: &str = r#"
[package]
name = "example"
[package.metadata.wix]
name = "Metadata"
"#;
let execution = Execution::default();
let name = execution
.name(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(name, "Metadata".to_owned());
}
#[test]
fn no_build_metadata_works() {
const PKG_META_WIX: &str = r#"
[package.metadata.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#"
[package.metadata.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#"
[package.metadata.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#"
[package.metadata.wix]
output = "target/wix/test.msi"
"#;
let execution = Execution::default();
let output = execution
.installer_destination(
"Different",
&"2.1.0".parse::<Version>().unwrap(),
Platform::X64,
false,
&InstallerKind::default(),
&PKG_META_WIX.parse::<Value>().unwrap(),
)
.unwrap();
assert_eq!(output, PathBuf::from("target/wix/test.msi"));
}
#[test]
fn include_metadata_works() {
const PKG_META_WIX: &str = r#"
[package.metadata.wix]
include = ["Cargo.toml"]
"#;
let execution = Execution::default();
let sources = execution
.wxs_sources(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(sources, vec![PathBuf::from("Cargo.toml")]);
}
#[test]
fn compiler_args_metadata_works() {
const PKG_META_WIX: &str = r#"
[package.metadata.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_metadata_works() {
const PKG_META_WIX: &str = r#"
[package.metadata.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#"[package.metadata.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().unwrap(),
PathBuf::from("target\\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)
)
}
}
}