pub use crate::templates::Template;
pub mod clean;
pub mod create;
pub mod initialize;
mod licenses;
pub mod print;
pub mod purge;
pub mod sign;
pub mod stored_path;
mod templates;
use camino::Utf8Path;
use log::debug;
use std::convert::TryFrom;
use std::default::Default;
use std::env;
use std::error::Error as StdError;
use std::ffi::OsStr;
use std::fmt;
use std::fmt::Display;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use cargo_metadata::{Metadata, MetadataCommand, Package};
use rustc_cfg::Cfg;
pub const BINARY_FOLDER_NAME: &str = "bin";
pub const CARGO_MANIFEST_FILE: &str = "Cargo.toml";
pub const CARGO: &str = "cargo";
pub const EXE_FILE_EXTENSION: &str = "exe";
pub const LICENSE_FILE_NAME: &str = "License";
pub const MSI_FILE_EXTENSION: &str = "msi";
pub const RTF_FILE_EXTENSION: &str = "rtf";
pub const SIGNTOOL: &str = "signtool";
pub const SIGNTOOL_PATH_KEY: &str = "SIGNTOOL_PATH";
pub const WIX: &str = "wix";
pub const WIX_COMPILER: &str = "candle";
pub const WIX_LINKER: &str = "light";
pub const MSIEXEC: &str = "msiexec";
pub const WIX_OBJECT_FILE_EXTENSION: &str = "wixobj";
pub const WIX_PATH_KEY: &str = "WIX";
pub const WIX_SOURCE_FILE_EXTENSION: &str = "wxs";
pub const WIX_SOURCE_FILE_NAME: &str = "main";
pub type Result<T> = std::result::Result<T, Error>;
fn cargo_toml_file(input: Option<&PathBuf>) -> Result<PathBuf> {
let i = match input {
Some(i) => i.to_owned(),
None => {
let mut cwd = env::current_dir()?;
cwd.push(CARGO_MANIFEST_FILE);
cwd
}
};
if i.exists() {
if i.is_file() {
if i.file_name() == Some(OsStr::new(CARGO_MANIFEST_FILE)) {
Ok(i)
} else {
Err(Error::not_a_manifest(&i))
}
} else {
Err(Error::not_a_file(&i))
}
} else {
Err(Error::not_found(&i))
}
}
fn description(description: Option<String>, manifest: &Package) -> Option<String> {
description.or_else(|| manifest.description.clone())
}
fn manifest(input: Option<&PathBuf>) -> Result<Metadata> {
let cargo_file_path = cargo_toml_file(input)?;
debug!("cargo_file_path = {:?}", cargo_file_path);
Ok(MetadataCommand::new()
.no_deps()
.manifest_path(cargo_file_path)
.exec()?)
}
fn package(manifest: &Metadata, package: Option<&str>) -> Result<Package> {
let package_id = if let Some(p) = package {
manifest
.workspace_members
.iter()
.find(|n| manifest[n].name == p)
.ok_or_else(|| Error::Generic(format!("No `{p}` package found in the project")))?
} else if manifest.workspace_members.len() == 1 {
&manifest.workspace_members[0]
} else {
return Err(Error::Generic(String::from(
"Workspace detected. Please pass a package name.",
)));
};
Ok(manifest[package_id].clone())
}
fn product_name(product_name: Option<&String>, manifest: &Package) -> String {
if let Some(p) = product_name {
p.to_owned()
} else {
manifest.name.clone()
}
}
#[derive(Debug)]
pub enum Error {
CargoMetadata(cargo_metadata::Error),
Command(&'static str, i32, bool),
Generic(String),
Io(io::Error),
Manifest(&'static str),
Mustache(mustache::Error),
Uuid(uuid::Error),
Version(semver::Error),
Xml(sxd_document::parser::Error),
XPath(sxd_xpath::ExecutionError),
}
impl Error {
pub fn code(&self) -> i32 {
match *self {
Error::Command(..) => 1,
Error::Generic(..) => 2,
Error::Io(..) => 3,
Error::Manifest(..) => 4,
Error::Mustache(..) => 5,
Error::Uuid(..) => 6,
Error::Version(..) => 7,
Error::Xml(..) => 8,
Error::XPath(..) => 9,
Error::CargoMetadata(..) => 10,
}
}
pub fn already_exists(p: &Utf8Path) -> Self {
io::Error::new(ErrorKind::AlreadyExists, p.to_string()).into()
}
pub fn not_found(p: &Path) -> Self {
io::Error::new(ErrorKind::NotFound, p.display().to_string()).into()
}
pub fn not_a_file(p: &Path) -> Self {
io::Error::new(
ErrorKind::InvalidInput,
format!("The '{}' path is not a file.", p.display()),
)
.into()
}
pub fn not_a_manifest(p: &Path) -> Self {
io::Error::new(
ErrorKind::InvalidInput,
format!(
"The '{}' path does not appear to be to a '{}' file.",
p.display(),
CARGO_MANIFEST_FILE
),
)
.into()
}
pub fn as_str(&self) -> &str {
match *self {
Error::CargoMetadata(..) => "CargoMetadata",
Error::Command(..) => "Command",
Error::Generic(..) => "Generic",
Error::Io(..) => "Io",
Error::Manifest(..) => "Manifest",
Error::Mustache(..) => "Mustache",
Error::Uuid(..) => "UUID",
Error::Version(..) => "Version",
Error::Xml(..) => "XML",
Error::XPath(..) => "XPath",
}
}
}
impl StdError for Error {
fn description(&self) -> &str {
self.as_str()
}
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Error::CargoMetadata(ref err) => Some(err),
Error::Io(ref err) => Some(err),
Error::Mustache(ref err) => Some(err),
Error::Uuid(ref err) => Some(err),
Error::Version(ref err) => Some(err),
Error::Xml(ref err) => Some(err),
Error::XPath(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::CargoMetadata(ref err) => err.fmt(f),
Error::Command(ref command, ref code, captured_output) => {
if captured_output {
write!(
f,
"The '{command}' application failed with exit code = {code}. Consider using the \
'--nocapture' flag to obtain more information."
)
} else {
write!(
f,
"The '{command}' application failed with exit code = {code}"
)
}
}
Error::Generic(ref msg) => msg.fmt(f),
Error::Io(ref err) => match err.kind() {
ErrorKind::AlreadyExists => {
if let Some(path) = err.get_ref() {
write!(f, "The '{path}' file already exists. Use the '--force' flag to overwrite the contents.")
} else {
err.fmt(f)
}
}
ErrorKind::NotFound => {
if let Some(path) = err.get_ref() {
write!(f, "The '{path}' path does not exist")
} else {
err.fmt(f)
}
}
_ => err.fmt(f),
},
Error::Manifest(ref var) => write!(
f,
"No '{var}' field found in the package's manifest (Cargo.toml)"
),
Error::Mustache(ref err) => err.fmt(f),
Error::Uuid(ref err) => err.fmt(f),
Error::Version(ref err) => err.fmt(f),
Error::Xml(ref err) => err.fmt(f),
Error::XPath(ref err) => err.fmt(f),
}
}
}
impl PartialEq for Error {
fn eq(&self, other: &Error) -> bool {
self.code() == other.code()
}
}
impl<'a> From<&'a str> for Error {
fn from(s: &str) -> Self {
Error::Generic(s.to_string())
}
}
impl From<cargo_metadata::Error> for Error {
fn from(err: cargo_metadata::Error) -> Self {
Error::CargoMetadata(err)
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
impl From<mustache::Error> for Error {
fn from(err: mustache::Error) -> Self {
Error::Mustache(err)
}
}
impl From<semver::Error> for Error {
fn from(err: semver::Error) -> Self {
Error::Version(err)
}
}
impl From<std::path::StripPrefixError> for Error {
fn from(err: std::path::StripPrefixError) -> Self {
Error::Generic(err.to_string())
}
}
impl From<sxd_document::parser::Error> for Error {
fn from(err: sxd_document::parser::Error) -> Self {
Error::Xml(err)
}
}
impl From<sxd_xpath::ExecutionError> for Error {
fn from(err: sxd_xpath::ExecutionError) -> Self {
Error::XPath(err)
}
}
impl From<uuid::Error> for Error {
fn from(err: uuid::Error) -> Self {
Error::Uuid(err)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WixArch {
X86,
X64,
Arm,
Arm64,
}
impl Display for WixArch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Self::X86 => write!(f, "x86"),
Self::X64 => write!(f, "x64"),
Self::Arm => write!(f, "arm"),
Self::Arm64 => write!(f, "arm64"),
}
}
}
impl TryFrom<&Cfg> for WixArch {
type Error = crate::Error;
fn try_from(c: &Cfg) -> std::result::Result<Self, Self::Error> {
match &*c.target_arch {
"x86" => Ok(Self::X86),
"x86_64" => Ok(Self::X64),
"aarch64" => Ok(Self::Arm64),
"thumbv7a" => Ok(Self::Arm),
a => {
if a.starts_with("arm") {
Ok(Self::Arm)
} else {
Err(Error::Generic(format!(
"Unsupported target architecture: {a}"
)))
}
}
}
}
}
impl FromStr for WixArch {
type Err = crate::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
Self::try_from(&Cfg::of(s).map_err(|e| Error::Generic(e.to_string()))?)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimestampServer {
Custom(String),
Comodo,
Verisign,
}
impl TimestampServer {
pub fn url(&self) -> &str {
match *self {
TimestampServer::Custom(ref url) => url,
TimestampServer::Comodo => "http://timestamp.comodoca.com/",
TimestampServer::Verisign => "http://timestamp.verisign.com/scripts/timstamp.dll",
}
}
}
impl fmt::Display for TimestampServer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.url())
}
}
impl FromStr for TimestampServer {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().trim() {
"comodo" => Ok(TimestampServer::Comodo),
"verisign" => Ok(TimestampServer::Verisign),
u => Ok(TimestampServer::Custom(String::from(u))),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Cultures {
ArSa,
BgBg,
CaEs,
HrHr,
CsCz,
DaDk,
NlNl,
#[default]
EnUs,
EtEe,
FiFi,
FrFr,
DeDe,
ElGr,
HeIl,
HiIn,
HuHu,
ItIt,
JaJp,
KkKz,
KoKr,
LvLv,
LtLt,
NbNo,
PlPl,
PtBr,
PtPt,
RoRo,
RuRu,
SrLatnCs,
ZhCn,
SkSk,
SlSi,
EsEs,
SvSe,
ThTh,
ZhHk,
ZhTw,
TrTr,
UkUa,
}
impl Cultures {
pub fn language(&self) -> &'static str {
match *self {
Cultures::ArSa => "Arabic",
Cultures::BgBg => "Bulgarian",
Cultures::CaEs => "Catalan",
Cultures::HrHr => "Croatian",
Cultures::CsCz => "Czech",
Cultures::DaDk => "Danish",
Cultures::NlNl => "Dutch",
Cultures::EnUs => "English",
Cultures::EtEe => "Estonian",
Cultures::FiFi => "Finnish",
Cultures::FrFr => "French",
Cultures::DeDe => "German",
Cultures::ElGr => "Greek",
Cultures::HeIl => "Hebrew",
Cultures::HiIn => "Hindi",
Cultures::HuHu => "Hungarian",
Cultures::ItIt => "Italian",
Cultures::JaJp => "Japanese",
Cultures::KkKz => "Kazakh",
Cultures::KoKr => "Korean",
Cultures::LvLv => "Latvian",
Cultures::LtLt => "Lithuanian",
Cultures::NbNo => "Norwegian",
Cultures::PlPl => "Polish",
Cultures::PtBr => "Portuguese",
Cultures::PtPt => "Portuguese",
Cultures::RoRo => "Romanian",
Cultures::RuRu => "Russian",
Cultures::SrLatnCs => "Serbian (Latin)",
Cultures::ZhCn => "Simplified Chinese",
Cultures::SkSk => "Slovak",
Cultures::SlSi => "Slovenian",
Cultures::EsEs => "Spanish",
Cultures::SvSe => "Swedish",
Cultures::ThTh => "Thai",
Cultures::ZhHk => "Traditional Chinese",
Cultures::ZhTw => "Traditional Chinese",
Cultures::TrTr => "Turkish",
Cultures::UkUa => "Ukrainian",
}
}
pub fn location(&self) -> &'static str {
match *self {
Cultures::ArSa => "Saudi Arabia",
Cultures::BgBg => "Bulgaria",
Cultures::CaEs => "Spain",
Cultures::HrHr => "Croatia",
Cultures::CsCz => "Czech Republic",
Cultures::DaDk => "Denmark",
Cultures::NlNl => "Netherlands",
Cultures::EnUs => "United States",
Cultures::EtEe => "Estonia",
Cultures::FiFi => "Finland",
Cultures::FrFr => "France",
Cultures::DeDe => "Germany",
Cultures::ElGr => "Greece",
Cultures::HeIl => "Israel",
Cultures::HiIn => "India",
Cultures::HuHu => "Hungary",
Cultures::ItIt => "Italy",
Cultures::JaJp => "Japan",
Cultures::KkKz => "Kazakhstan",
Cultures::KoKr => "Korea",
Cultures::LvLv => "Latvia",
Cultures::LtLt => "Lithuania",
Cultures::NbNo => "Norway",
Cultures::PlPl => "Poland",
Cultures::PtBr => "Brazil",
Cultures::PtPt => "Portugal",
Cultures::RoRo => "Romania",
Cultures::RuRu => "Russia",
Cultures::SrLatnCs => "Serbia and Montenegro",
Cultures::ZhCn => "China",
Cultures::SkSk => "Slovak Republic",
Cultures::SlSi => "Solvenia",
Cultures::EsEs => "Spain",
Cultures::SvSe => "Sweden",
Cultures::ThTh => "Thailand",
Cultures::ZhHk => "Hong Kong SAR",
Cultures::ZhTw => "Taiwan",
Cultures::TrTr => "Turkey",
Cultures::UkUa => "Ukraine",
}
}
}
impl fmt::Display for Cultures {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Cultures::ArSa => write!(f, "ar-SA"),
Cultures::BgBg => write!(f, "bg-BG"),
Cultures::CaEs => write!(f, "ca-ES"),
Cultures::HrHr => write!(f, "hr-HR"),
Cultures::CsCz => write!(f, "cs-CZ"),
Cultures::DaDk => write!(f, "da-DK"),
Cultures::NlNl => write!(f, "nl-NL"),
Cultures::EnUs => write!(f, "en-US"),
Cultures::EtEe => write!(f, "et-EE"),
Cultures::FiFi => write!(f, "fi-FI"),
Cultures::FrFr => write!(f, "fr-FR"),
Cultures::DeDe => write!(f, "de-DE"),
Cultures::ElGr => write!(f, "el-GR"),
Cultures::HeIl => write!(f, "he-IL"),
Cultures::HiIn => write!(f, "hi-IN"),
Cultures::HuHu => write!(f, "hu-HU"),
Cultures::ItIt => write!(f, "it-IT"),
Cultures::JaJp => write!(f, "ja-JP"),
Cultures::KkKz => write!(f, "kk-KZ"),
Cultures::KoKr => write!(f, "ko-KR"),
Cultures::LvLv => write!(f, "lv-LV"),
Cultures::LtLt => write!(f, "lt-LT"),
Cultures::NbNo => write!(f, "nb-NO"),
Cultures::PlPl => write!(f, "pl-PL"),
Cultures::PtBr => write!(f, "pt-BR"),
Cultures::PtPt => write!(f, "pt-PT"),
Cultures::RoRo => write!(f, "ro-RO"),
Cultures::RuRu => write!(f, "ru-RU"),
Cultures::SrLatnCs => write!(f, "sr-Latn-CS"),
Cultures::ZhCn => write!(f, "zh-CN"),
Cultures::SkSk => write!(f, "sk-SK"),
Cultures::SlSi => write!(f, "sl-SI"),
Cultures::EsEs => write!(f, "es-ES"),
Cultures::SvSe => write!(f, "sv-SE"),
Cultures::ThTh => write!(f, "th-TH"),
Cultures::ZhHk => write!(f, "zh-HK"),
Cultures::ZhTw => write!(f, "zh-TW"),
Cultures::TrTr => write!(f, "tr-TR"),
Cultures::UkUa => write!(f, "uk-UA"),
}
}
}
impl FromStr for Cultures {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().trim() {
"ar-sa" => Ok(Cultures::ArSa),
"bg-bg" => Ok(Cultures::BgBg),
"ca-es" => Ok(Cultures::CaEs),
"hr-hr" => Ok(Cultures::HrHr),
"cs-cz" => Ok(Cultures::CsCz),
"da-dk" => Ok(Cultures::DaDk),
"nl-nl" => Ok(Cultures::NlNl),
"en-us" => Ok(Cultures::EnUs),
"et-ee" => Ok(Cultures::EtEe),
"fi-fi" => Ok(Cultures::FiFi),
"fr-fr" => Ok(Cultures::FrFr),
"de-de" => Ok(Cultures::DeDe),
"el-gr" => Ok(Cultures::ElGr),
"he-il" => Ok(Cultures::HeIl),
"hi-in" => Ok(Cultures::HiIn),
"hu-hu" => Ok(Cultures::HuHu),
"it-it" => Ok(Cultures::ItIt),
"ja-jp" => Ok(Cultures::JaJp),
"kk-kz" => Ok(Cultures::KkKz),
"ko-kr" => Ok(Cultures::KoKr),
"lv-lv" => Ok(Cultures::LvLv),
"lt-lt" => Ok(Cultures::LtLt),
"nb-no" => Ok(Cultures::NbNo),
"pl-pl" => Ok(Cultures::PlPl),
"pt-br" => Ok(Cultures::PtBr),
"pt-pt" => Ok(Cultures::PtPt),
"ro-ro" => Ok(Cultures::RoRo),
"ru-ru" => Ok(Cultures::RuRu),
"sr-latn-cs" => Ok(Cultures::SrLatnCs),
"zh-cn" => Ok(Cultures::ZhCn),
"sk-sk" => Ok(Cultures::SkSk),
"sl-si" => Ok(Cultures::SlSi),
"es-es" => Ok(Cultures::EsEs),
"sv-se" => Ok(Cultures::SvSe),
"th-th" => Ok(Cultures::ThTh),
"zh-hk" => Ok(Cultures::ZhHk),
"zh-tw" => Ok(Cultures::ZhTw),
"tr-tr" => Ok(Cultures::TrTr),
"uk-ua" => Ok(Cultures::UkUa),
e => Err(Error::Generic(format!("Unknown '{e}' culture"))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_fs::TempDir;
use std::env;
use std::fs;
pub fn setup_project(toml: &str) -> TempDir {
pub const PERSIST_VAR_NAME: &str = "CARGO_WIX_TEST_PERSIST";
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("Cargo.toml"), toml).unwrap();
fs::create_dir(temp_dir.path().join("src")).unwrap();
fs::write(temp_dir.path().join("src").join("main.rs"), "fn main() {}").unwrap();
temp_dir.into_persistent_if(env::var(PERSIST_VAR_NAME).is_ok())
}
mod culture {
use super::*;
#[test]
fn from_str_is_correct_for_dash_russian() {
assert_eq!(Cultures::from_str("ru-ru"), Ok(Cultures::RuRu));
}
#[test]
#[should_panic]
fn from_str_fails_for_underscore_russian() {
Cultures::from_str("ru_ru").unwrap();
}
#[test]
fn display_is_correct_for_russian() {
assert_eq!(format!("{}", Cultures::RuRu), String::from("ru-RU"));
}
#[test]
fn from_str_is_correct_for_lowercase_slovak() {
assert_eq!(Cultures::from_str("sk-sk"), Ok(Cultures::SkSk));
}
#[test]
fn from_str_is_correct_for_uppercase_slovak() {
assert_eq!(Cultures::from_str("sk-SK"), Ok(Cultures::SkSk));
}
#[test]
#[should_panic]
fn from_str_fails_for_underscore_slovak() {
Cultures::from_str("sk_sk").unwrap();
}
#[test]
fn display_is_correct_for_slovak() {
assert_eq!(format!("{}", Cultures::SkSk), String::from("sk-SK"));
}
}
mod wix_arch {
use super::*;
#[test]
fn try_from_x86_64_pc_windows_msvc_is_correct() {
let arch = WixArch::try_from(&Cfg::of("x86_64-pc-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::X64);
}
#[test]
fn try_from_x86_64_pc_windows_gnu_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("x86_64-pc-windows-gnu").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X64);
}
#[test]
fn try_from_x86_64_uwp_windows_msvc_is_correct() {
let arch = WixArch::try_from(&Cfg::of("x86_64-uwp-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::X64);
}
#[test]
fn try_from_x86_64_uwp_windows_gnu_is_correct() {
let arch = WixArch::try_from(&Cfg::of("x86_64-uwp-windows-gnu").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::X64);
}
#[test]
fn try_from_i686_pc_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("i686-pc-windows-msvc").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X86);
}
#[test]
fn try_from_i686_pc_windows_gnu_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("i686-pc-windows-gnu").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X86);
}
#[test]
fn try_from_i686_uwp_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("i686-uwp-windows-msvc").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X86);
}
#[test]
fn try_from_i686_uwp_windows_gnu_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("i686-uwp-windows-gnu").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X86);
}
#[test]
fn try_from_i586_pc_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("i586-pc-windows-msvc").expect("Cfg parsing")).unwrap();
assert_eq!(arch, WixArch::X86);
}
#[test]
fn try_from_aarch64_pc_windows_msvc_is_correct() {
let arch = WixArch::try_from(&Cfg::of("aarch64-pc-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::Arm64);
}
#[test]
fn try_from_aarch64_uwp_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("aarch64-uwp-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::Arm64);
}
#[test]
fn try_from_thumbv7a_pc_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("thumbv7a-pc-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::Arm);
}
#[test]
fn try_from_thumbv7a_uwp_windows_msvc_is_correct() {
let arch =
WixArch::try_from(&Cfg::of("thumbv7a-uwp-windows-msvc").expect("Cfg parsing"))
.unwrap();
assert_eq!(arch, WixArch::Arm);
}
#[test]
fn from_str_is_correct() {
let arch = WixArch::from_str("thumbv7a-uwp-windows-msvc").unwrap();
assert_eq!(arch, WixArch::Arm);
}
}
}