wix/
lib.rs

1// Copyright (C) 2017 Christopher R. Field.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # `wix` Library
16//!
17//! The goal of the cargo-wix project and the `wix` library is to make it easy
18//! to create a Windows installer (msi) for any Rust project. The cargo-wix
19//! project is primarily implemented as a [cargo subcommand], but its core
20//! functionality has been organized into a separate library. Documentation for
21//! the binary and Command Line Interface (CLI) are provided in the module-level
22//! documentation for the [binary] and the `cargo wix --help` command.
23//!
24//! ## Table of Contents
25//!
26//! - [Usage](#usage)
27//! - [Organization](#organization)
28//!
29//! ## Usage
30//!
31//! First, add this to your `Cargo.toml`:
32//!
33//! ```toml
34//! [dependencies]
35//! wix = "0.1"
36//! ```
37//!
38//! Next, if not using Rust 2018 edition, then add this to the `lib.rs` or
39//! `main.rs` file for your project:
40//!
41//! ```ignore
42//! extern crate wix;
43//! ```
44//!
45//! ## Organization
46//!
47//! Each subcommand is organized into a separate module. So, there is a
48//! `create`, `initialize`, `print`, etc. module within the crate. Some of the
49//! modules are in a single Rust source file, while others are organized into
50//! sub-folders. Each module follows the [Builder] design pattern, so there is a
51//! `Builder` and `Execution` struct for each module/subcommand. The `Builder`
52//! struct is used to customize the execution of the subcommand and builds an
53//! `Execution` struct. The `Execution` struct is responsible for actually
54//! executing the subcommand, which generally involves executing a process with
55//! the [`std::process::Command`] struct, but not always. Each method for the
56//! `Builder` struct generally corresponds to a CLI option or argument found in
57//! the [`cargo wix`] subcommand and binary.
58//!
59//! [binary]: ../cargo_wix/index.html
60//! [Builder]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
61//! [cargo subcommand]: https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands
62//! [`cargo wix`]: ../cargo_wix/index.html
63//! [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
64
65pub use crate::templates::Template;
66
67pub mod clean;
68pub mod create;
69pub mod initialize;
70mod licenses;
71pub mod print;
72pub mod purge;
73pub mod sign;
74pub mod stored_path;
75mod templates;
76
77use camino::Utf8Path;
78use log::debug;
79
80use std::convert::TryFrom;
81use std::default::Default;
82use std::env;
83use std::error::Error as StdError;
84use std::ffi::OsStr;
85use std::fmt;
86use std::fmt::Display;
87use std::io::{self, ErrorKind};
88use std::path::{Path, PathBuf};
89use std::str::FromStr;
90
91use cargo_metadata::{Metadata, MetadataCommand, Package};
92
93use rustc_cfg::Cfg;
94
95/// The name of the folder where binaries are typically stored.
96pub const BINARY_FOLDER_NAME: &str = "bin";
97
98/// The file name with extension for a package's manifest.
99pub const CARGO_MANIFEST_FILE: &str = "Cargo.toml";
100
101/// The name of the builder application for a Rust project.
102pub const CARGO: &str = "cargo";
103
104/// The file extension for an executable.
105pub const EXE_FILE_EXTENSION: &str = "exe";
106
107/// The file name without an extension when generating a license.
108pub const LICENSE_FILE_NAME: &str = "License";
109
110/// The file extension for a Windows installer.
111pub const MSI_FILE_EXTENSION: &str = "msi";
112
113/// The file extension for a Rich Text Format (RTF) file.
114pub const RTF_FILE_EXTENSION: &str = "rtf";
115
116/// The name of the signer application from the Windows SDK.
117pub const SIGNTOOL: &str = "signtool";
118
119/// The name of the environment variable to specify the path to the signer
120/// application.
121pub const SIGNTOOL_PATH_KEY: &str = "SIGNTOOL_PATH";
122
123/// The default name of the folder for output from this subcommand.
124pub const WIX: &str = "wix";
125
126/// The application name without the file extension of the compiler for the
127/// Windows installer.
128pub const WIX_COMPILER: &str = "candle";
129
130/// The application name without the file extension of the linker for the
131/// Windows installer.
132pub const WIX_LINKER: &str = "light";
133
134/// The application name without the file extension of the `msiexec` utility.
135pub const MSIEXEC: &str = "msiexec";
136
137/// The file extension for a WiX Toolset object file, which is the output from
138/// the WiX compiler.
139pub const WIX_OBJECT_FILE_EXTENSION: &str = "wixobj";
140
141/// The name of the environment variable created by the WiX Toolset installer
142/// that points to the `bin` folder for the WiX Toolet's compiler (candle.exe)
143/// and linker (light.exe).
144pub const WIX_PATH_KEY: &str = "WIX";
145
146/// The file extension of the WiX Source file, which is the input to the WiX
147/// Toolset compiler.
148pub const WIX_SOURCE_FILE_EXTENSION: &str = "wxs";
149
150/// The default file name for the WiX Source file, which is the input to the WiX
151/// Toolset compiler.
152pub const WIX_SOURCE_FILE_NAME: &str = "main";
153
154/// A specialized [`Result`] type for wix operations.
155///
156/// [`Result`]: https://doc.rust-lang.org/std/result/
157pub type Result<T> = std::result::Result<T, Error>;
158
159fn cargo_toml_file(input: Option<&PathBuf>) -> Result<PathBuf> {
160    let i = match input {
161        Some(i) => i.to_owned(),
162        None => {
163            let mut cwd = env::current_dir()?;
164            cwd.push(CARGO_MANIFEST_FILE);
165            cwd
166        }
167    };
168    if i.exists() {
169        if i.is_file() {
170            if i.file_name() == Some(OsStr::new(CARGO_MANIFEST_FILE)) {
171                Ok(i)
172            } else {
173                Err(Error::not_a_manifest(&i))
174            }
175        } else {
176            Err(Error::not_a_file(&i))
177        }
178    } else {
179        Err(Error::not_found(&i))
180    }
181}
182
183fn description(description: Option<String>, manifest: &Package) -> Option<String> {
184    description.or_else(|| manifest.description.clone())
185}
186
187fn manifest(input: Option<&PathBuf>) -> Result<Metadata> {
188    let cargo_file_path = cargo_toml_file(input)?;
189    debug!("cargo_file_path = {:?}", cargo_file_path);
190    Ok(MetadataCommand::new()
191        .no_deps()
192        .manifest_path(cargo_file_path)
193        .exec()?)
194}
195
196fn package(manifest: &Metadata, package: Option<&str>) -> Result<Package> {
197    let package_id = if let Some(p) = package {
198        manifest
199            .workspace_members
200            .iter()
201            .find(|n| manifest[n].name == p)
202            .ok_or_else(|| Error::Generic(format!("No `{p}` package found in the project")))?
203    } else if manifest.workspace_members.len() == 1 {
204        &manifest.workspace_members[0]
205    } else {
206        // TODO: Replace error with creating installers for all packages in a
207        // workspace. I think this currently means that to create installers for
208        // all packages in workspace, a `cargo wix --package <name>` must be
209        // executed for each workspace member.
210        return Err(Error::Generic(String::from(
211            "Workspace detected. Please pass a package name.",
212        )));
213    };
214    Ok(manifest[package_id].clone())
215}
216
217fn product_name(product_name: Option<&String>, manifest: &Package) -> String {
218    if let Some(p) = product_name {
219        p.to_owned()
220    } else {
221        manifest.name.clone()
222    }
223}
224
225/// The error type for wix-related operations and associated traits.
226///
227/// Errors mostly originate from the dependencies, but custom instances of `Error` can be created
228/// with the `Generic` variant and a message.
229#[derive(Debug)]
230pub enum Error {
231    /// Parsing of Cargo metadata failed.
232    CargoMetadata(cargo_metadata::Error),
233    /// A command operation failed.
234    Command(&'static str, i32, bool),
235    /// A generic or custom error occurred. The message should contain the detailed information.
236    Generic(String),
237    /// An I/O operation failed.
238    Io(io::Error),
239    /// A needed field within the `Cargo.toml` manifest could not be found.
240    Manifest(&'static str),
241    /// An error occurred with rendering the template using the mustache renderer.
242    Mustache(mustache::Error),
243    /// UUID generation or parsing failed.
244    Uuid(uuid::Error),
245    /// Parsing error for a version string or field.
246    Version(semver::Error),
247    /// Parsing the intermediate WiX Object (wixobj) file, which is XML, failed.
248    Xml(sxd_document::parser::Error),
249    /// Evaluation of an XPath expression failed.
250    XPath(sxd_xpath::ExecutionError),
251}
252
253impl Error {
254    /// Gets an error code related to the error.
255    ///
256    /// # Examples
257    ///
258    /// ```rust
259    /// use wix::Error;
260    ///
261    /// let err = Error::from("A generic error");
262    /// assert_ne!(err.code(), 0)
263    /// ```
264    ///
265    /// This is useful as a return, or exit, code for a command line application, where a non-zero
266    /// integer indicates a failure in the application. it can also be used for quickly and easily
267    /// testing equality between two errors.
268    pub fn code(&self) -> i32 {
269        match *self {
270            Error::Command(..) => 1,
271            Error::Generic(..) => 2,
272            Error::Io(..) => 3,
273            Error::Manifest(..) => 4,
274            Error::Mustache(..) => 5,
275            Error::Uuid(..) => 6,
276            Error::Version(..) => 7,
277            Error::Xml(..) => 8,
278            Error::XPath(..) => 9,
279            Error::CargoMetadata(..) => 10,
280        }
281    }
282
283    /// Creates a new `Error` from a [std::io::Error] with the
284    /// [std::io::ErrorKind::AlreadyExists] variant.
285    ///
286    /// # Examples
287    ///
288    /// ```rust
289    /// use std::io;
290    /// use camino::Utf8Path;
291    /// use wix::Error;
292    ///
293    /// let path = Utf8Path::new("C:\\");
294    /// let expected = Error::Io(io::Error::new(
295    ///     io::ErrorKind::AlreadyExists,
296    ///     path.to_string()
297    /// ));
298    /// assert_eq!(expected, Error::already_exists(path));
299    /// ```
300    ///
301    /// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
302    /// [std::io::ErrorKind::AlreadyExists]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
303    pub fn already_exists(p: &Utf8Path) -> Self {
304        io::Error::new(ErrorKind::AlreadyExists, p.to_string()).into()
305    }
306
307    /// Creates a new `Error` from a [std::io::Error] with the
308    /// [std::io::ErrorKind::NotFound] variant.
309    ///
310    /// # Examples
311    ///
312    /// ```rust
313    /// use std::io;
314    /// use std::path::Path;
315    /// use wix::Error;
316    ///
317    /// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
318    /// let expected = Error::Io(io::Error::new(
319    ///     io::ErrorKind::NotFound,
320    ///     path.display().to_string()
321    /// ));
322    /// assert_eq!(expected, Error::not_found(path));
323    /// ```
324    ///
325    /// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
326    /// [std::io::ErrorKind::NotFound]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
327    pub fn not_found(p: &Path) -> Self {
328        io::Error::new(ErrorKind::NotFound, p.display().to_string()).into()
329    }
330
331    /// Creates a new `Error` from a [std::io::Error] with the
332    /// [std::io::ErrorKind::InvalidInput] variant if a path is not a file.
333    ///
334    /// # Examples
335    ///
336    /// ```rust
337    /// use std::io;
338    /// use std::path::Path;
339    /// use wix::Error;
340    ///
341    /// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
342    /// let expected = Error::Io(io::Error::new(
343    ///     io::ErrorKind::InvalidInput,
344    ///     format!("The '{}' path is not a file.", path.display())
345    /// ));
346    /// assert_eq!(expected, Error::not_a_file(path));
347    /// ```
348    ///
349    /// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
350    /// [std::io::ErrorKind::InvalidInput]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
351    pub fn not_a_file(p: &Path) -> Self {
352        io::Error::new(
353            ErrorKind::InvalidInput,
354            format!("The '{}' path is not a file.", p.display()),
355        )
356        .into()
357    }
358
359    /// Creates a new `Error` from a [std::io::Error] with the
360    /// [std::io::ErrorKind::InvalidInput] variant if a path is not to a
361    /// `Cargo.toml` file.
362    ///
363    /// # Examples
364    ///
365    /// ```rust
366    /// use std::io;
367    /// use std::path::Path;
368    /// use wix::Error;
369    ///
370    /// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
371    /// let expected = Error::Io(io::Error::new(
372    ///     io::ErrorKind::InvalidInput,
373    ///     format!(
374    ///         "The '{}' path does not appear to be to a 'Cargo.toml' file.",
375    ///         path.display(),
376    ///     ),
377    /// ));
378    /// assert_eq!(expected, Error::not_a_manifest(path));
379    /// ```
380    ///
381    /// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
382    /// [std::io::ErrorKind::InvalidInput]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
383    pub fn not_a_manifest(p: &Path) -> Self {
384        io::Error::new(
385            ErrorKind::InvalidInput,
386            format!(
387                "The '{}' path does not appear to be to a '{}' file.",
388                p.display(),
389                CARGO_MANIFEST_FILE
390            ),
391        )
392        .into()
393    }
394
395    /// Extracts a short, single word representation of the error.
396    ///
397    /// The `std::error::Error::description` method is "soft-deprecated"
398    /// according to the Rust stdlib documentation. It is recommended to use the
399    /// `std::fmt::Display` implementation for a "description" string. However,
400    /// there is already a `std::fmt::Display` implementation for this error
401    /// type, and it is nice to have a short, single word representation for
402    /// nicely formatting errors to humans. This method maintains the error
403    /// message formatting.
404    pub fn as_str(&self) -> &str {
405        match *self {
406            Error::CargoMetadata(..) => "CargoMetadata",
407            Error::Command(..) => "Command",
408            Error::Generic(..) => "Generic",
409            Error::Io(..) => "Io",
410            Error::Manifest(..) => "Manifest",
411            Error::Mustache(..) => "Mustache",
412            Error::Uuid(..) => "UUID",
413            Error::Version(..) => "Version",
414            Error::Xml(..) => "XML",
415            Error::XPath(..) => "XPath",
416        }
417    }
418}
419
420impl StdError for Error {
421    fn description(&self) -> &str {
422        self.as_str()
423    }
424
425    fn source(&self) -> Option<&(dyn StdError + 'static)> {
426        match *self {
427            Error::CargoMetadata(ref err) => Some(err),
428            Error::Io(ref err) => Some(err),
429            Error::Mustache(ref err) => Some(err),
430            Error::Uuid(ref err) => Some(err),
431            Error::Version(ref err) => Some(err),
432            Error::Xml(ref err) => Some(err),
433            Error::XPath(ref err) => Some(err),
434            _ => None,
435        }
436    }
437}
438
439impl fmt::Display for Error {
440    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441        match *self {
442            Error::CargoMetadata(ref err) => err.fmt(f),
443            Error::Command(ref command, ref code, captured_output) => {
444                if captured_output {
445                    write!(
446                        f,
447                        "The '{command}' application failed with exit code = {code}. Consider using the \
448                         '--nocapture' flag to obtain more information."
449                    )
450                } else {
451                    write!(
452                        f,
453                        "The '{command}' application failed with exit code = {code}"
454                    )
455                }
456            }
457            Error::Generic(ref msg) => msg.fmt(f),
458            Error::Io(ref err) => match err.kind() {
459                ErrorKind::AlreadyExists => {
460                    if let Some(path) = err.get_ref() {
461                        write!(f, "The '{path}' file already exists. Use the '--force' flag to overwrite the contents.")
462                    } else {
463                        err.fmt(f)
464                    }
465                }
466                ErrorKind::NotFound => {
467                    if let Some(path) = err.get_ref() {
468                        write!(f, "The '{path}' path does not exist")
469                    } else {
470                        err.fmt(f)
471                    }
472                }
473                _ => err.fmt(f),
474            },
475            Error::Manifest(ref var) => write!(
476                f,
477                "No '{var}' field found in the package's manifest (Cargo.toml)"
478            ),
479            Error::Mustache(ref err) => err.fmt(f),
480            Error::Uuid(ref err) => err.fmt(f),
481            Error::Version(ref err) => err.fmt(f),
482            Error::Xml(ref err) => err.fmt(f),
483            Error::XPath(ref err) => err.fmt(f),
484        }
485    }
486}
487
488impl PartialEq for Error {
489    fn eq(&self, other: &Error) -> bool {
490        self.code() == other.code()
491    }
492}
493
494impl<'a> From<&'a str> for Error {
495    fn from(s: &str) -> Self {
496        Error::Generic(s.to_string())
497    }
498}
499
500impl From<cargo_metadata::Error> for Error {
501    fn from(err: cargo_metadata::Error) -> Self {
502        Error::CargoMetadata(err)
503    }
504}
505
506impl From<io::Error> for Error {
507    fn from(err: io::Error) -> Self {
508        Error::Io(err)
509    }
510}
511
512impl From<mustache::Error> for Error {
513    fn from(err: mustache::Error) -> Self {
514        Error::Mustache(err)
515    }
516}
517
518impl From<semver::Error> for Error {
519    fn from(err: semver::Error) -> Self {
520        Error::Version(err)
521    }
522}
523
524impl From<std::path::StripPrefixError> for Error {
525    fn from(err: std::path::StripPrefixError) -> Self {
526        Error::Generic(err.to_string())
527    }
528}
529
530impl From<sxd_document::parser::Error> for Error {
531    fn from(err: sxd_document::parser::Error) -> Self {
532        Error::Xml(err)
533    }
534}
535
536impl From<sxd_xpath::ExecutionError> for Error {
537    fn from(err: sxd_xpath::ExecutionError) -> Self {
538        Error::XPath(err)
539    }
540}
541
542impl From<uuid::Error> for Error {
543    fn from(err: uuid::Error) -> Self {
544        Error::Uuid(err)
545    }
546}
547
548/// The different architectures supported by the WiX Toolset.
549///
550/// These are also the valid values for the `-arch` option to the WiX compiler
551/// (candle.exe).
552#[derive(Debug, Clone, PartialEq, Eq)]
553pub enum WixArch {
554    /// The x86 32-bit architecture.
555    X86,
556    /// The x86_64 or AMD64 64-bit architecture.
557    X64,
558    /// The ARM 32-bit architecture.
559    Arm,
560    /// The ARM 64-bit architecture.
561    Arm64,
562}
563
564impl Display for WixArch {
565    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
566        match &self {
567            Self::X86 => write!(f, "x86"),
568            Self::X64 => write!(f, "x64"),
569            Self::Arm => write!(f, "arm"),
570            Self::Arm64 => write!(f, "arm64"),
571        }
572    }
573}
574
575impl TryFrom<&Cfg> for WixArch {
576    type Error = crate::Error;
577
578    fn try_from(c: &Cfg) -> std::result::Result<Self, Self::Error> {
579        match &*c.target_arch {
580            "x86" => Ok(Self::X86),
581            "x86_64" => Ok(Self::X64),
582            "aarch64" => Ok(Self::Arm64),
583            "thumbv7a" => Ok(Self::Arm),
584            a => {
585                if a.starts_with("arm") {
586                    Ok(Self::Arm)
587                } else {
588                    Err(Error::Generic(format!(
589                        "Unsupported target architecture: {a}"
590                    )))
591                }
592            }
593        }
594    }
595}
596
597impl FromStr for WixArch {
598    type Err = crate::Error;
599
600    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
601        Self::try_from(&Cfg::of(s).map_err(|e| Error::Generic(e.to_string()))?)
602    }
603}
604
605/// The aliases for the URLs to different Microsoft Authenticode timestamp servers.
606#[derive(Debug, Clone, PartialEq, Eq)]
607pub enum TimestampServer {
608    /// A URL to a timestamp server.
609    Custom(String),
610    /// The alias for the Comodo timestamp server.
611    Comodo,
612    /// The alias for the Verisign timestamp server.
613    Verisign,
614}
615
616impl TimestampServer {
617    /// Gets the URL of the timestamp server for an alias.
618    ///
619    /// # Examples
620    ///
621    /// ```rust
622    /// use std::str::FromStr;
623    /// use wix::TimestampServer;
624    ///
625    /// assert_eq!(
626    ///     TimestampServer::from_str("http://www.example.com").unwrap().url(),
627    ///     "http://www.example.com"
628    /// );
629    /// assert_eq!(
630    ///     TimestampServer::Comodo.url(),
631    ///     "http://timestamp.comodoca.com/"
632    /// );
633    /// assert_eq!(
634    ///     TimestampServer::Verisign.url(),
635    ///     "http://timestamp.verisign.com/scripts/timstamp.dll"
636    /// );
637    /// ```
638    pub fn url(&self) -> &str {
639        match *self {
640            TimestampServer::Custom(ref url) => url,
641            TimestampServer::Comodo => "http://timestamp.comodoca.com/",
642            TimestampServer::Verisign => "http://timestamp.verisign.com/scripts/timstamp.dll",
643        }
644    }
645}
646
647impl fmt::Display for TimestampServer {
648    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
649        write!(f, "{}", self.url())
650    }
651}
652
653impl FromStr for TimestampServer {
654    type Err = Error;
655
656    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
657        match s.to_lowercase().trim() {
658            "comodo" => Ok(TimestampServer::Comodo),
659            "verisign" => Ok(TimestampServer::Verisign),
660            u => Ok(TimestampServer::Custom(String::from(u))),
661        }
662    }
663}
664
665/// The various culture codes for localization.
666///
667/// These are taken from the table in the [WixUI localization] documentation.
668///
669/// [WixUI localization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
670#[derive(Clone, Debug, Default, PartialEq, Eq)]
671pub enum Cultures {
672    /// Arabic, Saudi Arabia
673    ArSa,
674    /// Bulgarian, Bulgaria
675    BgBg,
676    /// Catalan, Spain
677    CaEs,
678    /// Croatian, Croatia
679    HrHr,
680    /// Czech, Czech Republic
681    CsCz,
682    /// Danish, Denmark
683    DaDk,
684    /// Dutch, Netherlands
685    NlNl,
686    /// English, United States
687    #[default]
688    EnUs,
689    /// Estonian, Estonia
690    EtEe,
691    /// Finnish, Finland
692    FiFi,
693    /// French, France
694    FrFr,
695    /// German, Germany
696    DeDe,
697    /// Greek, Greece
698    ElGr,
699    /// Hebrew, Israel
700    HeIl,
701    /// Hindi, India
702    HiIn,
703    /// Hungarian, Hungary
704    HuHu,
705    /// Italian, Italy
706    ItIt,
707    /// Japanese, Japan
708    JaJp,
709    /// Kazakh, Kazakhstan
710    KkKz,
711    /// Korean, Korea
712    KoKr,
713    /// Latvian, Latvia
714    LvLv,
715    /// Lithuanian, Lithuania
716    LtLt,
717    /// Norwegian, Norway
718    NbNo,
719    /// Polish, Poland
720    PlPl,
721    /// Portuguese, Brazil
722    PtBr,
723    /// Portuguese, Portugal
724    PtPt,
725    /// Romanian, Romania
726    RoRo,
727    /// Russian, Russian
728    RuRu,
729    /// Serbian, Serbia and Montenegro
730    SrLatnCs,
731    /// Simplified Chinese, China
732    ZhCn,
733    /// Slovak, Slovak Republic
734    SkSk,
735    /// Solvenian, Solvenia
736    SlSi,
737    /// Spanish, Spain
738    EsEs,
739    /// Swedish, Sweden
740    SvSe,
741    /// Thai, Thailand
742    ThTh,
743    /// Traditional Chinese, Hong Kong SAR
744    ZhHk,
745    /// Traditional Chinese, Taiwan
746    ZhTw,
747    /// Turkish, Turkey
748    TrTr,
749    /// Ukrainian, Ukraine
750    UkUa,
751}
752
753impl Cultures {
754    /// The language of the culture code.
755    ///
756    /// # Examples
757    ///
758    /// ```rust
759    /// use wix::Cultures;
760    ///
761    /// assert_eq!(Cultures::ArSa.language(), "Arabic");
762    /// assert_eq!(Cultures::BgBg.language(), "Bulgarian");
763    /// assert_eq!(Cultures::CaEs.language(), "Catalan");
764    /// assert_eq!(Cultures::HrHr.language(), "Croatian");
765    /// assert_eq!(Cultures::CsCz.language(), "Czech");
766    /// assert_eq!(Cultures::DaDk.language(), "Danish");
767    /// assert_eq!(Cultures::NlNl.language(), "Dutch");
768    /// assert_eq!(Cultures::EnUs.language(), "English");
769    /// assert_eq!(Cultures::EtEe.language(), "Estonian");
770    /// assert_eq!(Cultures::FiFi.language(), "Finnish");
771    /// assert_eq!(Cultures::FrFr.language(), "French");
772    /// assert_eq!(Cultures::DeDe.language(), "German");
773    /// assert_eq!(Cultures::ElGr.language(), "Greek");
774    /// assert_eq!(Cultures::HeIl.language(), "Hebrew");
775    /// assert_eq!(Cultures::HiIn.language(), "Hindi");
776    /// assert_eq!(Cultures::HuHu.language(), "Hungarian");
777    /// assert_eq!(Cultures::ItIt.language(), "Italian");
778    /// assert_eq!(Cultures::JaJp.language(), "Japanese");
779    /// assert_eq!(Cultures::KkKz.language(), "Kazakh");
780    /// assert_eq!(Cultures::KoKr.language(), "Korean");
781    /// assert_eq!(Cultures::LvLv.language(), "Latvian");
782    /// assert_eq!(Cultures::LtLt.language(), "Lithuanian");
783    /// assert_eq!(Cultures::NbNo.language(), "Norwegian");
784    /// assert_eq!(Cultures::PlPl.language(), "Polish");
785    /// assert_eq!(Cultures::PtBr.language(), "Portuguese");
786    /// assert_eq!(Cultures::PtPt.language(), "Portuguese");
787    /// assert_eq!(Cultures::RoRo.language(), "Romanian");
788    /// assert_eq!(Cultures::RuRu.language(), "Russian");
789    /// assert_eq!(Cultures::SrLatnCs.language(), "Serbian (Latin)");
790    /// assert_eq!(Cultures::ZhCn.language(), "Simplified Chinese");
791    /// assert_eq!(Cultures::SkSk.language(), "Slovak");
792    /// assert_eq!(Cultures::SlSi.language(), "Slovenian");
793    /// assert_eq!(Cultures::EsEs.language(), "Spanish");
794    /// assert_eq!(Cultures::SvSe.language(), "Swedish");
795    /// assert_eq!(Cultures::ThTh.language(), "Thai");
796    /// assert_eq!(Cultures::ZhHk.language(), "Traditional Chinese");
797    /// assert_eq!(Cultures::ZhTw.language(), "Traditional Chinese");
798    /// assert_eq!(Cultures::TrTr.language(), "Turkish");
799    /// assert_eq!(Cultures::UkUa.language(), "Ukrainian");
800    /// ```
801    pub fn language(&self) -> &'static str {
802        match *self {
803            Cultures::ArSa => "Arabic",
804            Cultures::BgBg => "Bulgarian",
805            Cultures::CaEs => "Catalan",
806            Cultures::HrHr => "Croatian",
807            Cultures::CsCz => "Czech",
808            Cultures::DaDk => "Danish",
809            Cultures::NlNl => "Dutch",
810            Cultures::EnUs => "English",
811            Cultures::EtEe => "Estonian",
812            Cultures::FiFi => "Finnish",
813            Cultures::FrFr => "French",
814            Cultures::DeDe => "German",
815            Cultures::ElGr => "Greek",
816            Cultures::HeIl => "Hebrew",
817            Cultures::HiIn => "Hindi",
818            Cultures::HuHu => "Hungarian",
819            Cultures::ItIt => "Italian",
820            Cultures::JaJp => "Japanese",
821            Cultures::KkKz => "Kazakh",
822            Cultures::KoKr => "Korean",
823            Cultures::LvLv => "Latvian",
824            Cultures::LtLt => "Lithuanian",
825            Cultures::NbNo => "Norwegian",
826            Cultures::PlPl => "Polish",
827            Cultures::PtBr => "Portuguese",
828            Cultures::PtPt => "Portuguese",
829            Cultures::RoRo => "Romanian",
830            Cultures::RuRu => "Russian",
831            Cultures::SrLatnCs => "Serbian (Latin)",
832            Cultures::ZhCn => "Simplified Chinese",
833            Cultures::SkSk => "Slovak",
834            Cultures::SlSi => "Slovenian",
835            Cultures::EsEs => "Spanish",
836            Cultures::SvSe => "Swedish",
837            Cultures::ThTh => "Thai",
838            Cultures::ZhHk => "Traditional Chinese",
839            Cultures::ZhTw => "Traditional Chinese",
840            Cultures::TrTr => "Turkish",
841            Cultures::UkUa => "Ukrainian",
842        }
843    }
844
845    /// The location of the culture component, typically the country that speaks the language.
846    ///
847    /// # Examples
848    ///
849    /// ```rust
850    /// use wix::Cultures;
851    ///
852    /// assert_eq!(Cultures::ArSa.location(), "Saudi Arabia");
853    /// assert_eq!(Cultures::BgBg.location(), "Bulgaria");
854    /// assert_eq!(Cultures::CaEs.location(), "Spain");
855    /// assert_eq!(Cultures::HrHr.location(), "Croatia");
856    /// assert_eq!(Cultures::CsCz.location(), "Czech Republic");
857    /// assert_eq!(Cultures::DaDk.location(), "Denmark");
858    /// assert_eq!(Cultures::NlNl.location(), "Netherlands");
859    /// assert_eq!(Cultures::EnUs.location(), "United States");
860    /// assert_eq!(Cultures::EtEe.location(), "Estonia");
861    /// assert_eq!(Cultures::FiFi.location(), "Finland");
862    /// assert_eq!(Cultures::FrFr.location(), "France");
863    /// assert_eq!(Cultures::DeDe.location(), "Germany");
864    /// assert_eq!(Cultures::ElGr.location(), "Greece");
865    /// assert_eq!(Cultures::HeIl.location(), "Israel");
866    /// assert_eq!(Cultures::HiIn.location(), "India");
867    /// assert_eq!(Cultures::HuHu.location(), "Hungary");
868    /// assert_eq!(Cultures::ItIt.location(), "Italy");
869    /// assert_eq!(Cultures::JaJp.location(), "Japan");
870    /// assert_eq!(Cultures::KkKz.location(), "Kazakhstan");
871    /// assert_eq!(Cultures::KoKr.location(), "Korea");
872    /// assert_eq!(Cultures::LvLv.location(), "Latvia");
873    /// assert_eq!(Cultures::LtLt.location(), "Lithuania");
874    /// assert_eq!(Cultures::NbNo.location(), "Norway");
875    /// assert_eq!(Cultures::PlPl.location(), "Poland");
876    /// assert_eq!(Cultures::PtBr.location(), "Brazil");
877    /// assert_eq!(Cultures::PtPt.location(), "Portugal");
878    /// assert_eq!(Cultures::RoRo.location(), "Romania");
879    /// assert_eq!(Cultures::RuRu.location(), "Russia");
880    /// assert_eq!(Cultures::SrLatnCs.location(), "Serbia and Montenegro");
881    /// assert_eq!(Cultures::ZhCn.location(), "China");
882    /// assert_eq!(Cultures::SkSk.location(), "Slovak Republic");
883    /// assert_eq!(Cultures::SlSi.location(), "Solvenia");
884    /// assert_eq!(Cultures::EsEs.location(), "Spain");
885    /// assert_eq!(Cultures::SvSe.location(), "Sweden");
886    /// assert_eq!(Cultures::ThTh.location(), "Thailand");
887    /// assert_eq!(Cultures::ZhHk.location(), "Hong Kong SAR");
888    /// assert_eq!(Cultures::ZhTw.location(), "Taiwan");
889    /// assert_eq!(Cultures::TrTr.location(), "Turkey");
890    /// assert_eq!(Cultures::UkUa.location(), "Ukraine");
891    /// ```
892    pub fn location(&self) -> &'static str {
893        match *self {
894            Cultures::ArSa => "Saudi Arabia",
895            Cultures::BgBg => "Bulgaria",
896            Cultures::CaEs => "Spain",
897            Cultures::HrHr => "Croatia",
898            Cultures::CsCz => "Czech Republic",
899            Cultures::DaDk => "Denmark",
900            Cultures::NlNl => "Netherlands",
901            Cultures::EnUs => "United States",
902            Cultures::EtEe => "Estonia",
903            Cultures::FiFi => "Finland",
904            Cultures::FrFr => "France",
905            Cultures::DeDe => "Germany",
906            Cultures::ElGr => "Greece",
907            Cultures::HeIl => "Israel",
908            Cultures::HiIn => "India",
909            Cultures::HuHu => "Hungary",
910            Cultures::ItIt => "Italy",
911            Cultures::JaJp => "Japan",
912            Cultures::KkKz => "Kazakhstan",
913            Cultures::KoKr => "Korea",
914            Cultures::LvLv => "Latvia",
915            Cultures::LtLt => "Lithuania",
916            Cultures::NbNo => "Norway",
917            Cultures::PlPl => "Poland",
918            Cultures::PtBr => "Brazil",
919            Cultures::PtPt => "Portugal",
920            Cultures::RoRo => "Romania",
921            Cultures::RuRu => "Russia",
922            Cultures::SrLatnCs => "Serbia and Montenegro",
923            Cultures::ZhCn => "China",
924            Cultures::SkSk => "Slovak Republic",
925            Cultures::SlSi => "Solvenia",
926            Cultures::EsEs => "Spain",
927            Cultures::SvSe => "Sweden",
928            Cultures::ThTh => "Thailand",
929            Cultures::ZhHk => "Hong Kong SAR",
930            Cultures::ZhTw => "Taiwan",
931            Cultures::TrTr => "Turkey",
932            Cultures::UkUa => "Ukraine",
933        }
934    }
935}
936
937impl fmt::Display for Cultures {
938    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
939        match *self {
940            Cultures::ArSa => write!(f, "ar-SA"),
941            Cultures::BgBg => write!(f, "bg-BG"),
942            Cultures::CaEs => write!(f, "ca-ES"),
943            Cultures::HrHr => write!(f, "hr-HR"),
944            Cultures::CsCz => write!(f, "cs-CZ"),
945            Cultures::DaDk => write!(f, "da-DK"),
946            Cultures::NlNl => write!(f, "nl-NL"),
947            Cultures::EnUs => write!(f, "en-US"),
948            Cultures::EtEe => write!(f, "et-EE"),
949            Cultures::FiFi => write!(f, "fi-FI"),
950            Cultures::FrFr => write!(f, "fr-FR"),
951            Cultures::DeDe => write!(f, "de-DE"),
952            Cultures::ElGr => write!(f, "el-GR"),
953            Cultures::HeIl => write!(f, "he-IL"),
954            Cultures::HiIn => write!(f, "hi-IN"),
955            Cultures::HuHu => write!(f, "hu-HU"),
956            Cultures::ItIt => write!(f, "it-IT"),
957            Cultures::JaJp => write!(f, "ja-JP"),
958            Cultures::KkKz => write!(f, "kk-KZ"),
959            Cultures::KoKr => write!(f, "ko-KR"),
960            Cultures::LvLv => write!(f, "lv-LV"),
961            Cultures::LtLt => write!(f, "lt-LT"),
962            Cultures::NbNo => write!(f, "nb-NO"),
963            Cultures::PlPl => write!(f, "pl-PL"),
964            Cultures::PtBr => write!(f, "pt-BR"),
965            Cultures::PtPt => write!(f, "pt-PT"),
966            Cultures::RoRo => write!(f, "ro-RO"),
967            Cultures::RuRu => write!(f, "ru-RU"),
968            Cultures::SrLatnCs => write!(f, "sr-Latn-CS"),
969            Cultures::ZhCn => write!(f, "zh-CN"),
970            Cultures::SkSk => write!(f, "sk-SK"),
971            Cultures::SlSi => write!(f, "sl-SI"),
972            Cultures::EsEs => write!(f, "es-ES"),
973            Cultures::SvSe => write!(f, "sv-SE"),
974            Cultures::ThTh => write!(f, "th-TH"),
975            Cultures::ZhHk => write!(f, "zh-HK"),
976            Cultures::ZhTw => write!(f, "zh-TW"),
977            Cultures::TrTr => write!(f, "tr-TR"),
978            Cultures::UkUa => write!(f, "uk-UA"),
979        }
980    }
981}
982
983impl FromStr for Cultures {
984    type Err = Error;
985
986    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
987        match s.to_lowercase().trim() {
988            "ar-sa" => Ok(Cultures::ArSa),
989            "bg-bg" => Ok(Cultures::BgBg),
990            "ca-es" => Ok(Cultures::CaEs),
991            "hr-hr" => Ok(Cultures::HrHr),
992            "cs-cz" => Ok(Cultures::CsCz),
993            "da-dk" => Ok(Cultures::DaDk),
994            "nl-nl" => Ok(Cultures::NlNl),
995            "en-us" => Ok(Cultures::EnUs),
996            "et-ee" => Ok(Cultures::EtEe),
997            "fi-fi" => Ok(Cultures::FiFi),
998            "fr-fr" => Ok(Cultures::FrFr),
999            "de-de" => Ok(Cultures::DeDe),
1000            "el-gr" => Ok(Cultures::ElGr),
1001            "he-il" => Ok(Cultures::HeIl),
1002            "hi-in" => Ok(Cultures::HiIn),
1003            "hu-hu" => Ok(Cultures::HuHu),
1004            "it-it" => Ok(Cultures::ItIt),
1005            "ja-jp" => Ok(Cultures::JaJp),
1006            "kk-kz" => Ok(Cultures::KkKz),
1007            "ko-kr" => Ok(Cultures::KoKr),
1008            "lv-lv" => Ok(Cultures::LvLv),
1009            "lt-lt" => Ok(Cultures::LtLt),
1010            "nb-no" => Ok(Cultures::NbNo),
1011            "pl-pl" => Ok(Cultures::PlPl),
1012            "pt-br" => Ok(Cultures::PtBr),
1013            "pt-pt" => Ok(Cultures::PtPt),
1014            "ro-ro" => Ok(Cultures::RoRo),
1015            "ru-ru" => Ok(Cultures::RuRu),
1016            "sr-latn-cs" => Ok(Cultures::SrLatnCs),
1017            "zh-cn" => Ok(Cultures::ZhCn),
1018            "sk-sk" => Ok(Cultures::SkSk),
1019            "sl-si" => Ok(Cultures::SlSi),
1020            "es-es" => Ok(Cultures::EsEs),
1021            "sv-se" => Ok(Cultures::SvSe),
1022            "th-th" => Ok(Cultures::ThTh),
1023            "zh-hk" => Ok(Cultures::ZhHk),
1024            "zh-tw" => Ok(Cultures::ZhTw),
1025            "tr-tr" => Ok(Cultures::TrTr),
1026            "uk-ua" => Ok(Cultures::UkUa),
1027            e => Err(Error::Generic(format!("Unknown '{e}' culture"))),
1028        }
1029    }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034    use super::*;
1035    use assert_fs::TempDir;
1036    use std::env;
1037    use std::fs;
1038
1039    /// Create a simple project with the provided TOML.
1040    pub fn setup_project(toml: &str) -> TempDir {
1041        pub const PERSIST_VAR_NAME: &str = "CARGO_WIX_TEST_PERSIST";
1042
1043        let temp_dir = TempDir::new().unwrap();
1044        fs::write(temp_dir.path().join("Cargo.toml"), toml).unwrap();
1045        fs::create_dir(temp_dir.path().join("src")).unwrap();
1046        fs::write(temp_dir.path().join("src").join("main.rs"), "fn main() {}").unwrap();
1047
1048        temp_dir.into_persistent_if(env::var(PERSIST_VAR_NAME).is_ok())
1049    }
1050
1051    mod culture {
1052        use super::*;
1053
1054        #[test]
1055        fn from_str_is_correct_for_dash_russian() {
1056            assert_eq!(Cultures::from_str("ru-ru"), Ok(Cultures::RuRu));
1057        }
1058
1059        #[test]
1060        #[should_panic]
1061        fn from_str_fails_for_underscore_russian() {
1062            Cultures::from_str("ru_ru").unwrap();
1063        }
1064
1065        #[test]
1066        fn display_is_correct_for_russian() {
1067            assert_eq!(format!("{}", Cultures::RuRu), String::from("ru-RU"));
1068        }
1069
1070        #[test]
1071        fn from_str_is_correct_for_lowercase_slovak() {
1072            assert_eq!(Cultures::from_str("sk-sk"), Ok(Cultures::SkSk));
1073        }
1074
1075        #[test]
1076        fn from_str_is_correct_for_uppercase_slovak() {
1077            assert_eq!(Cultures::from_str("sk-SK"), Ok(Cultures::SkSk));
1078        }
1079
1080        #[test]
1081        #[should_panic]
1082        fn from_str_fails_for_underscore_slovak() {
1083            Cultures::from_str("sk_sk").unwrap();
1084        }
1085
1086        #[test]
1087        fn display_is_correct_for_slovak() {
1088            assert_eq!(format!("{}", Cultures::SkSk), String::from("sk-SK"));
1089        }
1090    }
1091
1092    mod wix_arch {
1093        use super::*;
1094
1095        #[test]
1096        fn try_from_x86_64_pc_windows_msvc_is_correct() {
1097            let arch = WixArch::try_from(&Cfg::of("x86_64-pc-windows-msvc").expect("Cfg parsing"))
1098                .unwrap();
1099            assert_eq!(arch, WixArch::X64);
1100        }
1101
1102        #[test]
1103        fn try_from_x86_64_pc_windows_gnu_is_correct() {
1104            let arch =
1105                WixArch::try_from(&Cfg::of("x86_64-pc-windows-gnu").expect("Cfg parsing")).unwrap();
1106            assert_eq!(arch, WixArch::X64);
1107        }
1108
1109        #[test]
1110        fn try_from_x86_64_uwp_windows_msvc_is_correct() {
1111            let arch = WixArch::try_from(&Cfg::of("x86_64-uwp-windows-msvc").expect("Cfg parsing"))
1112                .unwrap();
1113            assert_eq!(arch, WixArch::X64);
1114        }
1115
1116        #[test]
1117        fn try_from_x86_64_uwp_windows_gnu_is_correct() {
1118            let arch = WixArch::try_from(&Cfg::of("x86_64-uwp-windows-gnu").expect("Cfg parsing"))
1119                .unwrap();
1120            assert_eq!(arch, WixArch::X64);
1121        }
1122
1123        #[test]
1124        fn try_from_i686_pc_windows_msvc_is_correct() {
1125            let arch =
1126                WixArch::try_from(&Cfg::of("i686-pc-windows-msvc").expect("Cfg parsing")).unwrap();
1127            assert_eq!(arch, WixArch::X86);
1128        }
1129
1130        #[test]
1131        fn try_from_i686_pc_windows_gnu_is_correct() {
1132            let arch =
1133                WixArch::try_from(&Cfg::of("i686-pc-windows-gnu").expect("Cfg parsing")).unwrap();
1134            assert_eq!(arch, WixArch::X86);
1135        }
1136
1137        #[test]
1138        fn try_from_i686_uwp_windows_msvc_is_correct() {
1139            let arch =
1140                WixArch::try_from(&Cfg::of("i686-uwp-windows-msvc").expect("Cfg parsing")).unwrap();
1141            assert_eq!(arch, WixArch::X86);
1142        }
1143
1144        #[test]
1145        fn try_from_i686_uwp_windows_gnu_is_correct() {
1146            let arch =
1147                WixArch::try_from(&Cfg::of("i686-uwp-windows-gnu").expect("Cfg parsing")).unwrap();
1148            assert_eq!(arch, WixArch::X86);
1149        }
1150
1151        #[test]
1152        fn try_from_i586_pc_windows_msvc_is_correct() {
1153            let arch =
1154                WixArch::try_from(&Cfg::of("i586-pc-windows-msvc").expect("Cfg parsing")).unwrap();
1155            assert_eq!(arch, WixArch::X86);
1156        }
1157
1158        #[test]
1159        fn try_from_aarch64_pc_windows_msvc_is_correct() {
1160            let arch = WixArch::try_from(&Cfg::of("aarch64-pc-windows-msvc").expect("Cfg parsing"))
1161                .unwrap();
1162            assert_eq!(arch, WixArch::Arm64);
1163        }
1164
1165        #[test]
1166        fn try_from_aarch64_uwp_windows_msvc_is_correct() {
1167            let arch =
1168                WixArch::try_from(&Cfg::of("aarch64-uwp-windows-msvc").expect("Cfg parsing"))
1169                    .unwrap();
1170            assert_eq!(arch, WixArch::Arm64);
1171        }
1172
1173        #[test]
1174        fn try_from_thumbv7a_pc_windows_msvc_is_correct() {
1175            let arch =
1176                WixArch::try_from(&Cfg::of("thumbv7a-pc-windows-msvc").expect("Cfg parsing"))
1177                    .unwrap();
1178            assert_eq!(arch, WixArch::Arm);
1179        }
1180
1181        #[test]
1182        fn try_from_thumbv7a_uwp_windows_msvc_is_correct() {
1183            let arch =
1184                WixArch::try_from(&Cfg::of("thumbv7a-uwp-windows-msvc").expect("Cfg parsing"))
1185                    .unwrap();
1186            assert_eq!(arch, WixArch::Arm);
1187        }
1188
1189        #[test]
1190        fn from_str_is_correct() {
1191            let arch = WixArch::from_str("thumbv7a-uwp-windows-msvc").unwrap();
1192            assert_eq!(arch, WixArch::Arm);
1193        }
1194    }
1195}