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}