Showing preview only (742K chars total). Download the full file or copy to clipboard to get everything.
Repository: volks73/cargo-wix
Branch: main
Commit: 041cc3c624ab
Files: 45
Total size: 717.4 KB
Directory structure:
gitextract_m7khl9b_/
├── .cargo/
│ └── config.toml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── src/
│ ├── clean.rs
│ ├── create.rs
│ ├── initialize.rs
│ ├── lib.rs
│ ├── licenses.rs
│ ├── main.rs
│ ├── migrate.rs
│ ├── print/
│ │ ├── license.rs
│ │ ├── mod.rs
│ │ └── wxs.rs
│ ├── purge.rs
│ ├── sign.rs
│ ├── stored_path.rs
│ ├── templates/
│ │ ├── Apache-2.0.rtf.mustache
│ │ ├── GPL-3.0.rtf.mustache
│ │ ├── MIT.rtf.mustache
│ │ ├── main.v3.wxs.mustache
│ │ ├── main.v4.wxs.mustache
│ │ └── mod.rs
│ └── toolset/
│ ├── ext.rs
│ ├── includes.rs
│ ├── mod.rs
│ ├── project.rs
│ ├── source.rs
│ └── test.rs
├── tests/
│ ├── common/
│ │ ├── mod.rs
│ │ ├── one.wxs
│ │ ├── post_v4/
│ │ │ ├── fragment.wxs
│ │ │ └── main.wxs
│ │ ├── pre_v4/
│ │ │ └── main.wxs
│ │ ├── three.wxs
│ │ ├── two.wxs
│ │ └── well_known_exts/
│ │ └── main.wxs
│ ├── create.rs
│ └── initialize.rs
└── xtask/
├── Cargo.toml
└── src/
└── main.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .cargo/config.toml
================================================
[alias]
xtask = "run --package xtask --"
================================================
FILE: .github/dependabot.yml
================================================
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
jobs:
test:
strategy:
matrix:
rust: [stable, 1.88.0]
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source code
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.rust }}
- name: Run tests
run: cargo test --all-targets
docs:
runs-on: windows-latest
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- name: Checkout source code
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rust-docs
- name: Generate documentation
run: cargo doc --workspace --no-deps
rustfmt:
runs-on: windows-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Format Rust code
run: cargo fmt --all -- --check
clippy:
runs-on: windows-latest
steps:
- name: Checkout source code
uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Lint Rust code
run: cargo clippy --all-targets -- -D warnings
================================================
FILE: .gitignore
================================================
/target/
**/*.rs.bk
/wix/
.lock
.wix/
.test**
wix0/
**.profraw
================================================
FILE: Cargo.toml
================================================
[package]
name = "cargo-wix"
description = "Build Windows installers using the WiX Toolset"
version = "0.3.9"
authors = ["Christopher Field <cfield2@gmail.com>"]
license = "MIT/Apache-2.0"
repository = "https://github.com/volks73/cargo-wix"
documentation = "https://volks73.github.io/cargo-wix"
categories = ["development-tools"]
keywords = ["cargo-subcommand", "installer", "wix-toolset", "wix"]
homepage = "https://github.com/volks73/cargo-wix"
readme = "README.md"
edition = "2024"
exclude = ["/.github"]
rust-version = "1.88.0"
[[bin]]
name = "cargo-wix"
[lib]
name = "wix"
[dependencies]
camino = "1"
chrono = "0.4"
clap = { version = "4", features = ["derive"] }
encoding_rs_io = "0.1"
env_logger = "0.11"
itertools = "0.14"
lazy_static = "1"
log = "0.4"
mustache = "0.9"
regex = "1"
rustc-cfg = "0.5"
semver = "1"
sxd-document = "0.3"
sxd-xpath = "0.4"
termcolor = "1"
uuid = { version = "1", features = ["v4"] }
cargo_metadata = "0.23"
serde_json = "1"
[dev-dependencies]
assert_fs = "1"
maplit = "1"
predicates = "3"
serial_test = "3"
toml = "1"
[workspace]
members = [
"xtask"
]
[badges]
is-it-maintained-issue-resolution = { repository = "volks73/cargo-wix" }
is-it-maintained-open-issues = { repository = "volks73/cargo-wix" }
================================================
FILE: LICENSE-APACHE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Christopher R. Field
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: LICENSE-MIT
================================================
Copyright (c) 2017 Christopher R. Field
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# cargo-wix: A cargo subcommand to create Windows installers
A subcommand for [Cargo] that builds a Windows installer (MSI) using the [WiX
Toolset] from the build of a [Rust] binary project. It also supports signing the
Windows installer if a code signing certificate is available using the
[SignTool] application available in the [Windows SDK].
[](https://crates.io/crates/cargo-wix)
[](https://github.com/volks73/cargo-wix/releases)
[](https://github.com/volks73/cargo-wix#license)
[](https://github.com/volks73/cargo-wix/actions/workflows/ci.yml)
## Quick Start
Start a command prompt (cmd.exe) and then execute the following commands:
```dos
C:\>cargo install cargo-wix
C:\>cd Path\To\Project
C:\Path\To\Project\>cargo wix init
C:\Path\To\Project\>cargo wix
```
The Windows installer (MSI) for the project will be in the
`C:\Path\To\Project\target\wix` folder. Please see the [Documentation] for more
advanced usage, configuration, and customization.
## Installation
The cargo-wix project can be installed on any platform supported by the Rust
programming language, but the [WiX Toolset] is Windows only; thus, this project is
only useful when installed on a Windows machine. Ensure the following
dependencies are installed before proceeding.
>[!NOTE]
>Cargo is installed automatically when installing the Rust programming language.
>The `stable-x86_64-pc-windows-msvc` toolchain is recommended.
- [Cargo]
- [Rust v1.88.0 or newer](https://www.rust-lang.org)
- [WiX Toolset] [v3.14.1](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm) or [newer](https://github.com/wixtoolset/wix/releases)
- [Windows SDK] (Optional), needed for signing the installer
>[!IMPORTANT]
>The [WiX Toolset] is available in two different variants: [Legacy] (v3.14.1) and
>[Modern] (v4+). The Legacy variant uses a two-stage approach with a compiler,
>`candle.exe`, and a linker, `light.exe`. The Modern variant uses a single stage
>approach with a single executable, `wix.exe`, and a new XML schema and
>namespace. The Legacy variant is no longer supported by the WiX Toolset
>developers, [FireGiant]. Both variants are supported by the cargo-wix project
>and the `cargo wix` subcommand, but usage of the Legacy variant is currently the
>default.
>[!WARNING]
>As of April 3rd, 2026, the `windows-latest` GitHub Action image still only
>contains the [Legacy] variant, v3.14.1, of the WiX Toolset. If the [Modern] variant,
>v4+, of the WiX Toolset is desired, then a newer version will needed to be
>explicitly installed as a step in the action _before_ any `cargo wix` commands.
>[!WARNING]
>As of April 3rd, 2026, the GitHub Action Windows 11 ARM64 runner image does not
>have the [WiX Toolset] installed. The [Legacy] or [Modern] variants will have
>to be explicitly installed as a step in the action _before_ any `cargo wix`
>commands. See <https://github.com/actions/partner-runner-images/blob/main/images/arm-windows-11-image.md#omitted-software>.
Once the prerequisites have been installed, execute the following command to
install the `cargo wix` subcommand.
```dos
C:\> cargo install cargo-wix
```
## Usage
Start a command prompt, such as `cmd.exe`, the [Developer Prompt] installed with
the [Build Tools for Visual Studio 2026], [Windows Terminal] with [Powershell],
or [git bash], and navigate to the project's root folder. Run the subcommand:
```dos
C:\Path\to\Project> cargo wix init
```
This will create the `wix` folder in the project's root (along side the
`Cargo.toml` file) and then it will create the `wix\main.wxs` file from the WiX
Source (wxs) embedded within the subcommand. The generated `wix\main.wxs` file
can be used without modification with the following command to create an
installer for the project:
```dos
C:\Path\to\Project> cargo wix
```
The `cargo wix` subcommand without any arguments searches for a `wix\main.wxs`
file, relative to the project's root. It will compile the `wix\main.wxs` file
and then link the object file (`target\wix\build\main.wixobj`) to create the
Windows installer (MSI). The installer will be located in the `target\wix`
folder. All artifacts of the installer compilation and linking process are
placed within the `target\wix` folder. Paths in the `wix\main.wxs` file should
be relative to the project's root, i.e. the same location as the `Cargo.toml`
manifest file.
A different WiX Source (wxs) file from the `wix\main.wxs` file can be used by
specifying a path to it as an argument to the subcommand as follows:
```dos
C:\Path\to\Project> cargo wix Path\to\WiX\Source\File.wxs
```
You can also automatically run the installer after creating it by specifying the
`--install` argument:
```dos
C:\Path\to\Project> cargo wix --install
```
The `print <template>` subcommand, which prints one of the embedded templates to
stdout, can be used to create the `main.wxs` file. A [WXS template] file
specifically designed to work with this subcommand is embedded within the
`cargo-wix` binary during installation. Use the following commands to create a
WiX Source file and use it to create an installer with this subcommand.
```dos
C:\Path\to\Project> cargo wix print wxs > example.wxs
C:\Path\to\Project> cargo wix example.wxs
```
The WiX source file can be customized using a text editor, but modification of
the XML preprocessor variables should be avoided to ensure the `cargo wix`
command works properly.
To sign the installer (MSI) as part of the build process, ensure the `signtool`
command is available in the `PATH` system environment variable or use the
[Developer Prompt] that was installed with the Windows SDK, and use the `sign`
sub-subcommand as follows:
```dos
C:\Path\to\Project> cargo wix sign
```
Use the `-h,--help` flag to display information about additional options and
features.
```dos
C:\Path\to\Project> cargo wix -h
```
## Tests
There are set environment variables that can be used to help debug a failing
test. The `CARGO_WIX_TEST_PERSIST` environment variable can be set to persist
the temporary directories that are created during integration tests. This allows
the developer to inspect the contents of the temporary directory. The
`CARGO_WIX_TEST_PERSIST` environment variable accepts any value. Unsetting the
environment variable will delete the temporary directories after each test.
The `CARGO_WIX_TEST_LOG` environment variable sets the log level while running
an integration test. It accepts an integer value between 0 and 5, with 0 turning
off logging, and 5 displaying all log statements (ERROR, WARN, INFO, DEBUG, and
TRACE). Log statements are __not__ captured during tests, so this environment
variable should be used only when running an integration test in isolation to
prevent "swampping" the terminal/console with statements.
Finally, the `CARGO_WIX_TEST_NO_CAPTURE` environment variable accepts any value
and will display the output from the WiX Toolset compiler (candle.exe) and
linker (light.exe) when running an integration test. Similar to the
`CARGO_WIX_TEST_LOG` environment variable, this variable should only be used in
isolation to prevent "swamping" the terminal/console with the output from the
WiX Toolset commands. By default, the output is captured by the _test_ not
cargo's test framework; thus, the `cargo test -- --nocapture` command has no
affect. Example of setting and unsetting all of the environment variables for
running a specific test:
```dos
C:\Path\to\Cargo\Wix> set CARGO_WIX_TEST_PERSIST=1
C:\Path\to\Cargo\Wix> set CARGO_WIX_TEST_LOG=5
C:\Path\to\Cargo\Wix> set CARGO_WIX_TEST_NO_CAPTURE=1
C:\Path\to\Cargo\Wix> cargo test <TEST NAME>
C:\Path\to\Cargo\Wix> set "CARGO_WIX_TEST_NO_CAPTURE="
C:\Path\to\Cargo\Wix> set "CARGO_WIX_TEST_LOG="
C:\Path\to\Cargo\Wix> set "CARGO_WIX_TEST_PERSIST="
```
where `<TEST NAME>` is replaced with the name of an integration test. The last
three lines/commands are optional and unsets the three environment variables to
avoid additional tests from also persisting, logging, and dumping output to the
terminal/console.
>[!NOTE]
>The `-- --nocapture` option is _not_ needed to display the logging
>statements or the output from the WiX Toolset compiler (candle.exe) and linker
>(light.exe).
Here is the same example with [Powershell]:
```powershell
PS C:\Path\to\Cargo\Wix> $env:CARGO_WIX_TEST_PERSIST=1; $env:CARGO_WIX_TEST_LOG=5; $env:CARGO_WIX_TEST_NO_CAPTURE=1;
PS C:\Path\to\Cargo\Wix> cargo test <TEST NAME>
PS C:\Path\to\Cargo\Wix> Remove-Item Env:\CARGO_WIX_TEST_PERSIST; Remove-Item Env:\CARGO_WIX_TEST_LOG; Remove-Item Env:\CARGO_WIX_TEST_NO_CAPTURE
```
## License
The `cargo-wix` project is licensed under either the [MIT license] or [Apache
2.0 license]. See the [LICENSE-MIT] or [LICENSE-APACHE] files for more
information about licensing and copyright.
[apache 2.0 license]: http://www.apache.org/licenses/LICENSE-2.0
[build tools for visual studio 2026]: https://visualstudio.microsoft.com/downloads/
[cargo]: http://doc.crates.io/
[developer prompt]: https://learn.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell
[documentation]: https://volks73.github.io/cargo-wix/cargo_wix/index.html
[firegiant]: https://www.firegiant.com
[git bash]: https://gitforwindows.org/
[legacy]: https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm
[license-apache]: https://github.com/volks73/cargo-wix/blob/main/LICENSE-APACHE
[license-mit]: https://github.com/volks73/cargo-wix/blob/main/LICENSE-MIT
[mit license]: https://opensource.org/licenses/MIT
[modern]: https://github.com/wixtoolset/wix/releases
[powershell]: https://learn.microsoft.com/en-us/powershell/
[rust]: https://www.rust-lang.org
[signtool]: https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool
[windows sdk]: https://learn.microsoft.com/en-us/windows/apps/windows-sdk/downloads
[windows terminal]: https://learn.microsoft.com/en-us/windows/terminal/install
[wix toolset]: http://wixtoolset.org/
[wxs template]: https://github.com/volks73/cargo-wix/blob/main/src/templates/main.v3.wxs.mustache
================================================
FILE: src/clean.rs
================================================
// Copyright (C) 2017 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The implementation for the `clean` command. This command is focused on
//! cleaning up build output, similar to the `cargo clean` subcommand.
use crate::Result;
use crate::WIX;
use log::{debug, info, trace, warn};
use std::fs;
use std::path::{Path, PathBuf};
/// A builder for creating an execution context to clean a package of WiX
/// Toolset-related output.
#[derive(Debug, Clone)]
pub struct Builder<'a> {
input: Option<&'a str>,
}
impl<'a> Builder<'a> {
/// Creates a new `Builder` instance.
pub fn new() -> Self {
Builder { input: None }
}
/// Sets the path to a package's manifest (Cargo.toml) to be cleaned.
///
/// The default is to use the current working directory if a Cargo.toml file
/// is found.
pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
self.input = i;
self
}
/// Builds an execution context to clean the package of WiX Toolset-related
/// output.
pub fn build(&mut self) -> Execution {
Execution {
input: self.input.map(PathBuf::from),
}
}
}
impl Default for Builder<'_> {
fn default() -> Self {
Builder::new()
}
}
/// A context for removing WiX Toolset-related output from a package.
#[derive(Debug)]
pub struct Execution {
input: Option<PathBuf>,
}
impl Execution {
/// Removes WiX Toolset-related output from the package's `target` folder.
///
/// This is similar to the `cargo clean` subcommand.
pub fn run(self) -> Result<()> {
debug!("input = {:?}", self.input);
let manifest = super::manifest(self.input.as_ref())?;
debug!("target_directory = {:?}", manifest.target_directory);
let target_wix = self.target_wix(manifest.target_directory.as_std_path());
debug!("target_wix = {:?}", target_wix);
if target_wix.exists() {
trace!("The 'target\\wix' folder exists");
warn!("Removing the 'target\\wix' folder");
fs::remove_dir_all(target_wix)?;
} else {
trace!("The 'target\\wix' folder does not exist");
info!("Nothing to clean");
}
Ok(())
}
fn target_wix(&self, target_directory: &Path) -> PathBuf {
target_directory.join(WIX)
}
}
impl Default for Execution {
fn default() -> Self {
Builder::new().build()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod builder {
use super::*;
#[test]
fn input_works() {
const EXPECTED: &str = "C:\\Cargo.toml";
let mut actual = Builder::default();
actual.input(Some(EXPECTED));
assert_eq!(actual.input, Some(EXPECTED));
}
}
mod execution {
extern crate assert_fs;
use super::*;
use std::env;
use std::fs::File;
#[test]
fn target_wix_works() {
let mut cwd = env::current_dir().expect("Current Working Directory");
let actual = Execution::default().target_wix(&cwd.join("target"));
cwd.push("target");
cwd.push(WIX);
assert_eq!(actual, cwd);
}
#[test]
fn target_wix_with_existing_cargo_toml_works() {
let temp_dir = assert_fs::TempDir::new().unwrap();
let cargo_toml_path = temp_dir.path().join("Cargo.toml");
let target = temp_dir.path().join("target");
let expected = target.join(WIX);
let _non_cargo_toml_handle = File::create(&cargo_toml_path).expect("Create file");
let actual = Builder::new()
.input(cargo_toml_path.to_str())
.build()
.target_wix(&target);
assert_eq!(actual, expected);
}
}
}
================================================
FILE: src/create.rs
================================================
// Copyright (C) 2017 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The implementation for the `create`, or default, command. The default
//! command, `cargo wix`, is focused on creating, or building, the installer
//! using the WiX Toolset.
//!
//! Generally, this involves locating the WiX Source file (wxs) and passing
//! options and flags to the WiX Toolset's compiler (`candle.exe`) and linker
//! (`light.exe`). By default, it looks for a `wix\main.wxs` file relative to
//! the root of the package's manifest (Cargo.toml). A different WiX Source file
//! can be set with the `input` method using the `Builder` struct.
//!
//! When using the modern WiX Toolset (v4+) via `--toolset modern`, the build
//! uses `wix build` instead of the separate compile and link steps. The
//! `--migrate` flag can be used to automatically convert WiX v3 source files
//! and install required extension packages before building.
use crate::BINARY_FOLDER_NAME;
use crate::CARGO;
use crate::Cultures;
use crate::EXE_FILE_EXTENSION;
use crate::Error;
use crate::MSI_FILE_EXTENSION;
use crate::MSIEXEC;
use crate::Result;
use crate::WIX;
use crate::WIX_COMPILER;
use crate::WIX_LINKER;
use crate::WIX_MODERN_TOOLSET;
use crate::WIX_OBJECT_FILE_EXTENSION;
use crate::WIX_PATH_KEY;
use crate::WixArch;
use crate::toolset::Includes;
use crate::toolset::ProjectProvider;
use crate::toolset::Toolset;
use crate::toolset::ToolsetSetupMode;
use itertools::Itertools;
use log::error;
use log::{debug, info, trace, warn};
use semver::Version;
use std::convert::TryFrom;
use std::env;
use std::ffi::OsString;
use std::fmt;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::str::FromStr;
use cargo_metadata::Package;
use rustc_cfg::Cfg;
use serde_json::Value;
/// A builder for running the `cargo wix` subcommand.
#[derive(Debug, Clone)]
pub struct Builder<'a> {
bin_path: Option<&'a str>,
capture_output: bool,
compiler_args: Option<Vec<&'a str>>,
culture: Option<&'a str>,
debug_build: bool,
profile: Option<&'a str>,
debug_name: bool,
includes: Option<Vec<&'a str>>,
input: Option<&'a str>,
linker_args: Option<Vec<&'a str>>,
locale: Option<&'a str>,
name: Option<&'a str>,
no_build: bool,
target_bin_dir: Option<&'a str>,
install: bool,
output: Option<&'a str>,
package: Option<&'a str>,
target: Option<&'a str>,
version: Option<&'a str>,
toolset: Toolset,
toolset_setup_mode: ToolsetSetupMode,
// toolset_restore: bool,
}
impl<'a> Builder<'a> {
/// Creates a new `Builder` instance.
pub fn new() -> Self {
Builder {
bin_path: None,
capture_output: true,
compiler_args: None,
culture: None,
debug_build: false,
profile: None,
debug_name: false,
includes: None,
input: None,
linker_args: None,
locale: None,
name: None,
no_build: false,
install: false,
target_bin_dir: None,
output: None,
package: None,
target: None,
version: None,
toolset: Toolset::Legacy,
toolset_setup_mode: ToolsetSetupMode::None,
}
}
/// Sets the path to the WiX Toolset's `bin` folder.
///
/// The WiX Toolset's `bin` folder should contain the needed `candle.exe`
/// and `light.exe` applications. The default is to use the WIX system
/// environment variable that is created during installation of the WiX
/// Toolset. This will override any value obtained from the environment.
pub fn bin_path(&mut self, b: Option<&'a str>) -> &mut Self {
self.bin_path = b;
self
}
/// Enables or disables capturing of the output from the builder (`cargo`),
/// compiler (`candle`), linker (`light`), and signer (`signtool`).
///
/// The default is to capture all output, i.e. display nothing in the
/// console but the log statements.
pub fn capture_output(&mut self, c: bool) -> &mut Self {
self.capture_output = c;
self
}
/// Adds an argument to the compiler command.
///
/// This "passes" the argument directly to the WiX compiler (candle.exe).
/// See the help documentation for the WiX compiler for information about
/// valid options and flags.
pub fn compiler_args(&mut self, c: Option<Vec<&'a str>>) -> &mut Self {
self.compiler_args = c;
self
}
/// Sets the culture to use with the linker (light.exe) for building a
/// localized installer.
///
/// This value will override any defaults and skip looking for a value in
/// the `[package.metadata.wix]` section of the package's manifest
/// (Cargo.toml).
pub fn culture(&mut self, c: Option<&'a str>) -> &mut Self {
self.culture = c;
self
}
/// Builds the package with the Debug profile instead of the Release profile.
///
/// See the [Cargo book] for more information about release profiles. The
/// default is to use the Release profile when creating the installer. This
/// value is ignored if the `no_build` method is set to `true`.
///
/// [Cargo book]: https://doc.rust-lang.org/book/ch14-01-release-profiles.html
pub fn debug_build(&mut self, d: bool) -> &mut Self {
self.debug_build = d;
self
}
/// Appends `-debug` to the file stem for the installer (msi).
///
/// If `true`, then `-debug` is added as suffix to the file stem (string
/// before the dot and file extension) for the installer's file name. For
/// example, if `true`, then file name would be
/// `example-0.1.0-x86_64-debug.msi`. The default is to _not_ append the
/// `-debug` because the Release profile is the default.
///
/// Generally, this should be used in combination with the `debug_build`
/// method to indicate the installer is for a debugging variant of the
/// installed binary.
pub fn debug_name(&mut self, d: bool) -> &mut Self {
self.debug_name = d;
self
}
/// Adds multiple WiX Source (wxs) files to the creation of an installer.
///
/// By default, any `.wxs` file located in the project's `wix` folder will
/// be included in the creation of an installer for the project. This method
/// adds, or appends, to the list of `.wxs` files. The value is a relative
/// or absolute path.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn includes(&mut self, i: Option<Vec<&'a str>>) -> &mut Self {
self.includes = i;
self
}
/// Sets the path to a package's manifest (Cargo.toml) file.
///
/// A package's manifest is used to create an installer. If no path is
/// specified, then the current working directory (CWD) is used. An error
/// will occur if there is no `Cargo.toml` file in the CWD or at the
/// specified path. Either an absolute or relative path is valid.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
self.input = i;
self
}
/// Adds an argument to the linker command.
///
/// This "passes" the argument directly to the WiX linker (light.exe). See
/// the help documentation for the WiX compiler for information about valid
/// options and flags.
pub fn linker_args(&mut self, l: Option<Vec<&'a str>>) -> &mut Self {
self.linker_args = l;
self
}
/// Sets the path to a WiX localization file, `.wxl`, for the linker
/// (light.exe).
///
/// The [WiX localization file] is an XML file that contains localization
/// strings.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
///
/// [WiX localization file]: http://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/make_installer_localizable.html
pub fn locale(&mut self, l: Option<&'a str>) -> &mut Self {
self.locale = l;
self
}
/// Sets the name.
///
/// The default is to use the `name` field under the `[package]` section of
/// the package's manifest (Cargo.toml). This overrides that value.
///
/// The installer (msi) that is created will be named in the following
/// format: "name-major.minor.patch-platform.msi", where _name_ is the value
/// specified with this method or the value from the `name` field under the
/// `[package]` section, the _major.minor.patch_ is the version number from
/// the package's manifest `version` field or the value specified at the
/// command line, and the _platform_ is either "i686" or "x86_64" depending
/// on the build environment.
///
/// This does __not__ change the name of the executable that is installed.
/// The name of the executable can be changed by modifying the WiX Source
/// (wxs) file with a text editor.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn name(&mut self, p: Option<&'a str>) -> &mut Self {
self.name = p;
self
}
/// Skips the building of the project with the release profile.
///
/// If `true`, the project will _not_ be built using the release profile,
/// i.e. the `cargo build --release` command will not be executed. The
/// default is to build the project before each creation. This is useful if
/// building the project is more involved or is handled in a separate
/// process.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn no_build(&mut self, n: bool) -> &mut Self {
self.no_build = n;
self
}
/// Specifies that binaries should be sourced from the given directory.
///
/// Specifically this sets `CargoTargetBinDir` in wxs templates. It is
/// intended to be combined with `no_build(true)` to let another tool
/// orchestrate cargo-wix and handle the builds for it.
pub fn target_bin_dir(&mut self, p: Option<&'a str>) -> &mut Self {
self.target_bin_dir = p;
self
}
/// Runs the installer after creating it.
///
/// If `true`, the MSI installer will be created and then launched. This will
/// automatically open the installation wizard for the project and allow the
/// user to install it.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn install(&mut self, n: bool) -> &mut Self {
self.install = n;
self
}
/// Sets the output file and destination.
///
/// The default is to create a MSI file with the
/// `<product-name>-<version>-<arch>.msi` file name and extension in the
/// `target\wix` folder. Use this method to override the destination and
/// file name of the Windows installer.
///
/// If the path is to an existing folder or contains a trailing slash
/// (forward or backward), then the default MSI file name is used, but the
/// installer will be available at the specified path. When specifying a
/// file name and path, the `.msi` file is not required. It will be added
/// automatically.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
self.output = o;
self
}
/// Sets the package.
///
/// If the project is organized using a workspace, this selects the package
/// by name to create an installer. If a workspace is not used, then this
/// has no effect.
pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
self.package = p;
self
}
/// Sets the build target.
///
/// The default is to use the default target for the environment. Use this
/// method to change the target for the build and installer creation. The
/// value should be a string from the `rustc --print target-list` command.
/// This enables "cross-compilation" of installers similar to the
/// cross-compilation of Rust code, but only for Windows targets.
pub fn target(&mut self, v: Option<&'a str>) -> &mut Self {
self.target = v;
self
}
/// Sets the version.
///
/// This overrides the `version` field of the package's manifest
/// (Cargo.toml). The version should be in the "Major.Minor.Patch" notation.
///
/// This value will override any default and skip looking for a value in the
/// `[package.metadata.wix]` section of the package's manifest (Cargo.toml).
pub fn version(&mut self, v: Option<&'a str>) -> &mut Self {
self.version = v;
self
}
/// Sets the wix toolset to use
pub fn toolset(&mut self, v: Toolset) -> &mut Self {
self.toolset = v;
self
}
/// Sets the wix toolset migration setup mode
pub fn toolset_migration(&mut self, setup: ToolsetSetupMode) -> &mut Self {
self.toolset_setup_mode = setup;
self
}
/// Builds a context for creating, or building, the installer.
pub fn build(&mut self) -> Execution {
Execution {
bin_path: self.bin_path.map(PathBuf::from),
capture_output: self.capture_output,
compiler_args: self
.compiler_args
.as_ref()
.map(|c| c.iter().map(|s| (*s).to_string()).collect()),
culture: self.culture.map(String::from),
debug_build: self.debug_build,
profile: self.profile.map(String::from),
debug_name: self.debug_name,
includes: self
.includes
.as_ref()
.map(|v| v.iter().map(&PathBuf::from).collect()),
input: self.input.map(PathBuf::from),
linker_args: self
.linker_args
.as_ref()
.map(|l| l.iter().map(|s| (*s).to_string()).collect()),
locale: self.locale.map(PathBuf::from),
name: self.name.map(String::from),
no_build: self.no_build,
toolset: self.toolset.clone(),
toolset_setup_mode: self.toolset_setup_mode,
target_bin_dir: self.target_bin_dir.map(PathBuf::from),
install: self.install,
output: self.output.map(String::from),
package: self.package.map(String::from),
version: self.version.map(String::from),
target: self.target.map(String::from),
}
}
pub fn profile(&mut self, profile: Option<&'a str>) -> &mut Self {
self.profile = profile;
self
}
}
impl Default for Builder<'_> {
fn default() -> Self {
Builder::new()
}
}
/// A context for creating, or building, an installer.
#[derive(Debug)]
pub struct Execution {
bin_path: Option<PathBuf>,
capture_output: bool,
compiler_args: Option<Vec<String>>,
culture: Option<String>,
debug_build: bool,
profile: Option<String>,
debug_name: bool,
includes: Option<Vec<PathBuf>>,
input: Option<PathBuf>,
target_bin_dir: Option<PathBuf>,
linker_args: Option<Vec<String>>,
locale: Option<PathBuf>,
name: Option<String>,
no_build: bool,
pub(crate) toolset: Toolset,
toolset_setup_mode: ToolsetSetupMode,
install: bool,
output: Option<String>,
package: Option<String>,
target: Option<String>,
version: Option<String>,
}
impl Execution {
/// Creates, or builds, an installer within a built context.
#[allow(clippy::cognitive_complexity)]
pub fn run(self) -> Result<()> {
debug!("self.bin_path = {:?}", self.bin_path);
debug!("self.capture_output = {:?}", self.capture_output);
debug!("self.compiler_args = {:?}", self.compiler_args);
debug!("self.culture = {:?}", self.culture);
debug!("self.debug_build = {:?}", self.debug_build);
debug!("self.profile = {:?}", self.profile);
debug!("self.debug_name = {:?}", self.debug_name);
debug!("self.includes = {:?}", self.includes);
debug!("self.input = {:?}", self.input);
debug!("self.linker_args = {:?}", self.linker_args);
debug!("self.locale = {:?}", self.locale);
debug!("self.name = {:?}", self.name);
debug!("self.no_build = {:?}", self.no_build);
debug!("self.target_bin_dir = {:?}", self.target_bin_dir);
debug!("self.install = {:?}", self.install);
debug!("self.output = {:?}", self.output);
debug!("self.package = {:?}", self.package);
debug!("self.target = {:?}", self.target);
debug!("self.version = {:?}", self.version);
let manifest_path = super::cargo_toml_file(self.input.as_ref())?;
debug!("manifest_path = {:?}", manifest_path);
let manifest = super::manifest(self.input.as_ref())?;
debug!("target_directory = {:?}", manifest.target_directory);
let package = super::package(&manifest, self.package.as_deref())?;
debug!("package = {:?}", package);
let metadata = package.metadata.clone();
debug!("metadata = {:?}", metadata);
let name = self.name(&package);
debug!("name = {:?}", name);
let target = self.target()?;
debug!("target = {:?}", target);
let version = self.version(&package)?;
debug!("version = {:?}", version);
let compiler_args = self.compiler_args(&metadata);
debug!("compiler_args = {:?}", compiler_args);
let culture = self.culture(&metadata)?;
debug!("culture = {:?}", culture);
let linker_args = self.linker_args(&metadata);
debug!("linker_args = {:?}", linker_args);
let locale = self.locale(&metadata)?;
debug!("locale = {:?}", locale);
let profile = self.profile(&metadata);
debug!("profile = {:?}", profile);
let debug_name = self.debug_name(&metadata);
debug!("debug_name = {:?}", debug_name);
let wxs_sources = self.wxs_sources(&package)?;
debug!("wxs_sources = {:?}", wxs_sources);
let wixobj_destination = self.wixobj_destination(manifest.target_directory.as_std_path());
debug!("wixobj_destination = {:?}", wixobj_destination);
let no_build = self.no_build(&metadata);
debug!("no_build = {:?}", no_build);
let target_bin_dir =
self.target_bin_dir(manifest.target_directory.as_std_path(), &target, &profile);
debug!("target_bin_dir = {:?}", target_bin_dir);
let cfg = Cfg::of(&target.triple).map_err(|e| Error::Generic(e.to_string()))?;
let wix_arch = WixArch::try_from(&cfg)?;
debug!("wix_arch = {:?}", wix_arch);
if no_build {
// Only warn if the user isn't clearly trying to be in charge of builds
if self.target_bin_dir.is_none() {
warn!("Skipped building the binary");
}
} else {
// Build the binary, if a binary been built, then this will essentially do nothing.
info!("Building the binary");
let mut builder = Command::new(
env::var("CARGO")
.map(PathBuf::from)
.ok()
.unwrap_or_else(|| PathBuf::from(CARGO)),
);
debug!("builder = {:?}", builder);
if self.capture_output {
trace!("Capturing the '{}' output", CARGO);
builder.stdout(Stdio::null());
builder.stderr(Stdio::null());
}
builder.arg("build");
builder.arg(format!("--profile={}", profile.name));
if let Some(target) = &target.arg {
builder.arg(format!("--target={target}"));
}
if let Some(ref package) = self.package {
builder.arg(format!("--package={package}"));
}
builder.arg("--manifest-path").arg(&manifest_path);
debug!("command = {:?}", builder);
let status = builder.status()?;
if !status.success() {
return Err(Error::Command(
CARGO,
status.code().unwrap_or(100),
self.capture_output,
));
}
}
// Compile the installer
info!("Compiling the installer");
// Legacy toolset uses `candle` and `light` (compile and link)
// Modern toolset only uses `wix build`
let mut compiler = if self.toolset.is_legacy() {
debug!("Using legacy wix build tools");
self.toolset.compiler(self.bin_path.clone())?
} else {
debug!("Using modern wix build tools");
self.toolset.wix("build")?
};
debug!("compiler = {:?}", compiler);
if self.capture_output {
trace!("Capturing the '{}' output", WIX_COMPILER);
compiler.stdout(Stdio::null());
compiler.stderr(Stdio::null());
}
compiler.arg("-arch").arg(wix_arch.to_string());
if self.toolset.is_legacy() {
compiler.arg("-ext").arg("WixUtilExtension");
if let Some(vendor) = &cfg.target_vendor {
compiler.arg(format!("-dTargetVendor={vendor}"));
}
compiler
.arg(format!("-dVersion={version}"))
.arg(format!("-dPlatform={wix_arch}"))
.arg(format!("-dProfile={}", profile.name))
.arg(format!("-dTargetEnv={}", cfg.target_env))
.arg(format!("-dTargetTriple={}", target.triple))
.arg(format!("-dCargoProfile={}", profile.name))
.arg({
let mut s = OsString::from("-dCargoTargetDir=");
s.push(&manifest.target_directory);
s
})
.arg({
let mut s = OsString::from("-dCargoTargetBinDir=");
s.push(&target_bin_dir);
s
})
.arg("-o")
.arg(&wixobj_destination);
} else {
let project = self.toolset_setup_mode.setup(&self, &package, None)?;
// Determine installer type from wxs content before building
let pkg_count = project.package_count();
debug!("pkg_count = {:?}", pkg_count);
if pkg_count == 0 && !project.is_bundle() {
return Err(Error::Generic(String::from(
"No WiX <Package> or <Bundle> definition was found in any of the \
source files. There needs to be at least one package or bundle \
definition for the installer to be created.",
)));
}
// `wix build` produces a single output file per invocation.
// Multiple `<Package>` definitions across source files is not supported.
if pkg_count > 1 {
return Err(Error::Generic(format!(
"Found {pkg_count} WiX package definitions across the source files. \
The modern WiX toolset (wix build) can only produce one installer per \
invocation. Please separate your WiX sources so that only one source \
file defines a <Package> element, or build each package source separately."
)));
}
let installer_kind = project.installer_kind();
debug!("installer_kind = {:?}", installer_kind);
let installer_destination = self.installer_destination(
&name,
&version,
&cfg,
debug_name,
&installer_kind,
&package,
manifest.target_directory.as_std_path(),
);
debug!("installer_destination = {:?}", installer_destination);
// In WiX v4+, `-d` is a separate flag from the key=value pair
// (two args: `-d Key=Value`). Legacy candle.exe uses a single arg: `-dKey=Value`.
if let Some(vendor) = &cfg.target_vendor {
compiler.arg("-d").arg(format!("TargetVendor={vendor}"));
}
compiler
.arg("-d")
.arg(format!("Version={version}"))
.arg("-d")
.arg(format!("Platform={wix_arch}"))
.arg("-d")
.arg(format!("Profile={}", profile.name))
.arg("-d")
.arg(format!("TargetEnv={}", cfg.target_env))
.arg("-d")
.arg(format!("TargetTriple={}", target.triple))
.arg("-d")
.arg(format!("CargoProfile={}", profile.name))
.arg("-d")
.arg({
let mut s = OsString::from("CargoTargetDir=");
s.push(&manifest.target_directory);
s
})
.arg("-d")
.arg({
let mut s = OsString::from("CargoTargetBinDir=");
s.push(&target_bin_dir);
s
});
// "Linker" flags
// In WiX v4+, `-pdbtype none` replaces the legacy `light.exe -spdb`
// flag to suppress PDB generation. The `-culture` flag replaces the legacy `-cultures:{v}`
// colon-delimited syntax.
compiler
.args(["-pdbtype", "none"])
.arg("-culture")
.arg(culture.to_string());
// `wix build -o <path>` infers the output type (msi vs exe) from
// the file extension. This is unlike legacy `light.exe -out` which accepts a directory.
compiler.arg("-o").arg(&installer_destination);
// Applies all `-ext` flags
project.configure_toolset_extensions(&mut compiler)?;
if let Some(ref l) = locale {
trace!("Using the a WiX localization file");
compiler.arg("-loc").arg(l);
}
}
if let Some(args) = &compiler_args {
trace!("Appending compiler arguments");
compiler.args(args);
}
if self.toolset.is_modern()
&& let Some(args) = &linker_args
{
trace!("Appending linker arguments to wix build");
compiler.args(args);
}
compiler.args(&wxs_sources);
debug!("command = {:?}", compiler);
let status = compiler.status().map_err(|err| {
if err.kind() == ErrorKind::NotFound {
Error::Generic(format!(
"The compiler application ({WIX_COMPILER}) could not be found in the PATH environment \
variable. Please check the WiX Toolset (http://wixtoolset.org/) is \
installed and check the WiX Toolset's '{BINARY_FOLDER_NAME}' folder has been added to the PATH \
system environment variable, the {WIX_PATH_KEY} system environment variable exists, or use \
the '-b,--bin-path' command line argument."
))
} else {
err.into()
}
})?;
if !status.success() {
error!(
"Could not execute {}",
compiler
.inner
.get_args()
.filter_map(|a| a.to_str())
.join(" ")
);
return Err(Error::Command(
if self.toolset.is_legacy() {
WIX_COMPILER
} else {
WIX_MODERN_TOOLSET
},
status.code().unwrap_or(100),
self.capture_output,
));
}
if self.toolset.is_modern() {
// Launch the installer (modern path)
if self.install {
info!("Launching the installer");
let project = self.create_project(&package)?;
let installer_kind = project.installer_kind();
let dest = self.installer_destination(
&name,
&version,
&cfg,
debug_name,
&installer_kind,
&package,
manifest.target_directory.as_std_path(),
);
let status = match installer_kind {
InstallerKind::Exe => {
// Bundles produce .exe files that are launched directly
debug!("Launching bundle installer: {:?}", dest);
Command::new(&dest).status()?
}
InstallerKind::Msi => {
// MSI packages are installed via msiexec
debug!("Launching MSI installer via msiexec: {:?}", dest);
let mut installer = Command::new(MSIEXEC);
installer.arg("/i").arg(&dest);
installer.status()?
}
};
if !status.success() {
return Err(Error::Command(
match installer_kind {
InstallerKind::Exe => "bundle",
InstallerKind::Msi => MSIEXEC,
},
status.code().unwrap_or(100),
self.capture_output,
));
}
}
}
// Modern wix no longer requires `light`
if self.toolset.is_legacy() {
let wixobj_sources = self.wixobj_sources(&wixobj_destination)?;
debug!("wixobj_sources = {:?}", wixobj_sources);
let installer_kind = InstallerKind::try_from(
wixobj_sources
.iter()
.map(WixObjKind::try_from)
.collect::<Result<Vec<WixObjKind>>>()?,
)?;
debug!("installer_kind = {:?}", installer_kind);
let installer_destination = self.installer_destination(
&name,
&version,
&cfg,
debug_name,
&installer_kind,
&package,
manifest.target_directory.as_std_path(),
);
debug!("installer_destination = {:?}", installer_destination);
// Link the installer
info!("Linking the installer");
let mut linker = self.linker()?;
debug!("linker = {:?}", linker);
let base_path = manifest_path.parent().ok_or_else(|| {
Error::Generic(String::from("The base path for the linker is invalid"))
})?;
debug!("base_path = {:?}", base_path);
if self.capture_output {
trace!("Capturing the '{}' output", WIX_LINKER);
linker.stdout(Stdio::null());
linker.stderr(Stdio::null());
}
linker
.arg("-spdb")
.arg("-ext")
.arg("WixUIExtension")
.arg("-ext")
.arg("WixUtilExtension")
.arg(format!("-cultures:{culture}"))
.arg("-out")
.arg(&installer_destination)
.arg("-b")
.arg(base_path);
if let Some(l) = locale {
trace!("Using the a WiX localization file");
linker.arg("-loc").arg(l);
}
if let InstallerKind::Exe = installer_kind {
trace!("Adding the WixBalExtension for the bundle-based installer");
linker.arg("-ext").arg("WixBalExtension");
}
if let Some(args) = &linker_args {
trace!("Appending linker arguments");
linker.args(args);
}
linker.args(&wixobj_sources);
debug!("command = {:?}", linker);
let status = linker.status().map_err(|err| {
if err.kind() == ErrorKind::NotFound {
Error::Generic(format!(
"The linker application ({WIX_LINKER}) could not be found in the PATH environment \
variable. Please check the WiX Toolset (http://wixtoolset.org/) is \
installed and check the WiX Toolset's '{BINARY_FOLDER_NAME}' folder has been added to the PATH \
environment variable, the {WIX_PATH_KEY} system environment variable exists, or use the \
'-b,--bin-path' command line argument."
))
} else {
err.into()
}
})?;
if !status.success() {
return Err(Error::Command(
WIX_LINKER,
status.code().unwrap_or(100),
self.capture_output,
));
}
// Launch the installer
if self.install {
info!("Launching the installer");
let mut installer = Command::new(MSIEXEC);
installer.arg("/i").arg(&installer_destination);
let status = installer.status()?;
if !status.success() {
return Err(Error::Command(
MSIEXEC,
status.code().unwrap_or(100),
self.capture_output,
));
}
}
}
Ok(())
}
fn compiler_args(&self, metadata: &Value) -> Option<Vec<String>> {
self.compiler_args.to_owned().or_else(|| {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("compiler-args"))
.and_then(|i| i.as_array())
.map(|a| {
a.iter()
.map(|s| s.as_str().map(String::from).unwrap())
.collect::<Vec<String>>()
})
})
}
fn culture(&self, metadata: &Value) -> Result<Cultures> {
if let Some(culture) = &self.culture {
Cultures::from_str(culture)
} else if let Some(pkg_meta_wix_culture) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("culture"))
.and_then(|c| c.as_str())
{
Cultures::from_str(pkg_meta_wix_culture)
} else {
Ok(Cultures::EnUs)
}
}
fn debug_build(&self, metadata: &Value) -> bool {
if self.debug_build {
true
} else {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("dbg-build"))
.and_then(|c| c.as_bool())
.unwrap_or_default()
}
}
fn debug_name(&self, metadata: &Value) -> bool {
if self.debug_name {
true
} else {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("dbg-name"))
.and_then(|c| c.as_bool())
.unwrap_or_default()
}
}
/// Get the name of the cargo build profile
///
/// If `profile` is set, prefer that.
/// Otherwise use `debug_build` to choose if we're debug or release.
fn profile_name(&self, metadata: &Value) -> String {
if let Some(profile) = self.profile.clone() {
profile
} else if let Some(pkg_meta_wix_profile) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("profile"))
.and_then(|c| c.as_str())
{
pkg_meta_wix_profile.to_owned()
} else if self.debug_build(metadata) {
// The default "debug" build profile is called "dev" for whatever reason
"dev".to_owned()
} else {
"release".to_owned()
}
}
/// Gets the name and dir of the cargo build profile
fn profile(&self, metadata: &Value) -> Profile {
let name = self.profile_name(metadata);
// Figure out what subdir of target will contain our output
// Cargo specially maps the builtin profile names to "debug" or "release"
// in the target directory, but custom profiles get forwarded verbatim.
let dir = match &*name {
"dev" | "test" => "debug",
"release" | "bench" => "release",
p => p,
}
.to_owned();
Profile { name, dir }
}
#[allow(clippy::too_many_arguments)]
fn installer_destination(
&self,
name: &str,
version: &str,
cfg: &Cfg,
debug_name: bool,
installer_kind: &InstallerKind,
package: &Package,
target_directory: &Path,
) -> PathBuf {
let filename = if debug_name {
format!(
"{}-{}-{}-debug.{}",
name, version, cfg.target_arch, installer_kind
)
} else {
format!(
"{}-{}-{}.{}",
name, version, cfg.target_arch, installer_kind
)
};
if let Some(ref path_str) = self.output {
trace!("Using the explicitly specified output path for the MSI destination");
let path = Path::new(path_str);
if path_str.ends_with('/') || path_str.ends_with('\\') || path.is_dir() {
path.join(filename)
} else {
path.to_owned()
}
} else if let Some(pkg_meta_wix_output) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("output"))
.and_then(|o| o.as_str())
{
trace!("Using the output path in the package's metadata for the MSI destination");
let path = Path::new(pkg_meta_wix_output);
if pkg_meta_wix_output.ends_with('/')
|| pkg_meta_wix_output.ends_with('\\')
|| path.is_dir()
{
path.join(filename)
} else {
path.to_owned()
}
} else {
trace!(
"Using the package's manifest (Cargo.toml) file path to specify the MSI destination"
);
target_directory.join(WIX).join(filename)
}
}
fn linker(&self) -> Result<Command> {
if let Some(mut path) = self.bin_path.as_ref().map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset '{}' folder for the linker",
p.display(),
BINARY_FOLDER_NAME
);
p.push(WIX_LINKER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); // Remove the 'light' application from the path
Err(Error::Generic(format!(
"The linker application ('{}') does not exist at the '{}' path specified via \
the '-b,--bin-path' command line argument. Please check the path is correct \
and the linker application exists at the path.",
WIX_LINKER,
path.display()
)))
} else {
Ok(Command::new(path))
}
} else if let Some(mut path) = env::var_os(WIX_PATH_KEY).map(|s| {
let mut p = PathBuf::from(s);
trace!(
"Using the '{}' path to the WiX Toolset's '{}' folder for the linker",
p.display(),
BINARY_FOLDER_NAME
);
p.push(BINARY_FOLDER_NAME);
p.push(WIX_LINKER);
p.set_extension(EXE_FILE_EXTENSION);
p
}) {
if !path.exists() {
path.pop(); // Remove the `candle` application from the path
Err(Error::Generic(format!(
"The linker application ('{}') does not exist at the '{}' path specified \
via the {} environment variable. Please check the path is correct and the \
linker application exists at the path.",
WIX_LINKER,
path.display(),
WIX_PATH_KEY
)))
} else {
Ok(Command::new(path))
}
} else {
Ok(Command::new(WIX_LINKER))
}
}
fn linker_args(&self, metadata: &Value) -> Option<Vec<String>> {
self.linker_args.to_owned().or_else(|| {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("linker-args"))
.and_then(|i| i.as_array())
.map(|a| {
a.iter()
.map(|s| s.as_str().map(String::from).unwrap())
.collect::<Vec<String>>()
})
})
}
fn locale(&self, metadata: &Value) -> Result<Option<PathBuf>> {
if let Some(locale) = self.locale.as_ref().map(PathBuf::from) {
if locale.exists() {
Ok(Some(locale))
} else {
Err(Error::Generic(format!(
"The '{}' WiX localization file could not be found, or it does not exist. \
Please check the path is correct and the file exists.",
locale.display()
)))
}
} else if let Some(pkg_meta_wix_locale) = metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("locale"))
.and_then(|l| l.as_str())
.map(PathBuf::from)
{
Ok(Some(pkg_meta_wix_locale))
} else {
Ok(None)
}
}
fn name(&self, package: &Package) -> String {
if let Some(ref p) = self.name {
p.to_owned()
} else if let Some(pkg_meta_wix_name) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("name"))
.and_then(|n| n.as_str())
.map(String::from)
{
pkg_meta_wix_name
} else {
package.name.to_string()
}
}
fn no_build(&self, metadata: &Value) -> bool {
if self.no_build {
true
} else {
metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("no-build"))
.and_then(|c| c.as_bool())
.unwrap_or_default()
}
}
/// Get the value of CargoTargetBinDir
///
/// If it's explicitly set, just use that.
/// Otherwise compute it from the various relevant settings of a build.
fn target_bin_dir(
&self,
target_directory: &Path,
target: &Target,
profile: &Profile,
) -> PathBuf {
// Setting this via [package.metadata.wix] explicitly unsupported to avoid footguns
if let Some(target_bin_dir) = &self.target_bin_dir {
target_bin_dir.clone()
} else {
let mut bin_path = target_directory.to_owned();
// Cargo only adds the target to the path if it's explicitly passed via --target
if let Some(target) = &target.arg {
bin_path.push(target);
}
bin_path.push(&profile.dir);
bin_path
}
}
// TODO: Change to use --unit-graph feature of cargo once stable. See #124.
//
// This does not support default-target. Ideally we would use cargo
// --unit-graph to figure this out without having to second-guess the
// compiler. Unfortunately, cargo --unit-graph is unstable.
fn target(&self) -> Result<Target> {
if let Some(t) = &self.target {
Ok(Target {
triple: t.clone(),
arg: Some(t.clone()),
})
} else {
let output = Command::new("rustc")
.args(["--version", "--verbose"])
.output()?;
for line in output.stdout.split(|b| *b == b'\n') {
let mut line_elt = line.splitn(2, |b| *b == b':');
let first = line_elt.next();
let second = line_elt.next();
if let (Some(b"host"), Some(host_triple)) = (first, second) {
let s = String::from_utf8(host_triple.to_vec()).map_err(|_| {
Error::Generic(
"Failed to parse output of the 'rustc --verbose \
--version' command: invalid UTF8"
.to_string(),
)
});
return Ok(Target {
triple: s?.trim().to_string(),
arg: None,
});
}
}
Err(Error::Generic(
"Failed to parse output of the 'rustc --verbose --version' \
command"
.to_string(),
))
}
}
fn wixobj_destination(&self, target_directory: &Path) -> PathBuf {
// A trailing slash is needed; otherwise, candle tries to dump the
// object files to a `target\wix` file instead of dumping the object
// files in the `target\wix\` folder for the `-out` option. The trailing
// slash must be done "manually" as a string instead of using the
// PathBuf API because the PathBuf `push` and/or `join` methods treat a
// single slash (forward or backward) without a prefix as the root `C:\`
// or `/` and deletes the full path. This is noted in the documentation
// for PathBuf, but it was unexpected and kind of annoying because I am
// not sure how to add a trailing slash in a cross-platform way with
// PathBuf, not that cargo-wix needs to be cross-platform.
target_directory.join(WIX).join("")
}
fn wixobj_sources(&self, wixobj_dst: &Path) -> Result<Vec<PathBuf>> {
let wixobj_sources: Vec<PathBuf> = std::fs::read_dir(wixobj_dst)?
.filter(|r| r.is_ok())
.map(|r| r.unwrap().path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some(WIX_OBJECT_FILE_EXTENSION))
.collect();
if wixobj_sources.is_empty() {
Err(Error::Generic(String::from("No WiX object files found.")))
} else {
Ok(wixobj_sources)
}
}
/// Attempts to convert a Rust SemVer version to the format WiX desires.
///
/// WiX only supports numbers in versions, with a format of "x.x.x.x"
/// WiX itself requires each component to be an integer from 0 to 65534 (inclusive).
/// However the first 3 parts are forwarded to Windows as a [ProductVersion][0],
/// which interprets them as "major.minor.build" and states:
///
/// > The major version and has a maximum value of 255.
/// > The minor version and has a maximum value of 255.
/// > The build version or the update version and has a maximum value of 65,535.
///
/// So we take the intersection of these requirements, and shove the rust "major.minor.patch"
/// format into it. This leaves the more freeform "prerelease" and "build" components of a
/// SemVer Version to get squeezed into the 4th value.
///
/// The 4th value is seemingly just a bonus value that WiX keeps to itself, so it's not
/// terribly important that we get it perfect. We therefore attempt to heuritistically
/// parse out a numeric "prerelease version" based on common formats.
///
/// [0]: https://learn.microsoft.com/en-us/windows/win32/msi/productversion
fn version(&self, package: &Package) -> Result<String> {
// Select the version
let version = if let Some(ref v) = self.version {
Version::parse(v).map_err(Error::from)?
} else if let Some(pkg_meta_wix_version) = package
.metadata
.get("wix")
.and_then(|w| w.as_object())
.and_then(|t| t.get("version"))
.and_then(|v| v.as_str())
{
Version::parse(pkg_meta_wix_version).map_err(Error::from)?
} else {
package.version.clone()
};
// validate basic parts
if version.major > 255 {
return Err(Error::Generic(format!(
"The app's major version {} can't be greater than 255 for an msi",
version.major
)));
}
if version.minor > 255 {
return Err(Error::Generic(format!(
"The app's minor version {} can't be greater than 255 for an msi",
version.minor
)));
}
if version.patch > 65534 {
return Err(Error::Generic(format!(
"The app's patch version {} can't be greater than 65534 for an msi",
version.patch
)));
}
// Attempt to validate + convert the prerelease parts
let needs_prerelease_handling = !version.build.is_empty() || !version.pre.is_empty();
if needs_prerelease_handling {
// This mess is trying 3 approaches in sequence:
//
// * parse as if it's `1.2.3-4`
// * parse as if it's `1.2.3-prerelease.4`
// * parse as if it's `1.2.3-prerelease+4`
let bonus = version
.pre
.parse::<u64>()
.or_else(|e| {
if let Some((_, dotted)) = version.pre.split_once('.') {
dotted.parse::<u64>()
} else {
Err(e)
}
})
.or_else(|_| version.build.parse::<u64>());
let bonus = if let Ok(bonus) = bonus {
bonus
} else {
return Err(Error::Generic(format!(
"The app's version {} is a prerelease, but we couldn't convert the prerelease \
components to an integer. We recommend a format like 1.2.3-prerelease.4, \
as we can map it to the 1.2.3.4 format that works for an msi.",
version,
)));
};
if bonus > 65534 {
return Err(Error::Generic(format!(
"The app's prerelease version {} can't be greater than 65534 for an msi",
bonus
)));
}
Ok(format!(
"{}.{}.{}.{}",
version.major, version.minor, version.patch, bonus
))
} else {
Ok(format!(
"{}.{}.{}",
version.major, version.minor, version.patch
))
}
}
}
impl Includes for Execution {
fn includes(&self) -> Option<&Vec<PathBuf>> {
self.includes.as_ref()
}
}
impl Default for Execution {
fn default() -> Self {
Builder::new().build()
}
}
/// The kind of WiX Object (wixobj) file.
#[derive(Debug, PartialEq, Eq)]
pub enum WixObjKind {
/// A WiX Object (wixobj) file that ultimately links back to a WiX Source
/// (wxs) file with a [`bundle`] tag.
///
/// [`bundle`]: https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html
Bundle,
/// A WiX Object (wixobj) file that ultimately links back to a WiX Source
/// (wxs) file with a [`fragment`] tag.
///
/// [`fragment`]: https://wixtoolset.org/documentation/manual/v3/xsd/wix/fragment.html
Fragment,
/// A WiX Object (wixobj) file that ultimately links back to a WiX Source
/// (wxs) file with a [`product`] tag.
///
/// [`product`]: https://wixtoolset.org/documentation/manual/v3/xsd/wix/product.html
Product,
}
impl WixObjKind {
/// Determines if the WiX Object (wixobj) file kind is a [`bundle`].
///
/// # Examples
///
/// A WiX Object (wixobj) file identified as a WXS Source (wxs) file
/// containing a [`bundle`] tag.
///
/// ```
/// use wix::create::WixObjKind;
///
/// assert!(WixObjKind::Bundle.is_bundle())
/// ```
///
/// A WiX Object (wixobj) file identified as a WXS Source (wxs) file
/// containing a [`product`] tag.
///
/// ```
/// use wix::create::WixObjKind;
///
/// assert!(!WixObjKind::Product.is_bundle())
/// ```
///
/// [`bundle`]: https://wixtoolset.org/documentation/manual/v3/xsd/wix/bundle.html
/// [`product`]: https://wixtoolset.org/documentation/manual/v3/xsd/wix/product.html
pub fn is_bundle(&self) -> bool {
match *self {
Self::Bundle => true,
Self::Fragment => false,
Self::Product => false,
}
}
}
impl FromStr for WixObjKind {
type Err = crate::Error;
fn from_str(value: &str) -> Result<Self> {
match &*value.to_lowercase() {
"bundle" => Ok(Self::Bundle),
"fragment" => Ok(Self::Fragment),
"product" => Ok(Self::Product),
v => Err(Self::Err::Generic(format!(
"Unknown '{v}' tag name from a WiX Object (wixobj) file."
))),
}
}
}
impl TryFrom<&PathBuf> for WixObjKind {
type Error = crate::Error;
fn try_from(path: &PathBuf) -> Result<Self> {
let file = std::fs::File::open(path)?;
let mut decoder = encoding_rs_io::DecodeReaderBytes::new(file);
let mut content = String::new();
decoder.read_to_string(&mut content)?;
Self::try_from(content.as_str())
}
}
impl TryFrom<&str> for WixObjKind {
type Error = crate::Error;
fn try_from(content: &str) -> Result<Self> {
let package = sxd_document::parser::parse(content)?;
let document = package.as_document();
let mut context = sxd_xpath::Context::new();
context.set_namespace("wix", "http://schemas.microsoft.com/wix/2006/objects");
// The assumption is that the following cannot fail because the path is known to be valid at
// compile-time.
let xpath = sxd_xpath::Factory::new()
.build("/wix:wixObject/wix:section/@type")
.unwrap()
.unwrap();
let value = xpath.evaluate(&context, document.root())?.string();
Self::from_str(&value)
}
}
/// The kinds of installers that can be created using the WiX compiler
/// (candle.exe) and linker (light.exe).
#[derive(Debug, Default, PartialEq, Eq)]
pub enum InstallerKind {
/// An executable is used when an [Installation Package Bundle] is created.
///
/// [Installation Package Bundle]: https://wixtoolset.org/documentation/manual/v3/bundle/
Exe,
/// A Microsoft installer. This is the more common and typical installer to be created.
#[default]
Msi,
}
impl InstallerKind {
/// Gets the file extension _without_ the dot separator.
///
/// # Examples
///
/// The extension for an installer of an [Installation Package Bundle]. Also
/// see the [`EXE_FILE_EXTENSION`] constant.
///
/// ```
/// use wix::create::InstallerKind;
///
/// assert_eq!(InstallerKind::Exe.extension(), "exe")
/// ```
///
/// The extension for a typical Microsoft installer. Also see the
/// [`MSI_FILE_EXTENSION`] constant.
///
/// ```
/// use wix::create::InstallerKind;
///
/// assert_eq!(InstallerKind::Msi.extension(), "msi")
/// ```
///
/// [Installation Package Bundle]: https://wixtoolset.org/documentation/manual/v3/bundle/
/// [`EXE_FILE_EXTENSION`]: lib.html#exe-file-extension
/// [`MSI_FILE_EXTENSION`]: lib.html#msi-file-extension
pub fn extension(&self) -> &'static str {
match *self {
Self::Exe => EXE_FILE_EXTENSION,
Self::Msi => MSI_FILE_EXTENSION,
}
}
}
impl FromStr for InstallerKind {
type Err = crate::Error;
fn from_str(value: &str) -> Result<Self> {
match &*value.to_lowercase() {
"exe" => Ok(Self::Exe),
"msi" => Ok(Self::Msi),
_ => Err(Self::Err::Generic(format!(
"Unknown '{value}' file extension for an installer"
))),
}
}
}
impl fmt::Display for InstallerKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.extension())
}
}
impl TryFrom<Vec<WixObjKind>> for InstallerKind {
type Error = crate::Error;
fn try_from(v: Vec<WixObjKind>) -> Result<Self> {
v.iter()
.fold(None, |a, v| match v {
WixObjKind::Bundle => Some(Self::Exe),
WixObjKind::Product => a.or(Some(Self::Msi)),
_ => a,
})
.ok_or_else(|| {
Self::Error::Generic(String::from(
"Could not determine the installer kind based on the WiX \
object files. There needs to be at least one 'product' or \
'bundle' tag in the collective WiX source files (wxs).",
))
})
}
}
/// Details of the cargo build profile
#[derive(Debug, Clone)]
pub struct Profile {
/// The name of the profile to pass to `--profile=...`
name: String,
/// The name of the subdirectory of the cargo target dir that the profile will get
dir: String,
}
/// Details of the cargo build target
#[derive(Debug, Clone)]
pub struct Target {
/// The name of the target triple being built
triple: String,
/// If an explicit --target flag is being passed to Cargo, this is it
arg: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
mod builder {
use super::*;
#[test]
fn defaults_are_correct() {
let actual = Builder::new();
assert!(actual.bin_path.is_none());
assert!(actual.capture_output);
assert!(actual.compiler_args.is_none());
assert!(actual.culture.is_none());
assert!(!actual.debug_build);
assert!(!actual.debug_name);
assert!(actual.includes.is_none());
assert!(actual.input.is_none());
assert!(actual.linker_args.is_none());
assert!(actual.locale.is_none());
assert!(actual.name.is_none());
assert!(!actual.no_build);
assert!(actual.output.is_none());
assert!(actual.version.is_none());
}
#[test]
fn bin_path_works() {
const EXPECTED: &str = "C:\\Wix Toolset\\bin";
let mut actual = Builder::new();
actual.bin_path(Some(EXPECTED));
assert_eq!(actual.bin_path, Some(EXPECTED));
}
#[test]
fn target_bin_dir_works() {
const EXPECTED: &str = "target\\special\\build\\";
let mut actual = Builder::new();
actual.target_bin_dir(Some(EXPECTED));
assert_eq!(actual.target_bin_dir, Some(EXPECTED));
}
#[test]
fn capture_output_works() {
let mut actual = Builder::new();
actual.capture_output(false);
assert!(!actual.capture_output);
}
#[test]
fn compiler_args_with_single_value_works() {
const EXPECTED: &str = "-nologo";
let mut actual = Builder::new();
actual.compiler_args(Some(vec![EXPECTED]));
assert_eq!(actual.compiler_args, Some(vec![EXPECTED]));
}
#[test]
fn compiler_args_with_multiple_values_works() {
let expected: Vec<&str> = vec!["-arch", "x86"];
let mut actual = Builder::new();
actual.compiler_args(Some(expected.clone()));
assert_eq!(actual.compiler_args, Some(expected));
}
#[test]
fn culture_works() {
const EXPECTED: &str = "FrFr";
let mut actual = Builder::new();
actual.culture(Some(EXPECTED));
assert_eq!(actual.culture, Some(EXPECTED));
}
#[test]
fn russian_culture_works() {
const EXPECTED: &str = "RuRu";
let mut actual = Builder::new();
actual.culture(Some(EXPECTED));
assert_eq!(actual.culture, Some(EXPECTED));
}
#[test]
fn debug_build_works() {
let mut actual = Builder::new();
actual.debug_build(true);
assert!(actual.debug_build);
}
#[test]
fn profile_works() {
const EXPECTED: &str = "dist";
let mut actual = Builder::new();
actual.profile(Some(EXPECTED));
assert_eq!(actual.profile, Some(EXPECTED));
}
#[test]
fn debug_name_works() {
let mut actual = Builder::new();
actual.debug_name(true);
assert!(actual.debug_name);
}
#[test]
fn includes_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\wix\\main.wxs";
let mut actual = Builder::new();
actual.includes(Some(vec![EXPECTED]));
assert_eq!(actual.includes, Some(vec![EXPECTED]));
}
#[test]
fn input_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\Cargo.toml";
let mut actual = Builder::new();
actual.input(Some(EXPECTED));
assert_eq!(actual.input, Some(EXPECTED));
}
#[test]
fn linker_args_with_single_value_works() {
const EXPECTED: &str = "-nologo";
let mut actual = Builder::new();
actual.linker_args(Some(vec![EXPECTED]));
assert_eq!(actual.linker_args, Some(vec![EXPECTED]));
}
#[test]
fn linker_args_with_multiple_values_works() {
let expected: Vec<&str> = vec!["-ext", "HelloExtension"];
let mut actual = Builder::new();
actual.linker_args(Some(expected.clone()));
assert_eq!(actual.linker_args, Some(expected));
}
#[test]
fn locale_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\wix\\main.wxl";
let mut actual = Builder::new();
actual.locale(Some(EXPECTED));
assert_eq!(actual.locale, Some(EXPECTED));
}
#[test]
fn name_works() {
const EXPECTED: &str = "Name";
let mut actual = Builder::new();
actual.name(Some(EXPECTED));
assert_eq!(actual.name, Some(EXPECTED));
}
#[test]
fn no_build_works() {
let mut actual = Builder::new();
actual.no_build(true);
assert!(actual.no_build);
}
#[test]
fn output_works() {
const EXPECTED: &str = "C:\\tmp\\hello_world\\output";
let mut actual = Builder::new();
actual.output(Some(EXPECTED));
assert_eq!(actual.output, Some(EXPECTED));
}
#[test]
fn version_works() {
const EXPECTED: &str = "1.2.3";
let mut actual = Builder::new();
actual.version(Some(EXPECTED));
assert_eq!(actual.version, Some(EXPECTED));
}
#[test]
fn build_with_defaults_works() {
let mut b = Builder::new();
let default_execution = b.build();
assert!(default_execution.bin_path.is_none());
assert!(default_execution.capture_output);
assert!(default_execution.compiler_args.is_none());
assert!(default_execution.culture.is_none());
assert!(!default_execution.debug_build);
assert!(!default_execution.debug_name);
assert!(default_execution.includes.is_none());
assert!(default_execution.input.is_none());
assert!(default_execution.linker_args.is_none());
assert!(default_execution.locale.is_none());
assert!(default_execution.name.is_none());
assert!(!default_execution.no_build);
assert!(default_execution.output.is_none());
assert!(default_execution.version.is_none());
}
#[test]
fn build_with_all_works() {
const EXPECTED_BIN_PATH: &str = "C:\\Wix Toolset\\bin";
const EXPECTED_CULTURE: &str = "FrFr";
const EXPECTED_COMPILER_ARGS: &str = "-nologo";
const EXPECTED_INCLUDES: &str = "C:\\tmp\\hello_world\\wix\\main.wxs";
const EXPECTED_INPUT: &str = "C:\\tmp\\hello_world\\Cargo.toml";
const EXPECTED_LINKER_ARGS: &str = "-nologo";
const EXPECTED_LOCALE: &str = "C:\\tmp\\hello_world\\wix\\main.wxl";
const EXPECTED_NAME: &str = "Name";
const EXPECTED_OUTPUT: &str = "C:\\tmp\\hello_world\\output";
const EXPECTED_VERSION: &str = "1.2.3";
let mut b = Builder::new();
b.bin_path(Some(EXPECTED_BIN_PATH));
b.capture_output(false);
b.culture(Some(EXPECTED_CULTURE));
b.compiler_args(Some(vec![EXPECTED_COMPILER_ARGS]));
b.debug_build(true);
b.debug_name(true);
b.includes(Some(vec![EXPECTED_INCLUDES]));
b.input(Some(EXPECTED_INPUT));
b.linker_args(Some(vec![EXPECTED_LINKER_ARGS]));
b.locale(Some(EXPECTED_LOCALE));
b.name(Some(EXPECTED_NAME));
b.no_build(true);
b.output(Some(EXPECTED_OUTPUT));
b.version(Some(EXPECTED_VERSION));
let execution = b.build();
assert_eq!(execution.bin_path, Some(PathBuf::from(EXPECTED_BIN_PATH)));
assert!(!execution.capture_output);
assert_eq!(
execution.compiler_args,
Some(vec![String::from(EXPECTED_COMPILER_ARGS)])
);
assert_eq!(execution.culture, Some(String::from(EXPECTED_CULTURE)));
assert!(execution.debug_build);
assert!(execution.debug_name);
assert_eq!(
execution.includes,
Some(vec![PathBuf::from(EXPECTED_INCLUDES)])
);
assert_eq!(execution.input, Some(PathBuf::from(EXPECTED_INPUT)));
assert_eq!(
execution.linker_args,
Some(vec![String::from(EXPECTED_LINKER_ARGS)])
);
assert_eq!(execution.locale, Some(PathBuf::from(EXPECTED_LOCALE)));
assert_eq!(execution.name, Some(String::from(EXPECTED_NAME)));
assert!(execution.no_build);
assert_eq!(execution.output, Some(String::from(EXPECTED_OUTPUT)));
assert_eq!(execution.version, Some(String::from(EXPECTED_VERSION)));
}
}
mod execution {
use super::*;
#[test]
fn default_profile_works() {
const PKG_META_WIX: &str = r#"{
"wix": { }
}"#;
let execution = Execution::default();
let profile = execution.profile(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(profile.name, "release");
assert_eq!(profile.dir, "release");
}
#[test]
fn debug_build_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"dbg-build": true
}
}"#;
let execution = Execution::default();
let profile = execution.profile(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(profile.name, "dev");
assert_eq!(profile.dir, "debug");
}
#[test]
fn debug_name_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"dbg-name": true
}
}"#;
let execution = Execution::default();
let debug_name = execution.debug_name(&PKG_META_WIX.parse::<Value>().unwrap());
assert!(debug_name);
}
#[test]
fn version_metadata_works() {
const PKG_META_WIX: &str = r#"
{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "Apache-2.0",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "",
"metadata": {
"wix": {
"version": "2.1.0"
}
}
}"#;
let execution = Execution::default();
let version = execution
.version(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(version, "2.1.0");
}
#[test]
fn profile_metadata_works() {
const PKG_META_WIX: &str = r#" {
"wix": {
"profile": "dist"
}
}"#;
let execution = Execution::default();
let profile = execution.profile(&serde_json::from_str(PKG_META_WIX).unwrap());
assert_eq!(profile.name, "dist");
assert_eq!(profile.dir, "dist");
}
#[test]
fn version_prerelease_parse_works() {
const PKG_META_WIX: &str = r#"
{
"name": "Example",
"version": "2.1.0-5",
"authors": ["First Last <first.last@example.com>"],
"license": "Apache-2.0",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": ""
}"#;
let execution = Execution::default();
let version = execution
.version(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(version, "2.1.0.5");
}
#[test]
fn version_prerelease_dot_parse_works() {
const PKG_META_WIX: &str = r#"
{
"name": "Example",
"version": "2.1.0-prerelease.5",
"authors": ["First Last <first.last@example.com>"],
"license": "Apache-2.0",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": ""
}"#;
let execution = Execution::default();
let version = execution
.version(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(version, "2.1.0.5");
}
#[test]
fn version_build_parse_works() {
const PKG_META_WIX: &str = r#"
{
"name": "Example",
"version": "2.1.0-prerelease+5",
"authors": ["First Last <first.last@example.com>"],
"license": "Apache-2.0",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": ""
}"#;
let execution = Execution::default();
let version = execution
.version(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(version, "2.1.0.5");
}
#[test]
fn name_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"metadata": {
"wix": {
"name": "Metadata"
}
},
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": ""
}"#;
let execution = Execution::default();
let name = execution.name(&serde_json::from_str(PKG_META_WIX).unwrap());
assert_eq!(name, "Metadata".to_owned());
}
#[test]
fn no_build_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"no-build": true
}
}"#;
let execution = Execution::default();
let no_build = execution.no_build(&PKG_META_WIX.parse::<Value>().unwrap());
assert!(no_build);
}
#[test]
fn culture_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"culture": "Fr-Fr"
}
}"#;
let execution = Execution::default();
let culture = execution
.culture(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(culture, Cultures::FrFr);
}
#[test]
fn locale_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"locale": "wix/French.wxl"
}
}"#;
let execution = Execution::default();
let locale = execution
.locale(&PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(locale, Some(PathBuf::from("wix/French.wxl")));
}
#[test]
fn output_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "XYZ",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "",
"metadata": {
"wix": {
"output": "target/wix/test.msi"
}
}
}"#;
let execution = Execution::default();
let output = execution.installer_destination(
"Different",
"2.1.0",
&Cfg::of("x86_64-pc-windows-msvc").unwrap(),
false,
&InstallerKind::default(),
&serde_json::from_str(PKG_META_WIX).unwrap(),
Path::new("target/"),
);
assert_eq!(output, PathBuf::from("target/wix/test.msi"));
}
#[test]
fn include_metadata_works() {
const PKG_META_WIX: &str = r#"{
"name": "Example",
"version": "0.1.0",
"authors": ["First Last <first.last@example.com>"],
"license": "XYZ",
"id": "",
"dependencies": [],
"targets": [],
"features": {},
"manifest_path": "C:\\Cargo.toml",
"metadata": {
"wix": {
"include": ["Cargo.toml"]
}
}
}"#;
let execution = Execution::default();
let sources = execution
.wxs_sources(&serde_json::from_str(PKG_META_WIX).unwrap())
.unwrap();
assert_eq!(sources, vec![PathBuf::from("Cargo.toml")]);
}
#[test]
fn compiler_args_override_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"compiler-args": ["-nologo"]
}
}"#;
let mut builder = Builder::default();
builder.compiler_args(Some(vec!["-ws"]));
let execution = builder.build();
let args = execution.compiler_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(args, Some(vec![String::from("-ws")]));
}
#[test]
fn compiler_args_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"compiler-args": ["-nologo", "-ws"]
}
}"#;
let execution = Execution::default();
let args = execution.compiler_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(
args,
Some(vec![String::from("-nologo"), String::from("-ws")])
);
}
#[test]
fn linker_args_override_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"linker-args": ["-nologo"]
}
}"#;
let mut builder = Builder::default();
builder.linker_args(Some(vec!["-ws"]));
let execution = builder.build();
let args = execution.linker_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(args, Some(vec![String::from("-ws")]));
}
#[test]
fn linker_args_metadata_works() {
const PKG_META_WIX: &str = r#"{
"wix": {
"linker-args": ["-nologo", "-ws"]
}
}"#;
let execution = Execution::default();
let args = execution.linker_args(&PKG_META_WIX.parse::<Value>().unwrap());
assert_eq!(
args,
Some(vec![String::from("-nologo"), String::from("-ws")])
);
}
const EMPTY_PKG_META_WIX: &str = r#"{"wix": {}}"#;
#[test]
fn culture_works() {
let execution = Execution::default();
let culture = execution
.culture(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert_eq!(culture, Cultures::EnUs);
}
#[test]
fn locale_works() {
let execution = Execution::default();
let locale = execution
.locale(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap())
.unwrap();
assert!(locale.is_none());
}
#[test]
fn no_build_works() {
let execution = Execution::default();
let no_build = execution.no_build(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap());
assert!(!no_build);
}
#[test]
fn target_bin_dir_overwrite_works() {
const EXPECTED: &str = "C:\\my-app\\fancy\\build";
let mut builder = Builder::new();
builder.target_bin_dir(Some(EXPECTED));
let execution = builder.build();
let target_directory = PathBuf::from("C:\\my-app\\target");
let target = execution.target().unwrap();
let profile = execution.profile(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap());
let target_bin_dir = execution.target_bin_dir(&target_directory, &target, &profile);
assert_eq!(target_bin_dir, PathBuf::from(EXPECTED));
}
#[test]
#[cfg(windows)]
fn target_bin_dir_computation_works() {
const EXPECTED: &str = "C:\\my-app\\target\\i686-pc-windows-msvc\\release";
let mut builder = Builder::new();
builder.target(Some("i686-pc-windows-msvc"));
builder.profile(Some("bench"));
let target_directory = PathBuf::from("C:\\my-app\\target");
let execution = builder.build();
let target = execution.target().unwrap();
let profile = execution.profile(&EMPTY_PKG_META_WIX.parse::<Value>().unwrap());
let target_bin_dir = execution.target_bin_dir(&target_directory, &target, &profile);
assert_eq!(target_bin_dir, PathBuf::from(EXPECTED));
}
#[test]
fn wixobj_destination_works() {
let execution = Execution::default();
assert_eq!(
execution.wixobj_destination(Path::new("target")),
PathBuf::from("target").join("wix")
)
}
}
mod wixobj_kind {
use super::*;
// I do not know if any of these XML strings are actually valid WiX
// Object (wixobj) files. These are just snippets to test the XPath
// evaluation.
const PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="product"></section>
</wixObject>"#;
const BUNDLE_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="bundle"></section>
</wixObject>"#;
const FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="fragment"></section>
</wixObject>"#;
const UNKNOWN_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="unknown"></section>
</wixObject>"#;
const BUNDLE_AND_PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="bundle"></section>
<section id="*" type="product"></section>
</wixObject>"#;
const PRODUCT_AND_FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
<wixObject version="3.0.2002.0" xmlns="http://schemas.microsoft.com/wix/2006/objects">
<section id="*" type="product"></section>
<section id="*" type="fragment"></section>
</wixObject>"#;
#[test]
fn try_from_product_object_works() {
assert_eq!(
WixObjKind::try_from(PRODUCT_WIXOBJ),
Ok(WixObjKind::Product)
);
}
#[test]
fn try_from_bundle_object_works() {
assert_eq!(WixObjKind::try_from(BUNDLE_WIXOBJ), Ok(WixObjKind::Bundle));
}
#[test]
fn try_from_fragment_object_works() {
assert_eq!(
WixObjKind::try_from(FRAGMENT_WIXOBJ),
Ok(WixObjKind::Fragment)
);
}
#[test]
fn try_from_bundle_and_product_object_works() {
assert_eq!(
WixObjKind::try_from(BUNDLE_AND_PRODUCT_WIXOBJ),
Ok(WixObjKind::Bundle)
);
}
#[test]
fn try_from_product_and_fragment_object_works() {
assert_eq!(
WixObjKind::try_from(PRODUCT_AND_FRAGMENT_WIXOBJ),
Ok(WixObjKind::Product)
);
}
#[test]
#[should_panic]
fn try_from_unknown_object_fails() {
WixObjKind::try_from(UNKNOWN_WIXOBJ).unwrap();
}
}
mod installer_kind {
use super::*;
#[test]
fn try_from_wixobj_single_product_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Product]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Bundle]),
Ok(InstallerKind::Exe)
)
}
#[test]
#[should_panic]
fn try_from_wixobj_single_fragment_fails() {
InstallerKind::try_from(vec![WixObjKind::Fragment]).unwrap();
}
#[test]
#[should_panic]
fn try_from_wixobj_multiple_fragments_fails() {
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Fragment,
])
.unwrap();
}
#[test]
fn try_from_wixobj_product_and_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![WixObjKind::Product, WixObjKind::Bundle]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_products_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_single_product_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product,
WixObjKind::Fragment
]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Bundle,
WixObjKind::Fragment
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_fragments_and_products_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product,
WixObjKind::Fragment,
WixObjKind::Product
]),
Ok(InstallerKind::Msi)
)
}
#[test]
fn try_from_wixobj_multiple_products_and_bundles_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
#[test]
fn try_from_wixobj_multiple_products_fragments_and_single_bundle_works() {
assert_eq!(
InstallerKind::try_from(vec![
WixObjKind::Product,
WixObjKind::Product,
WixObjKind::Bundle,
WixObjKind::Fragment,
WixObjKind::Fragment,
WixObjKind::Product
]),
Ok(InstallerKind::Exe)
)
}
}
}
================================================
FILE: src/initialize.rs
================================================
// Copyright (C) 2018 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The implementation for the `init` command. The `init` command for the `cargo
//! wix` subcommand is focused on creating a WiX Source file (wxs) based on the
//! contents of the Cargo manifest file (Cargo.toml) for the project and any
//! run-time based settings.
//!
//! The `init` command should generally be called before any other commands and
//! it should only be called once per project. Once a WiX Source file (wxs)
//! exists for the project, the `init` command does not need to be executed
//! again.
use camino::Utf8PathBuf;
use cargo_metadata::Package;
use crate::Error;
use crate::Result;
use crate::WIX;
use crate::WIX_SOURCE_FILE_EXTENSION;
use crate::WIX_SOURCE_FILE_NAME;
use crate::print;
use crate::stored_path::StoredPathBuf;
use crate::toolset::project::WxsSchema;
use log::{debug, info, trace};
use std::fs;
use std::path::{Path, PathBuf};
/// A builder for running the `cargo wix init` subcommand.
#[derive(Debug, Clone)]
pub struct Builder<'a> {
banner: Option<&'a str>,
binaries: Option<Vec<&'a str>>,
copyright_year: Option<&'a str>,
copyright_holder: Option<&'a str>,
description: Option<&'a str>,
dialog: Option<&'a str>,
eula: Option<&'a str>,
force: bool,
help_url: Option<&'a str>,
input: Option<&'a str>,
license: Option<&'a str>,
manufacturer: Option<&'a str>,
output: Option<&'a str>,
package: Option<&'a str>,
path_guid: Option<&'a str>,
product_icon: Option<&'a str>,
product_name: Option<&'a str>,
upgrade_guid: Option<&'a str>,
schema: Option<WxsSchema>,
}
impl<'a> Builder<'a> {
/// Creates a new `Builder` instance.
pub fn new() -> Self {
Builder {
banner: None,
binaries: None,
copyright_year: None,
copyright_holder: None,
description: None,
dialog: None,
eula: None,
force: false,
help_url: None,
input: None,
license: None,
manufacturer: None,
output: None,
package: None,
path_guid: None,
product_icon: None,
product_name: None,
upgrade_guid: None,
schema: None,
}
}
/// Sets the path to a bitmap (BMP) file to be used as a banner image across
/// the top of each dialog in the installer.
///
/// The banner image must be 493 x 58 pixels. See the [Wix Toolset
/// documentation] for details about [customization].
///
/// [Wix Toolset documentation]: http://wixtoolset.org/documentation/
/// [customization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_customizations.html
pub fn banner(&mut self, b: Option<&'a str>) -> &mut Self {
self.banner = b;
self
}
/// Sets the path to the binaries.
///
/// The default is to, first, collect all of the `bin` sections and use the
/// `name` field within each `bin` section of the package's manifest for
/// each binary's name and create the following source with the `.exe` file
/// extension: `target\release\<binary-name>.exe`, where `<binary-name>` is
/// replaced with the name obtained from each `bin` section. All binaries
/// are included in the installer. If no `bin` sections exist, then the
/// package's `name` field is used and only one binary is included in the
/// installer.
///
/// This method skips creating the binary names and sources from the
/// package's manifest (Cargo.toml) and uses the supplied paths, regardless
/// of the number of `bin` sections in the package's manifest. The binary
/// name is extracted from each supplied path as the file stem (file name
/// without extension).
///
/// This method is useful for including binaries, a.k.a. executables, in the
/// installer that are necessary for the application to run but are not
/// necessarily Rust/Cargo built binaries. However, this method overrides
/// _all_ binaries in the Cargo-based project, so if the installer is to
/// include a mixture of external and internal binaries, the internal
/// binaries must be explicitly included in this method.
pub fn binaries(&mut self, b: Option<Vec<&'a str>>) -> &mut Self {
self.binaries = b;
self
}
/// Sets the copyright holder for the generated license file and EULA.
///
/// The default is to use the `authors` field of the
/// package's manifest (Cargo.toml). This method can be used to override the
/// default and set a different copyright holder if and when a Rich Text
/// Format (RTF) license and EULA are generated based on the value of the
/// `license` field in the package's manifest (Cargo.toml).
///
/// This value is ignored and not used if an EULA is set with the [`eula`]
/// method, if a custom EULA is set using the `license-file` field in the
/// package's manifest (Cargo.toml), or an EULA is _not_ generated from the
/// `license` field in the package's manifest (Cargo.toml).
///
/// [`eula`]: https://volks73.github.io/cargo-wix/cargo_wix/initialize.html#eula
pub fn copyright_holder(&mut self, h: Option<&'a str>) -> &mut Self {
self.copyright_holder = h;
self
}
/// Sets the copyright year for the generated license file and EULA.
///
/// The default is to use the current year. This method can be used to
/// override the default and set a specific year if and when a Rich Text
/// Format (RTF) license and EULA are generated based on the value of the
/// `license` field in the package's manifest (Cargo.toml).
///
/// This value is ignored and not used if an EULA is set with the [`eula`]
/// method, if a custom EULA is set using the `license-file` field in the
/// package's manifest (Cargo.toml), or an EULA is _not_ generated from the
/// `license` field in the package's manifest (Cargo.toml).
///
/// [`eula`]: https://volks73.github.io/cargo-wix/cargo_wix/initialize.html#eula
pub fn copyright_year(&mut self, y: Option<&'a str>) -> &mut Self {
self.copyright_year = y;
self
}
/// Sets the description.
///
/// This overrides the description determined from the `description` field
/// in the package's manifest (Cargo.toml).
pub fn description(&mut self, d: Option<&'a str>) -> &mut Self {
self.description = d;
self
}
/// Sets the path to a bitmap (`.bmp`) file that will be displayed on the
/// first dialog to the left.
///
/// The image must be 493 x 312 pixels. See the [Wix Toolset
/// documentation] for details about [customization].
///
/// [Wix Toolset documentation]: http://wixtoolset.org/documentation/
/// [customization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_customizations.html
pub fn dialog(&mut self, d: Option<&'a str>) -> &mut Self {
self.dialog = d;
self
}
/// Sets the path to a custom End User License Agreement (EULA).
///
/// The EULA is the text that appears in the license agreement dialog of the
/// installer, where a checkbox is present for the user to agree to the
/// terms of the license. Typically, this is the same as the license file
/// that is included as a [sidecar] file in the installation destination of
/// the executable.
///
/// The default is to generate an EULA from an embedded template as a RTF
/// file based on the name of the license specified in the `license` field
/// of the package's manifest (Cargo.toml). This method can be used to
/// override the default and specify a custom EULA. A custom EULA must be in
/// the RTF format and have the `.rtf` file extension.
///
/// If the `license` field is not specified or a template for the license
/// does not exist but the `license-file` field does specify a path to a
/// file with the RTF extension, then that RTF file is used as the EULA for
/// the license agreement dialog in the installer. Finally, if the
/// `license-file` does not exist or it specifies a file that does not have
/// the `.rtf` extension, then the license agreement dialog is skipped and
/// there is no EULA for the installer. This would override the default
/// behavior and ensure the license agreement dialog is used.
///
/// [sidecar]: https://en.wikipedia.org/wiki/Sidecar_file
pub fn eula(&mut self, e: Option<&'a str>) -> &mut Self {
self.eula = e;
self
}
/// Forces the generation of new output even if the various outputs already
/// exists at the destination.
pub fn force(&mut self, f: bool) -> &mut Self {
self.force = f;
self
}
/// Sets the help URL.
///
/// The default is to obtain a URL from one of the following fields in the
/// package's manifest (Cargo.toml): `documentation`, `homepage`, or
/// `repository`. If none of these are specified, then the default is to
/// exclude a help URL from the installer. This will override the default
/// behavior and provide a help URL for the installer if none of the fields
/// exist.
///
/// The help URL is the URL that appears in the Add/Remove Program control
/// panel, a.k.a. `ARPHELPLINK`.
pub fn help_url(&mut self, h: Option<&'a str>) -> &mut Self {
self.help_url = h;
self
}
/// Sets the path to a package's manifest (Cargo.toml) to be used to
/// generate a WiX Source (wxs) file from the embedded template.
///
/// A `wix` and `wix\main.wxs` file will be created in the same directory as
/// the package's manifest. The default is to use the package's manifest in
/// the current working directory.
pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
self.input = i;
self
}
/// Sets the path to a file to be used as the [sidecar] license file.
///
/// This will override the `license-file` field in the package's manifest
/// (Cargo.toml). If the file has the `.rtf` extension, then it will also be
/// used for the EULA in the license agreement dialog for the installer.
/// Otherwise, the [`eula`] method can be used to set an RTF file as the
/// EULA for the license agreement dialog that is independent of the sidecar
/// license file.
///
/// The default is to use the value specified in the `license-file` field of
/// the package's manifest or generate a license file and EULA from an
/// embedded template based on the license ID used in the `license` field
/// of the package's manifest. If none of these fields are specified or
/// overridden, then a license file is _not_ included in the installation
/// directory and the license agreement dialog is skipped in the installer.
///
/// [sidecar]: https://en.wikipedia.org/wiki/Sidecar_file
/// [`eula`]: #eula
pub fn license(&mut self, l: Option<&'a str>) -> &mut Self {
self.license = l;
self
}
/// Sets the manufacturer.
///
/// Default is to use the `authors` field of the
/// package's manifest (Cargo.toml). This would override the default value.
pub fn manufacturer(&mut self, m: Option<&'a str>) -> &mut Self {
self.manufacturer = m;
self
}
/// Sets the destination for creating all of the output from initialization.
///
/// The default is to create all initialization output in the same folder as
/// the package's manifest (Cargo.toml). Thus, a `wix` folder will be
/// created within the same folder as the `Cargo.toml` file and all
/// initialization created files will be placed in the `wix` folder.
///
/// This method can be used to override the default output destination and
/// have the files related to creating an installer placed in a different
/// location inside or outside of the package's project folder.
pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
self.output = o;
self
}
/// Sets the path to an image file to be used for product icon.
///
/// The product icon is the icon that appears for an installed application
/// in the Add/Remove Programs (ARP) control panel. If a product icon is
/// _not_ defined for an application within the installer, then the Windows
/// OS assigns a generic one.
pub fn product_icon(&mut self, p: Option<&'a str>) -> &mut Self {
self.product_icon = p;
self
}
/// Sets the package within a workspace to initialize an installer.
///
/// Each package within a workspace has its own package manifest, i.e.
/// `Cargo.toml`. This indicates within package manifest within a workspace
/// should be used when initializing an installer.
pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
self.package = p;
self
}
/// Sets the GUID for the path component.
///
/// The default automatically generates the GUID needed for the path
/// component. A GUID is needed so that the path component can be
/// successfully removed on uninstall.
///
/// Generally, the path component GUID should be generated only once per
/// project/product and then the same GUID used every time the installer is
/// created. The GUID is stored in the WiX Source (WXS) file. However,
/// this allows using an existing GUID, possibly obtained with another tool.
pub fn path_guid(&mut self, p: Option<&'a str>) -> &mut Self {
self.path_guid = p;
self
}
/// Sets the product name.
///
/// The default is to use the `name` field under the `package` section of
/// the package's manifest (Cargo.toml). This overrides that value. An error
/// occurs if the `name` field is not found in the manifest.
///
/// The product name is also used for the disk prompt during installation
/// and the name of the default installation destination. For example, a
/// product anme of `Example` will have an installation destination of
/// `C:\Program Files\Example` as the default during installation.
pub fn product_name(&mut self, p: Option<&'a str>) -> &mut Self {
self.product_name = p;
self
}
/// Sets the Upgrade Code GUID.
///
/// The default automatically generates the need GUID for the `UpgradeCode`
/// attribute to the `Product` tag. The Upgrade Code uniquely identifies the
/// installer. It is used to determine if the new installer is the same
/// product and the current installation should be removed and upgraded to
/// this version. If the GUIDs of the current product and new product do
/// _not_ match, then Windows will treat the two installers as separate
/// products.
///
/// Generally, the upgrade code should be generated only one per
/// project/product and then the same code used every time the installer is
/// created and the GUID is stored in the WiX Source (WXS) file. However,
/// this allows the user to provide an existing GUID for the upgrade code.
pub fn upgrade_guid(&mut self, u: Option<&'a str>) -> &mut Self {
self.upgrade_guid = u;
self
}
/// Sets the Wxs Schema
///
/// Wix3 follows the "v3" schema, where as Wix4 and beyond use the "Modern" schema uri.
pub fn schema(&mut self, schema: Option<WxsSchema>) -> &mut Self {
self.schema = schema;
self
}
/// Builds a read-only initialization execution.
pub fn build(&mut self) -> Execution {
Execution {
banner: self.banner.map(StoredPathBuf::from),
binaries: self
.binaries
.as_ref()
.map(|b| b.iter().copied().map(StoredPathBuf::from).collect()),
copyright_year: self.copyright_year.map(String::from),
copyright_holder: self.copyright_holder.map(String::from),
description: self.description.map(String::from),
dialog: self.dialog.map(StoredPathBuf::from),
eula: self.eula.map(StoredPathBuf::from),
force: self.force,
help_url: self.help_url.map(String::from),
input: self.input.map(PathBuf::from),
license: self.license.map(StoredPathBuf::from),
manufacturer: self.manufacturer.map(String::from),
output: self.output.map(PathBuf::from),
package: self.package.map(String::from),
path_guid: self.path_guid.map(String::from),
product_icon: self.product_icon.map(StoredPathBuf::from),
product_name: self.product_name.map(String::from),
upgrade_guid: self.upgrade_guid.map(String::from),
schema: self.schema.unwrap_or(WxsSchema::Legacy),
}
}
}
impl Default for Builder<'_> {
fn default() -> Self {
Builder::new()
}
}
/// A context for creating the necessary files to eventually build an installer.
#[derive(Debug)]
pub struct Execution {
banner: Option<StoredPathBuf>,
binaries: Option<Vec<StoredPathBuf>>,
copyright_holder: Option<String>,
copyright_year: Option<String>,
description: Option<String>,
dialog: Option<StoredPathBuf>,
eula: Option<StoredPathBuf>,
force: bool,
help_url: Option<String>,
input: Option<PathBuf>,
license: Option<StoredPathBuf>,
manufacturer: Option<String>,
output: Option<PathBuf>,
package: Option<String>,
path_guid: Option<String>,
product_icon: Option<StoredPathBuf>,
product_name: Option<String>,
upgrade_guid: Option<String>,
schema: WxsSchema,
}
impl Execution {
/// Generates the necessary files to eventually create, or build, an
/// installer based on a built context.
pub fn run(self) -> Result<()> {
debug!("banner = {:?}", self.banner);
debug!("binaries = {:?}", self.binaries);
debug!("copyright_holder = {:?}", self.copyright_holder);
debug!("copyright_year = {:?}", self.copyright_year);
debug!("description = {:?}", self.description);
debug!("dialog = {:?}", self.dialog);
debug!("eula = {:?}", self.eula);
debug!("force = {:?}", self.force);
debug!("help_url = {:?}", self.help_url);
debug!("input = {:?}", self.input);
debug!("license = {:?}", self.license);
debug!("manufacturer = {:?}", self.manufacturer);
debug!("output = {:?}", self.output);
debug!("package = {:?}", self.package);
debug!("path_guid = {:?}", self.path_guid);
debug!("product_icon = {:?}", self.product_icon);
debug!("product_name = {:?}", self.product_name);
debug!("upgrade_guid = {:?}", self.upgrade_guid);
let manifest = super::manifest(self.input.as_ref())?;
let package = super::package(&manifest, self.package.as_deref())?;
let mut destination = self.destination(&package);
debug!("destination = {:?}", destination);
if !destination.exists() {
info!("Creating the '{}' directory", destination);
fs::create_dir(&destination)?;
}
destination.push(WIX_SOURCE_FILE_NAME);
destination.set_extension(WIX_SOURCE_FILE_EXTENSION);
if destination.exists() && !self.force {
return Err(Error::already_exists(&destination));
} else {
info!("Creating the '{}' file", destination);
let mut wxs_printer = print::wxs::Builder::new();
wxs_printer.banner(self.banner.as_ref().map(|s| s.as_str()));
wxs_printer.binaries(
self.binaries
.as_ref()
.map(|b| b.iter().map(|s| s.as_str()).collect()),
);
wxs_printer.description(self.description.as_ref().map(String::as_ref));
wxs_printer.dialog(self.dialog.as_deref().map(|s| s.as_str()));
wxs_printer.eula(self.eula.as_deref().map(|p| p.as_str()));
wxs_printer.help_url(self.help_url.as_ref().map(String::as_ref));
wxs_printer.input(self.input.as_deref().and_then(Path::to_str));
wxs_printer.license(self.license.as_deref().map(|p| p.as_str()));
wxs_printer.manufacturer(self.manufacturer.as_ref().map(String::as_ref));
wxs_printer.output(Some(destination.as_str()));
wxs_printer.package(self.package.as_deref());
wxs_printer.path_guid(self.path_guid.as_ref().map(String::as_ref));
wxs_printer.product_icon(self.product_icon.as_ref().map(|s| s.as_str()));
wxs_printer.product_name(self.product_name.as_ref().map(String::as_ref));
wxs_printer.upgrade_guid(self.upgrade_guid.as_ref().map(String::as_ref));
wxs_printer.schema(Some(self.schema));
wxs_printer.build().run()?;
}
Ok(())
}
fn destination(&self, package: &Package) -> Utf8PathBuf {
if let Some(output) = &self.output {
trace!("An output path has been explicitly specified");
Utf8PathBuf::from_path_buf(output.to_owned()).unwrap()
} else {
trace!(
"An output path has NOT been explicitly specified. Implicitly determine output from manifest location."
);
package
.manifest_path
.parent()
.map(|p| p.to_path_buf())
.map(|mut p| {
p.push(WIX);
p
})
.unwrap()
}
}
}
impl Default for Execution {
fn default() -> Self {
Builder::new().build()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod builder {
use super::*;
const PATH_GUID: &str = "0B5DFC00-1480-4044-AC1A-BEF00E0A91BB";
const UPGRADE_GUID: &str = "0631BBDF-4079-4C20-823F-7EA8DE40BF08";
#[test]
fn defaults_are_correct() {
let actual = Builder::new();
assert!(actual.banner.is_none());
assert!(actual.binaries.is_none());
assert!(actual.copyright_year.is_none());
assert!(actual.copyright_holder.is_none());
assert!(actual.description.is_none());
assert!(actual.dialog.is_none());
assert!(actual.eula.is_none());
assert!(!actual.force);
assert!(actual.help_url.is_none());
assert!(actual.input.is_none());
assert!(actual.license.is_none());
assert!(actual.manufacturer.is_none());
assert!(actual.output.is_none());
assert!(actual.path_guid.is_none());
assert!(actual.product_icon.is_none());
assert!(actual.product_name.is_none());
assert!(actual.upgrade_guid.is_none());
assert!(actual.schema.is_none());
}
#[test]
fn banner_works() {
const EXPECTED: &str = "img\\Banner.bmp";
let mut actual = Builder::new();
actual.banner(Some(EXPECTED));
assert_eq!(actual.banner, Some(EXPECTED));
}
#[test]
fn binaries_works() {
const EXPECTED: &str = "bin\\Example.exe";
let mut actual = Builder::new();
actual.binaries(Some(vec![EXPECTED]));
assert_eq!(actual.binaries, Some(vec![EXPECTED]));
}
#[test]
fn copyright_holder_works() {
const EXPECTED: &str = "holder";
let mut actual = Builder::new();
actual.copyright_holder(Some(EXPECTED));
assert_eq!(actual.copyright_holder, Some(EXPECTED));
}
#[test]
fn copyright_year_works() {
const EXPECTED: &str = "2018";
let mut actual = Builder::new();
actual.copyright_year(Some(EXPECTED));
assert_eq!(actual.copyright_year, Some(EXPECTED));
}
#[test]
fn description_works() {
const EXPECTED: &str = "description";
let mut actual = Builder::new();
actual.description(Some(EXPECTED));
assert_eq!(actual.description, Some(EXPECTED));
}
#[test]
fn dialog_works() {
const EXPECTED: &str = "img\\Dialog.bmp";
let mut actual = Builder::new();
actual.dialog(Some(EXPECTED));
assert_eq!(actual.dialog, Some(EXPECTED));
}
#[test]
fn eula_works() {
const EXPECTED: &str = "eula.rtf";
let mut actual = Builder::new();
actual.eula(Some(EXPECTED));
assert_eq!(actual.eula, Some(EXPECTED));
}
#[test]
fn force_works() {
let mut actual = Builder::new();
actual.force(true);
assert!(actual.force);
}
#[test]
fn help_url_works() {
const EXPECTED: &str = "http://github.com/volks73/cargo-wix";
let mut actual = Builder::new();
actual.help_url(Some(EXPECTED));
assert_eq!(actual.help_url, Some(EXPECTED));
}
#[test]
fn input_works() {
const EXPECTED: &str = "input.wxs";
let mut actual = Builder::new();
actual.input(Some(EXPECTED));
assert_eq!(actual.input, Some(EXPECTED));
}
#[test]
fn license_works() {
const EXPECTED: &str = "License.txt";
let mut actual = Builder::new();
actual.license(Some(EXPECTED));
assert_eq!(actual.license, Some(EXPECTED));
}
#[test]
fn manufacturer_works() {
const EXPECTED: &str = "manufacturer";
let mut actual = Builder::new();
actual.manufacturer(Some(EXPECTED));
assert_eq!(actual.manufacturer, Some(EXPECTED));
}
#[test]
fn output_works() {
const EXPECTED: &str = "output";
let mut actual = Builder::new();
actual.output(Some(EXPECTED));
assert_eq!(actual.output, Some(EXPECTED));
}
#[test]
fn path_guid_works() {
let mut actual = Builder::new();
actual.path_guid(Some(PATH_GUID));
assert_eq!(actual.path_guid, Some(PATH_GUID));
}
#[test]
fn product_icon_works() {
const EXPECTED: &str = "img\\Product.ico";
let mut actual = Builder::new();
actual.product_icon(Some(EXPECTED));
assert_eq!(actual.product_icon, Some(EXPECTED));
}
#[test]
fn product_name_works() {
const EXPECTED: &str = "product name";
let mut actual = Builder::new();
actual.product_name(Some(EXPECTED));
assert_eq!(actual.product_name, Some(EXPECTED));
}
#[test]
fn upgrade_guid_works() {
let mut actual = Builder::new();
actual.upgrade_guid(Some(UPGRADE_GUID));
assert_eq!(actual.upgrade_guid, Some(UPGRADE_GUID));
}
#[test]
fn schema_works() {
use crate::toolset::project::WxsSchema;
let mut actual = Builder::new();
actual.schema(Some(WxsSchema::V4));
assert_eq!(actual.schema, Some(WxsSchema::V4));
}
#[test]
fn schema_defaults_to_legacy_in_execution() {
use crate::toolset::project::WxsSchema;
let mut b = Builder::new();
let execution = b.build();
assert!(matches!(execution.schema, WxsSchema::Legacy));
}
#[test]
fn build_with_defaults_works() {
let mut b = Builder::new();
let default_execution = b.build();
assert!(default_execution.binaries.is_none());
assert!(default_execution.copyright_year.is_none());
assert!(default_execution.copyright_holder.is_none());
assert!(default_execution.description.is_none());
assert!(default_execution.eula.is_none());
assert!(!default_execution.force);
assert!(default_execution.help_url.is_none());
assert!(default_execution.input.is_none());
assert!(default_execution.license.is_none());
assert!(default_execution.manufacturer.is_none());
assert!(default_execution.output.is_none());
assert!(default_execution.product_icon.is_none());
assert!(default_execution.product_name.is_none());
assert!(default_execution.upgrade_guid.is_none());
assert!(matches!(
default_execution.schema,
crate::toolset::project::WxsSchema::Legacy
));
}
#[test]
fn build_with_all_works() {
const EXPECTED_BINARY: &str = "bin\\Example.exe";
const EXPECTED_COPYRIGHT_HOLDER: &str = "Copyright Holder";
const EXPECTED_COPYRIGHT_YEAR: &str = "Copyright Year";
const EXPECTED_DESCRIPTION: &str = "Description";
const EXPECTED_EULA: &str = "C:\\tmp\\eula.rtf";
const EXPECTED_URL: &str = "http://github.com/volks73/cargo-wix";
const EXPECTED_INPUT: &str = "C:\\tmp\\hello_world";
const EXPECTED_LICENSE: &str = "C:\\tmp\\hello_world\\License.rtf";
const EXPECTED_MANUFACTURER: &str = "Manufacturer";
const EXPECTED_OUTPUT: &str = "C:\\tmp\\output";
const EXPECTED_PRODUCT_ICON: &str = "img\\Product.ico";
const EXPECTED_PRODUCT_NAME: &str = "Product Name";
let mut b = Builder::new();
b.binaries(Some(vec![EXPECTED_BINARY]));
b.copyright_holder(Some(EXPECTED_COPYRIGHT_HOLDER));
b.copyright_year(Some(EXPECTED_COPYRIGHT_YEAR));
b.description(Some(EXPECTED_DESCRIPTION));
b.eula(Some(EXPECTED_EULA));
b.force(true);
b.help_url(Some(EXPECTED_URL));
b.input(Some(EXPECTED_INPUT));
b.license(Some(EXPECTED_LICENSE));
b.manufacturer(Some(EXPECTED_MANUFACTURER));
b.output(Some(EXPECTED_OUTPUT));
b.path_guid(Some(PATH_GUID));
b.product_icon(Some(EXPECTED_PRODUCT_ICON));
b.product_name(Some(EXPECTED_PRODUCT_NAME));
b.upgrade_guid(Some(UPGRADE_GUID));
let execution = b.build();
assert_eq!(
execution.binaries,
Some(vec![EXPECTED_BINARY])
.map(|s| s.into_iter().map(StoredPathBuf::from).collect())
);
assert_eq!(
execution.copyright_year,
Some(String::from(EXPECTED_COPYRIGHT_YEAR))
);
assert_eq!(
execution.copyright_holder,
Some(String::from(EXPECTED_COPYRIGHT_HOLDER))
);
assert_eq!(
execution.description,
Some(String::from(EXPECTED_DESCRIPTION))
);
assert_eq!(execution.eula, Some(StoredPathBuf::from(EXPECTED_EULA)));
assert!(execution.force);
assert_eq!(execution.help_url, Some(String::from(EXPECTED_URL)));
assert_eq!(execution.input, Some(PathBuf::from(EXPECTED_INPUT)));
assert_eq!(
execution.license,
Some(StoredPathBuf::from(EXPECTED_LICENSE))
);
assert_eq!(
execution.manufacturer,
Some(String::from(EXPECTED_MANUFACTURER))
);
assert_eq!(execution.output, Some(PathBuf::from(EXPECTED_OUTPUT)));
assert_eq!(execution.path_guid, Some(String::from(PATH_GUID)));
assert_eq!(
execution.product_icon,
Some(StoredPathBuf::from(EXPECTED_PRODUCT_ICON))
);
assert_eq!(
execution.product_name,
Some(String::from(EXPECTED_PRODUCT_NAME))
);
assert_eq!(execution.upgrade_guid, Some(String::from(UPGRADE_GUID)));
}
}
#[cfg(windows)]
mod execution {
extern crate assert_fs;
use super::*;
use serial_test::serial;
use std::env;
const MIN_PACKAGE: &str = r#"[package]
name = "cargowixtest"
version = "1.0.0"
"#;
#[test]
#[serial]
fn destination_is_correct_with_defaults() {
let original = env::current_dir().unwrap();
let temp_dir = crate::tests::setup_project(MIN_PACKAGE);
env::set_current_dir(temp_dir.path()).unwrap();
let mut expected = env::current_dir().unwrap();
expected.push(WIX);
let e = Execution::default();
let result = crate::manifest(None)
.and_then(|manifest| crate::package(&manifest, None))
.map(|package| e.destination(&package));
env::set_current_dir(original).unwrap();
let actual = result.unwrap();
assert_eq!(actual, expected);
}
#[test]
#[serial]
fn destination_is_correct_with_output() {
let expected = PathBuf::from("output");
let temp_dir = crate::tests::setup_project(MIN_PACKAGE);
let e = Execution {
output: Some(expected.clone()),
..Default::default()
};
let actual = crate::manifest(Some(&temp_dir.path().join("Cargo.toml")))
.and_then(|manifest| crate::package(&manifest, None))
.map(|package| e.destination(&package))
.unwrap();
assert_eq!(actual, expected);
}
}
}
================================================
FILE: src/lib.rs
================================================
// Copyright (C) 2017 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # `wix` Library
//!
//! The goal of the cargo-wix project and the `wix` library is to make it easy
//! to create a Windows installer (msi) for any Rust project. The cargo-wix
//! project is primarily implemented as a [cargo subcommand], but its core
//! functionality has been organized into a separate library. Documentation for
//! the binary and Command Line Interface (CLI) are provided in the module-level
//! documentation for the [binary] and the `cargo wix --help` command.
//!
//! ## Table of Contents
//!
//! - [Usage](#usage)
//! - [Organization](#organization)
//!
//! ## Usage
//!
//! First, add this to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! wix = "0.1"
//! ```
//!
//! Next, if not using Rust 2018 edition, then add this to the `lib.rs` or
//! `main.rs` file for your project:
//!
//! ```ignore
//! extern crate wix;
//! ```
//!
//! ## Organization
//!
//! Each subcommand is organized into a separate module. So, there is a
//! `create`, `initialize`, `print`, etc. module within the crate. Some of the
//! modules are in a single Rust source file, while others are organized into
//! sub-folders. Each module follows the [Builder] design pattern, so there is a
//! `Builder` and `Execution` struct for each module/subcommand. The `Builder`
//! struct is used to customize the execution of the subcommand and builds an
//! `Execution` struct. The `Execution` struct is responsible for actually
//! executing the subcommand, which generally involves executing a process with
//! the [`std::process::Command`] struct, but not always. Each method for the
//! `Builder` struct generally corresponds to a CLI option or argument found in
//! the [`cargo wix`] subcommand and binary.
//!
//! [binary]: ../cargo_wix/index.html
//! [Builder]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
//! [cargo subcommand]: https://github.com/rust-lang/cargo/wiki/Third-party-cargo-subcommands
//! [`cargo wix`]: ../cargo_wix/index.html
//! [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
pub use crate::templates::Template;
pub mod clean;
pub mod create;
pub mod initialize;
mod licenses;
pub mod migrate;
pub mod print;
pub mod purge;
pub mod sign;
pub mod stored_path;
mod templates;
pub mod toolset;
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;
/// The name of the folder where binaries are typically stored.
pub const BINARY_FOLDER_NAME: &str = "bin";
/// The file name with extension for a package's manifest.
pub const CARGO_MANIFEST_FILE: &str = "Cargo.toml";
/// The name of the builder application for a Rust project.
pub const CARGO: &str = "cargo";
/// The file extension for an executable.
pub const EXE_FILE_EXTENSION: &str = "exe";
/// The file name without an extension when generating a license.
pub const LICENSE_FILE_NAME: &str = "License";
/// The file extension for a Windows installer.
pub const MSI_FILE_EXTENSION: &str = "msi";
/// The file extension for a Rich Text Format (RTF) file.
pub const RTF_FILE_EXTENSION: &str = "rtf";
/// The name of the signer application from the Windows SDK.
pub const SIGNTOOL: &str = "signtool";
/// The name of the environment variable to specify the path to the signer
/// application.
pub const SIGNTOOL_PATH_KEY: &str = "SIGNTOOL_PATH";
/// The default name of the folder for output from this subcommand.
pub const WIX: &str = "wix";
/// The application name without the file extension of the compiler for the
/// Windows installer.
pub const WIX_COMPILER: &str = "candle";
/// The application name without the file extension of the linker for the
/// Windows installer.
pub const WIX_LINKER: &str = "light";
/// The application name of the modern toolset binary
pub const WIX_MODERN_TOOLSET: &str = "wix";
/// The application name without the file extension of the `msiexec` utility.
pub const MSIEXEC: &str = "msiexec";
/// The file extension for a WiX Toolset object file, which is the output from
/// the WiX compiler.
pub const WIX_OBJECT_FILE_EXTENSION: &str = "wixobj";
/// The name of the environment variable created by the WiX Toolset installer
/// that points to the `bin` folder for the WiX Toolet's compiler (candle.exe)
/// and linker (light.exe).
pub const WIX_PATH_KEY: &str = "WIX";
/// The file extension of the WiX Source file, which is the input to the WiX
/// Toolset compiler.
pub const WIX_SOURCE_FILE_EXTENSION: &str = "wxs";
/// The default file name for the WiX Source file, which is the input to the WiX
/// Toolset compiler.
pub const WIX_SOURCE_FILE_NAME: &str = "main";
/// A specialized [`Result`] type for wix operations.
///
/// [`Result`]: https://doc.rust-lang.org/std/result/
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 {
// TODO: Replace error with creating installers for all packages in a
// workspace. I think this currently means that to create installers for
// all packages in workspace, a `cargo wix --package <name>` must be
// executed for each workspace member.
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.to_string()
}
}
/// The error type for wix-related operations and associated traits.
///
/// Errors mostly originate from the dependencies, but custom instances of `Error` can be created
/// with the `Generic` variant and a message.
#[derive(Debug)]
pub enum Error {
/// Parsing of Cargo metadata failed.
CargoMetadata(cargo_metadata::Error),
/// A command operation failed.
Command(&'static str, i32, bool),
/// A generic or custom error occurred. The message should contain the detailed information.
Generic(String),
/// An I/O operation failed.
Io(io::Error),
/// A needed field within the `Cargo.toml` manifest could not be found.
Manifest(&'static str),
/// An error occurred with rendering the template using the mustache renderer.
Mustache(mustache::Error),
/// UUID generation or parsing failed.
Uuid(uuid::Error),
/// Parsing error for a version string or field.
Version(semver::Error),
/// Parsing the intermediate WiX Object (wixobj) file, which is XML, failed.
Xml(sxd_document::parser::Error),
/// Evaluation of an XPath expression failed.
XPath(sxd_xpath::Error),
/// Could not convert bytes to utf8
Utf8Error(std::string::FromUtf8Error),
}
impl Error {
/// Gets an error code related to the error.
///
/// # Examples
///
/// ```rust
/// use wix::Error;
///
/// let err = Error::from("A generic error");
/// assert_ne!(err.code(), 0)
/// ```
///
/// This is useful as a return, or exit, code for a command line application, where a non-zero
/// integer indicates a failure in the application. it can also be used for quickly and easily
/// testing equality between two errors.
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,
Error::Utf8Error(..) => 11,
}
}
/// Creates a new `Error` from a [std::io::Error] with the
/// [std::io::ErrorKind::AlreadyExists] variant.
///
/// # Examples
///
/// ```rust
/// use std::io;
/// use camino::Utf8Path;
/// use wix::Error;
///
/// let path = Utf8Path::new("C:\\");
/// let expected = Error::Io(io::Error::new(
/// io::ErrorKind::AlreadyExists,
/// path.to_string()
/// ));
/// assert_eq!(expected, Error::already_exists(path));
/// ```
///
/// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
/// [std::io::ErrorKind::AlreadyExists]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
pub fn already_exists(p: &Utf8Path) -> Self {
io::Error::new(ErrorKind::AlreadyExists, p.to_string()).into()
}
/// Creates a new `Error` from a [std::io::Error] with the
/// [std::io::ErrorKind::NotFound] variant.
///
/// # Examples
///
/// ```rust
/// use std::io;
/// use std::path::Path;
/// use wix::Error;
///
/// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
/// let expected = Error::Io(io::Error::new(
/// io::ErrorKind::NotFound,
/// path.display().to_string()
/// ));
/// assert_eq!(expected, Error::not_found(path));
/// ```
///
/// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
/// [std::io::ErrorKind::NotFound]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
pub fn not_found(p: &Path) -> Self {
io::Error::new(ErrorKind::NotFound, p.display().to_string()).into()
}
/// Creates a new `Error` from a [std::io::Error] with the
/// [std::io::ErrorKind::InvalidInput] variant if a path is not a file.
///
/// # Examples
///
/// ```rust
/// use std::io;
/// use std::path::Path;
/// use wix::Error;
///
/// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
/// let expected = Error::Io(io::Error::new(
/// io::ErrorKind::InvalidInput,
/// format!("The '{}' path is not a file.", path.display())
/// ));
/// assert_eq!(expected, Error::not_a_file(path));
/// ```
///
/// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
/// [std::io::ErrorKind::InvalidInput]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
pub fn not_a_file(p: &Path) -> Self {
io::Error::new(
ErrorKind::InvalidInput,
format!("The '{}' path is not a file.", p.display()),
)
.into()
}
/// Creates a new `Error` from a [std::io::Error] with the
/// [std::io::ErrorKind::InvalidInput] variant if a path is not to a
/// `Cargo.toml` file.
///
/// # Examples
///
/// ```rust
/// use std::io;
/// use std::path::Path;
/// use wix::Error;
///
/// let path = Path::new("C:\\Cargo\\Wix\\file.txt");
/// let expected = Error::Io(io::Error::new(
/// io::ErrorKind::InvalidInput,
/// format!(
/// "The '{}' path does not appear to be to a 'Cargo.toml' file.",
/// path.display(),
/// ),
/// ));
/// assert_eq!(expected, Error::not_a_manifest(path));
/// ```
///
/// [std::io::Error]: https://doc.rust-lang.org/std/io/struct.Error.html
/// [std::io::ErrorKind::InvalidInput]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
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()
}
/// Extracts a short, single word representation of the error.
///
/// The `std::error::Error::description` method is "soft-deprecated"
/// according to the Rust stdlib documentation. It is recommended to use the
/// `std::fmt::Display` implementation for a "description" string. However,
/// there is already a `std::fmt::Display` implementation for this error
/// type, and it is nice to have a short, single word representation for
/// nicely formatting errors to humans. This method maintains the error
/// message formatting.
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",
Error::Utf8Error(..) => "Utf8",
}
}
}
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),
Error::Utf8Error(ref err) => err.fmt(f),
}
}
}
impl PartialEq for Error {
fn eq(&self, other: &Error) -> bool {
self.code() == other.code()
}
}
impl From<&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<uuid::Error> for Error {
fn from(err: uuid::Error) -> Self {
Error::Uuid(err)
}
}
impl From<sxd_xpath::Error> for Error {
fn from(value: sxd_xpath::Error) -> Self {
Error::XPath(value)
}
}
impl From<sxd_xpath::ExecutionError> for Error {
fn from(value: sxd_xpath::ExecutionError) -> Self {
Error::XPath(sxd_xpath::Error::Executing(value))
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(value: std::string::FromUtf8Error) -> Self {
Error::Utf8Error(value)
}
}
/// The different architectures supported by the WiX Toolset.
///
/// These are also the valid values for the `-arch` option to the WiX compiler
/// (candle.exe).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum WixArch {
/// The x86 32-bit architecture.
X86,
/// The x86_64 or AMD64 64-bit architecture.
X64,
/// The ARM 32-bit architecture.
Arm,
/// The ARM 64-bit architecture.
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()))?)
}
}
/// The aliases for the URLs to different Microsoft Authenticode timestamp servers.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimestampServer {
/// A URL to a timestamp server.
Custom(String),
/// The alias for the Comodo timestamp server.
Comodo,
/// The alias for the Verisign timestamp server.
Verisign,
}
impl TimestampServer {
/// Gets the URL of the timestamp server for an alias.
///
/// # Examples
///
/// ```rust
/// use std::str::FromStr;
/// use wix::TimestampServer;
///
/// assert_eq!(
/// TimestampServer::from_str("http://www.example.com").unwrap().url(),
/// "http://www.example.com"
/// );
/// assert_eq!(
/// TimestampServer::Comodo.url(),
/// "http://timestamp.comodoca.com/"
/// );
/// assert_eq!(
/// TimestampServer::Verisign.url(),
/// "http://timestamp.verisign.com/scripts/timstamp.dll"
/// );
/// ```
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))),
}
}
}
/// The various culture codes for localization.
///
/// These are taken from the table in the [WixUI localization] documentation.
///
/// [WixUI localization]: http://wixtoolset.org/documentation/manual/v3/wixui/wixui_localization.html
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Cultures {
/// Arabic, Saudi Arabia
ArSa,
/// Bulgarian, Bulgaria
BgBg,
/// Catalan, Spain
CaEs,
/// Croatian, Croatia
HrHr,
/// Czech, Czech Republic
CsCz,
/// Danish, Denmark
DaDk,
/// Dutch, Netherlands
NlNl,
/// English, United States
#[default]
EnUs,
/// Estonian, Estonia
EtEe,
/// Finnish, Finland
FiFi,
/// French, France
FrFr,
/// German, Germany
DeDe,
/// Greek, Greece
ElGr,
/// Hebrew, Israel
HeIl,
/// Hindi, India
HiIn,
/// Hungarian, Hungary
HuHu,
/// Italian, Italy
ItIt,
/// Japanese, Japan
JaJp,
/// Kazakh, Kazakhstan
KkKz,
/// Korean, Korea
KoKr,
/// Latvian, Latvia
LvLv,
/// Lithuanian, Lithuania
LtLt,
/// Norwegian, Norway
NbNo,
/// Polish, Poland
PlPl,
/// Portuguese, Brazil
PtBr,
/// Portuguese, Portugal
PtPt,
/// Romanian, Romania
RoRo,
/// Russian, Russian
RuRu,
/// Serbian, Serbia and Montenegro
SrLatnCs,
/// Simplified Chinese, China
ZhCn,
/// Slovak, Slovak Republic
SkSk,
/// Solvenian, Solvenia
SlSi,
/// Spanish, Spain
EsEs,
/// Swedish, Sweden
SvSe,
/// Thai, Thailand
ThTh,
/// Traditional Chinese, Hong Kong SAR
ZhHk,
/// Traditional Chinese, Taiwan
ZhTw,
/// Turkish, Turkey
TrTr,
/// Ukrainian, Ukraine
UkUa,
}
impl Cultures {
/// The language of the culture code.
///
/// # Examples
///
/// ```rust
/// use wix::Cultures;
///
/// assert_eq!(Cultures::ArSa.language(), "Arabic");
/// assert_eq!(Cultures::BgBg.language(), "Bulgarian");
/// assert_eq!(Cultures::CaEs.language(), "Catalan");
/// assert_eq!(Cultures::HrHr.language(), "Croatian");
/// assert_eq!(Cultures::CsCz.language(), "Czech");
/// assert_eq!(Cultures::DaDk.language(), "Danish");
/// assert_eq!(Cultures::NlNl.language(), "Dutch");
/// assert_eq!(Cultures::EnUs.language(), "English");
/// assert_eq!(Cultures::EtEe.language(), "Estonian");
/// assert_eq!(Cultures::FiFi.language(), "Finnish");
/// assert_eq!(Cultures::FrFr.language(), "French");
/// assert_eq!(Cultures::DeDe.language(), "German");
/// assert_eq!(Cultures::ElGr.language(), "Greek");
/// assert_eq!(Cultures::HeIl.language(), "Hebrew");
/// assert_eq!(Cultures::HiIn.language(), "Hindi");
/// assert_eq!(Cultures::HuHu.language(), "Hungarian");
/// assert_eq!(Cultures::ItIt.language(), "Italian");
/// assert_eq!(Cultures::JaJp.language(), "Japanese");
/// assert_eq!(Cultures::KkKz.language(), "Kazakh");
/// assert_eq!(Cultures::KoKr.language(), "Korean");
/// assert_eq!(Cultures::LvLv.language(), "Latvian");
/// assert_eq!(Cultures::LtLt.language(), "Lithuanian");
/// assert_eq!(Cultures::NbNo.language(), "Norwegian");
/// assert_eq!(Cultures::PlPl.language(), "Polish");
/// assert_eq!(Cultures::PtBr.language(), "Portuguese");
/// assert_eq!(Cultures::PtPt.language(), "Portuguese");
/// assert_eq!(Cultures::RoRo.language(), "Romanian");
/// assert_eq!(Cultures::RuRu.language(), "Russian");
/// assert_eq!(Cultures::SrLatnCs.language(), "Serbian (Latin)");
/// assert_eq!(Cultures::ZhCn.language(), "Simplified Chinese");
/// assert_eq!(Cultures::SkSk.language(), "Slovak");
/// assert_eq!(Cultures::SlSi.language(), "Slovenian");
/// assert_eq!(Cultures::EsEs.language(), "Spanish");
/// assert_eq!(Cultures::SvSe.language(), "Swedish");
/// assert_eq!(Cultures::ThTh.language(), "Thai");
/// assert_eq!(Cultures::ZhHk.language(), "Traditional Chinese");
/// assert_eq!(Cultures::ZhTw.language(), "Traditional Chinese");
/// assert_eq!(Cultures::TrTr.language(), "Turkish");
/// assert_eq!(Cultures::UkUa.language(), "Ukrainian");
/// ```
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",
}
}
/// The location of the culture component, typically the country that speaks the language.
///
/// # Examples
///
/// ```rust
/// use wix::Cultures;
///
/// assert_eq!(Cultures::ArSa.location(), "Saudi Arabia");
/// assert_eq!(Cultures::BgBg.location(), "Bulgaria");
/// assert_eq!(Cultures::CaEs.location(), "Spain");
/// assert_eq!(Cultures::HrHr.location(), "Croatia");
/// assert_eq!(Cultures::CsCz.location(), "Czech Republic");
/// assert_eq!(Cultures::DaDk.location(), "Denmark");
/// assert_eq!(Cultures::NlNl.location(), "Netherlands");
/// assert_eq!(Cultures::EnUs.location(), "United States");
/// assert_eq!(Cultures::EtEe.location(), "Estonia");
/// assert_eq!(Cultures::FiFi.location(), "Finland");
/// assert_eq!(Cultures::FrFr.location(), "France");
/// assert_eq!(Cultures::DeDe.location(), "Germany");
/// assert_eq!(Cultures::ElGr.location(), "Greece");
/// assert_eq!(Cultures::HeIl.location(), "Israel");
/// assert_eq!(Cultures::HiIn.location(), "India");
/// assert_eq!(Cultures::HuHu.location(), "Hungary");
/// assert_eq!(Cultures::ItIt.location(), "Italy");
/// assert_eq!(Cultures::JaJp.location(), "Japan");
/// assert_eq!(Cultures::KkKz.location(), "Kazakhstan");
/// assert_eq!(Cultures::KoKr.location(), "Korea");
/// assert_eq!(Cultures::LvLv.location(), "Latvia");
/// assert_eq!(Cultures::LtLt.location(), "Lithuania");
/// assert_eq!(Cultures::NbNo.location(), "Norway");
/// assert_eq!(Cultures::PlPl.location(), "Poland");
/// assert_eq!(Cultures::PtBr.location(), "Brazil");
/// assert_eq!(Cultures::PtPt.location(), "Portugal");
/// assert_eq!(Cultures::RoRo.location(), "Romania");
/// assert_eq!(Cultures::RuRu.location(), "Russia");
/// assert_eq!(Cultures::SrLatnCs.location(), "Serbia and Montenegro");
/// assert_eq!(Cultures::ZhCn.location(), "China");
/// assert_eq!(Cultures::SkSk.location(), "Slovak Republic");
/// assert_eq!(Cultures::SlSi.location(), "Solvenia");
/// assert_eq!(Cultures::EsEs.location(), "Spain");
/// assert_eq!(Cultures::SvSe.location(), "Sweden");
/// assert_eq!(Cultures::ThTh.location(), "Thailand");
/// assert_eq!(Cultures::ZhHk.location(), "Hong Kong SAR");
/// assert_eq!(Cultures::ZhTw.location(), "Taiwan");
/// assert_eq!(Cultures::TrTr.location(), "Turkey");
/// assert_eq!(Cultures::UkUa.location(), "Ukraine");
/// ```
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;
/// Create a simple project with the provided TOML.
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_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);
}
}
}
================================================
FILE: src/licenses.rs
================================================
// Copyright (C) 2017 Christopher R. Field.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::Error;
use crate::LICENSE_FILE_NAME;
use crate::RTF_FILE_EXTENSION;
use crate::Result;
use crate::Template;
use crate::stored_path::{StoredPath, StoredPathBuf};
use camino::Utf8Path;
use camino::Utf8PathBuf;
use log::trace;
use log::warn;
use std::str::FromStr;
use cargo_metadata::Package;
/// License info
#[derive(Clone, Debug)]
pub struct Licenses {
/// The license for the actual source code
///
/// This likely will become/contain a Vec at some point,
/// since dual MIT/Apache wants to have two license files!
pub source: Option<License>,
/// The end-user license (EULA) that must be agreed to when installing
pub end_user: Option<License>,
}
/// A license file/item
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct License {
/// Path to the file, relative to package's root dir.
///
/// So generated files would be "wix\License.rtf"
/// and the typical LICENSE file would just be "LICENSE-MIT".
///
/// Conveniently this means we don't need to do any special handling
/// to rewrite/relativize a path we get out of a Cargo.toml.
///
/// It can also be an absolute path if the user passed this via CLI
/// and doesn't care about persistence/portability.
pub stored_path: StoredPathBuf,
/// File name to use for the license when installed to the user's system
///
/// If None, the source file name is used.
pub name: Option<String>,
/// This file needs to be generated, write it to the given path
/// using the given Template.
pub generate: Option<(Utf8PathBuf, Template)>,
}
impl License {
/// Create a License entry with just the StoredPath
fn from_stored_path(path: &StoredPath) -> Self {
Self {
name: None,
stored_path: path.to_owned(),
generate: None,
}
}
}
impl Licenses {
/// Get license/eula info for a package
pub fn new(
dest_dir: Option<&Utf8Path>,
license_path: Option<&StoredPath>,
eula_path: Option<&StoredPath>,
package: &Package,
) -> Result<Self> {
let source_license = Self::find_source_license(dest_dir, license_path, package)?;
let end_user_license =
Self::find_end_user_license(eula_path, package, source_license.as_ref())?;
Ok(Self {
source: source_license,
end_user: end_user_license,
})
}
/// Find the source-license for a package
fn find_source_license(
dest_dir: Option<&Utf8Path>,
path: Option<&StoredPath>,
package: &Package,
) -> Result<Option<License>> {
trace!("finding so
gitextract_m7khl9b_/
├── .cargo/
│ └── config.toml
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── ci.yml
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── src/
│ ├── clean.rs
│ ├── create.rs
│ ├── initialize.rs
│ ├── lib.rs
│ ├── licenses.rs
│ ├── main.rs
│ ├── migrate.rs
│ ├── print/
│ │ ├── license.rs
│ │ ├── mod.rs
│ │ └── wxs.rs
│ ├── purge.rs
│ ├── sign.rs
│ ├── stored_path.rs
│ ├── templates/
│ │ ├── Apache-2.0.rtf.mustache
│ │ ├── GPL-3.0.rtf.mustache
│ │ ├── MIT.rtf.mustache
│ │ ├── main.v3.wxs.mustache
│ │ ├── main.v4.wxs.mustache
│ │ └── mod.rs
│ └── toolset/
│ ├── ext.rs
│ ├── includes.rs
│ ├── mod.rs
│ ├── project.rs
│ ├── source.rs
│ └── test.rs
├── tests/
│ ├── common/
│ │ ├── mod.rs
│ │ ├── one.wxs
│ │ ├── post_v4/
│ │ │ ├── fragment.wxs
│ │ │ └── main.wxs
│ │ ├── pre_v4/
│ │ │ └── main.wxs
│ │ ├── three.wxs
│ │ ├── two.wxs
│ │ └── well_known_exts/
│ │ └── main.wxs
│ ├── create.rs
│ └── initialize.rs
└── xtask/
├── Cargo.toml
└── src/
└── main.rs
SYMBOL INDEX (860 symbols across 24 files)
FILE: src/clean.rs
type Builder (line 29) | pub struct Builder<'a> {
function new (line 35) | pub fn new() -> Self {
function input (line 43) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function build (line 50) | pub fn build(&mut self) -> Execution {
method default (line 58) | fn default() -> Self {
type Execution (line 65) | pub struct Execution {
method run (line 73) | pub fn run(self) -> Result<()> {
method target_wix (line 90) | fn target_wix(&self, target_directory: &Path) -> PathBuf {
method default (line 96) | fn default() -> Self {
function input_works (line 109) | fn input_works() {
function target_wix_works (line 125) | fn target_wix_works() {
function target_wix_with_existing_cargo_toml_works (line 134) | fn target_wix_with_existing_cargo_toml_works() {
FILE: src/create.rs
type Builder (line 72) | pub struct Builder<'a> {
function new (line 99) | pub fn new() -> Self {
function bin_path (line 131) | pub fn bin_path(&mut self, b: Option<&'a str>) -> &mut Self {
function capture_output (line 141) | pub fn capture_output(&mut self, c: bool) -> &mut Self {
function compiler_args (line 151) | pub fn compiler_args(&mut self, c: Option<Vec<&'a str>>) -> &mut Self {
function culture (line 162) | pub fn culture(&mut self, c: Option<&'a str>) -> &mut Self {
function debug_build (line 174) | pub fn debug_build(&mut self, d: bool) -> &mut Self {
function debug_name (line 190) | pub fn debug_name(&mut self, d: bool) -> &mut Self {
function includes (line 204) | pub fn includes(&mut self, i: Option<Vec<&'a str>>) -> &mut Self {
function input (line 218) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function linker_args (line 228) | pub fn linker_args(&mut self, l: Option<Vec<&'a str>>) -> &mut Self {
function locale (line 243) | pub fn locale(&mut self, l: Option<&'a str>) -> &mut Self {
function name (line 267) | pub fn name(&mut self, p: Option<&'a str>) -> &mut Self {
function no_build (line 282) | pub fn no_build(&mut self, n: bool) -> &mut Self {
function target_bin_dir (line 292) | pub fn target_bin_dir(&mut self, p: Option<&'a str>) -> &mut Self {
function install (line 305) | pub fn install(&mut self, n: bool) -> &mut Self {
function output (line 325) | pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
function package (line 335) | pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
function target (line 347) | pub fn target(&mut self, v: Option<&'a str>) -> &mut Self {
function version (line 359) | pub fn version(&mut self, v: Option<&'a str>) -> &mut Self {
function toolset (line 365) | pub fn toolset(&mut self, v: Toolset) -> &mut Self {
function toolset_migration (line 371) | pub fn toolset_migration(&mut self, setup: ToolsetSetupMode) -> &mut Self {
function build (line 377) | pub fn build(&mut self) -> Execution {
function profile (line 412) | pub fn profile(&mut self, profile: Option<&'a str>) -> &mut Self {
method default (line 419) | fn default() -> Self {
type Execution (line 426) | pub struct Execution {
method run (line 453) | pub fn run(self) -> Result<()> {
method compiler_args (line 876) | fn compiler_args(&self, metadata: &Value) -> Option<Vec<String>> {
method culture (line 891) | fn culture(&self, metadata: &Value) -> Result<Cultures> {
method debug_build (line 906) | fn debug_build(&self, metadata: &Value) -> bool {
method debug_name (line 919) | fn debug_name(&self, metadata: &Value) -> bool {
method profile_name (line 936) | fn profile_name(&self, metadata: &Value) -> String {
method profile (line 955) | fn profile(&self, metadata: &Value) -> Profile {
method installer_destination (line 972) | fn installer_destination(
method linker (line 1026) | fn linker(&self) -> Result<Command> {
method linker_args (line 1080) | fn linker_args(&self, metadata: &Value) -> Option<Vec<String>> {
method locale (line 1095) | fn locale(&self, metadata: &Value) -> Result<Option<PathBuf>> {
method name (line 1119) | fn name(&self, package: &Package) -> String {
method no_build (line 1136) | fn no_build(&self, metadata: &Value) -> bool {
method target_bin_dir (line 1153) | fn target_bin_dir(
method target (line 1178) | fn target(&self) -> Result<Target> {
method wixobj_destination (line 1214) | fn wixobj_destination(&self, target_directory: &Path) -> PathBuf {
method wixobj_sources (line 1228) | fn wixobj_sources(&self, wixobj_dst: &Path) -> Result<Vec<PathBuf>> {
method version (line 1261) | fn version(&self, package: &Package) -> Result<String> {
method includes (line 1348) | fn includes(&self) -> Option<&Vec<PathBuf>> {
method default (line 1354) | fn default() -> Self {
type WixObjKind (line 1361) | pub enum WixObjKind {
method is_bundle (line 1404) | pub fn is_bundle(&self) -> bool {
type Error (line 1429) | type Error = crate::Error;
method try_from (line 1431) | fn try_from(path: &PathBuf) -> Result<Self> {
type Error (line 1441) | type Error = crate::Error;
method try_from (line 1443) | fn try_from(content: &str) -> Result<Self> {
type Err (line 1414) | type Err = crate::Error;
method from_str (line 1416) | fn from_str(value: &str) -> Result<Self> {
type InstallerKind (line 1462) | pub enum InstallerKind {
method extension (line 1498) | pub fn extension(&self) -> &'static str {
method fmt (line 1521) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Error (line 1527) | type Error = crate::Error;
method try_from (line 1529) | fn try_from(v: Vec<WixObjKind>) -> Result<Self> {
type Err (line 1507) | type Err = crate::Error;
method from_str (line 1509) | fn from_str(value: &str) -> Result<Self> {
type Profile (line 1548) | pub struct Profile {
type Target (line 1557) | pub struct Target {
function defaults_are_correct (line 1572) | fn defaults_are_correct() {
function bin_path_works (line 1591) | fn bin_path_works() {
function target_bin_dir_works (line 1599) | fn target_bin_dir_works() {
function capture_output_works (line 1607) | fn capture_output_works() {
function compiler_args_with_single_value_works (line 1614) | fn compiler_args_with_single_value_works() {
function compiler_args_with_multiple_values_works (line 1622) | fn compiler_args_with_multiple_values_works() {
function culture_works (line 1630) | fn culture_works() {
function russian_culture_works (line 1638) | fn russian_culture_works() {
function debug_build_works (line 1646) | fn debug_build_works() {
function profile_works (line 1653) | fn profile_works() {
function debug_name_works (line 1661) | fn debug_name_works() {
function includes_works (line 1668) | fn includes_works() {
function input_works (line 1676) | fn input_works() {
function linker_args_with_single_value_works (line 1684) | fn linker_args_with_single_value_works() {
function linker_args_with_multiple_values_works (line 1692) | fn linker_args_with_multiple_values_works() {
function locale_works (line 1700) | fn locale_works() {
function name_works (line 1708) | fn name_works() {
function no_build_works (line 1716) | fn no_build_works() {
function output_works (line 1723) | fn output_works() {
function version_works (line 1731) | fn version_works() {
function build_with_defaults_works (line 1739) | fn build_with_defaults_works() {
function build_with_all_works (line 1759) | fn build_with_all_works() {
function default_profile_works (line 1816) | fn default_profile_works() {
function debug_build_metadata_works (line 1827) | fn debug_build_metadata_works() {
function debug_name_metadata_works (line 1840) | fn debug_name_metadata_works() {
function version_metadata_works (line 1852) | fn version_metadata_works() {
function profile_metadata_works (line 1880) | fn profile_metadata_works() {
function version_prerelease_parse_works (line 1893) | fn version_prerelease_parse_works() {
function version_prerelease_dot_parse_works (line 1915) | fn version_prerelease_dot_parse_works() {
function version_build_parse_works (line 1937) | fn version_build_parse_works() {
function name_metadata_works (line 1959) | fn name_metadata_works() {
function no_build_metadata_works (line 1981) | fn no_build_metadata_works() {
function culture_metadata_works (line 1993) | fn culture_metadata_works() {
function locale_metadata_works (line 2007) | fn locale_metadata_works() {
function output_metadata_works (line 2021) | fn output_metadata_works() {
function include_metadata_works (line 2053) | fn include_metadata_works() {
function compiler_args_override_works (line 2079) | fn compiler_args_override_works() {
function compiler_args_metadata_works (line 2093) | fn compiler_args_metadata_works() {
function linker_args_override_works (line 2108) | fn linker_args_override_works() {
function linker_args_metadata_works (line 2122) | fn linker_args_metadata_works() {
constant EMPTY_PKG_META_WIX (line 2136) | const EMPTY_PKG_META_WIX: &str = r#"{"wix": {}}"#;
function culture_works (line 2139) | fn culture_works() {
function locale_works (line 2148) | fn locale_works() {
function no_build_works (line 2157) | fn no_build_works() {
function target_bin_dir_overwrite_works (line 2164) | fn target_bin_dir_overwrite_works() {
function target_bin_dir_computation_works (line 2180) | fn target_bin_dir_computation_works() {
function wixobj_destination_works (line 2196) | fn wixobj_destination_works() {
constant PRODUCT_WIXOBJ (line 2212) | const PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
constant BUNDLE_WIXOBJ (line 2217) | const BUNDLE_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
constant FRAGMENT_WIXOBJ (line 2222) | const FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
constant UNKNOWN_WIXOBJ (line 2227) | const UNKNOWN_WIXOBJ: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
constant BUNDLE_AND_PRODUCT_WIXOBJ (line 2232) | const BUNDLE_AND_PRODUCT_WIXOBJ: &str = r#"<?xml version="1.0" encoding=...
constant PRODUCT_AND_FRAGMENT_WIXOBJ (line 2238) | const PRODUCT_AND_FRAGMENT_WIXOBJ: &str = r#"<?xml version="1.0" encodin...
function try_from_product_object_works (line 2245) | fn try_from_product_object_works() {
function try_from_bundle_object_works (line 2253) | fn try_from_bundle_object_works() {
function try_from_fragment_object_works (line 2258) | fn try_from_fragment_object_works() {
function try_from_bundle_and_product_object_works (line 2266) | fn try_from_bundle_and_product_object_works() {
function try_from_product_and_fragment_object_works (line 2274) | fn try_from_product_and_fragment_object_works() {
function try_from_unknown_object_fails (line 2283) | fn try_from_unknown_object_fails() {
function try_from_wixobj_single_product_works (line 2292) | fn try_from_wixobj_single_product_works() {
function try_from_wixobj_single_bundle_works (line 2300) | fn try_from_wixobj_single_bundle_works() {
function try_from_wixobj_single_fragment_fails (line 2309) | fn try_from_wixobj_single_fragment_fails() {
function try_from_wixobj_multiple_fragments_fails (line 2315) | fn try_from_wixobj_multiple_fragments_fails() {
function try_from_wixobj_product_and_bundle_works (line 2325) | fn try_from_wixobj_product_and_bundle_works() {
function try_from_wixobj_multiple_products_and_single_bundle_works (line 2333) | fn try_from_wixobj_multiple_products_and_single_bundle_works() {
function try_from_wixobj_multiple_fragments_and_single_product_works (line 2346) | fn try_from_wixobj_multiple_fragments_and_single_product_works() {
function try_from_wixobj_multiple_fragments_and_single_bundle_works (line 2359) | fn try_from_wixobj_multiple_fragments_and_single_bundle_works() {
function try_from_wixobj_multiple_fragments_and_products_works (line 2372) | fn try_from_wixobj_multiple_fragments_and_products_works() {
function try_from_wixobj_multiple_products_and_bundles_works (line 2386) | fn try_from_wixobj_multiple_products_and_bundles_works() {
function try_from_wixobj_multiple_products_fragments_and_single_bundle_works (line 2401) | fn try_from_wixobj_multiple_products_fragments_and_single_bundle_works() {
FILE: src/initialize.rs
type Builder (line 44) | pub struct Builder<'a> {
function new (line 68) | pub fn new() -> Self {
function banner (line 100) | pub fn banner(&mut self, b: Option<&'a str>) -> &mut Self {
function binaries (line 128) | pub fn binaries(&mut self, b: Option<Vec<&'a str>>) -> &mut Self {
function copyright_holder (line 147) | pub fn copyright_holder(&mut self, h: Option<&'a str>) -> &mut Self {
function copyright_year (line 165) | pub fn copyright_year(&mut self, y: Option<&'a str>) -> &mut Self {
function description (line 174) | pub fn description(&mut self, d: Option<&'a str>) -> &mut Self {
function dialog (line 187) | pub fn dialog(&mut self, d: Option<&'a str>) -> &mut Self {
function eula (line 216) | pub fn eula(&mut self, e: Option<&'a str>) -> &mut Self {
function force (line 223) | pub fn force(&mut self, f: bool) -> &mut Self {
function help_url (line 239) | pub fn help_url(&mut self, h: Option<&'a str>) -> &mut Self {
function input (line 250) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function license (line 273) | pub fn license(&mut self, l: Option<&'a str>) -> &mut Self {
function manufacturer (line 282) | pub fn manufacturer(&mut self, m: Option<&'a str>) -> &mut Self {
function output (line 297) | pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
function product_icon (line 308) | pub fn product_icon(&mut self, p: Option<&'a str>) -> &mut Self {
function package (line 318) | pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
function path_guid (line 333) | pub fn path_guid(&mut self, p: Option<&'a str>) -> &mut Self {
function product_name (line 348) | pub fn product_name(&mut self, p: Option<&'a str>) -> &mut Self {
function upgrade_guid (line 367) | pub fn upgrade_guid(&mut self, u: Option<&'a str>) -> &mut Self {
function schema (line 375) | pub fn schema(&mut self, schema: Option<WxsSchema>) -> &mut Self {
function build (line 381) | pub fn build(&mut self) -> Execution {
method default (line 410) | fn default() -> Self {
type Execution (line 417) | pub struct Execution {
method run (line 442) | pub fn run(self) -> Result<()> {
method destination (line 503) | fn destination(&self, package: &Package) -> Utf8PathBuf {
method default (line 525) | fn default() -> Self {
constant PATH_GUID (line 537) | const PATH_GUID: &str = "0B5DFC00-1480-4044-AC1A-BEF00E0A91BB";
constant UPGRADE_GUID (line 538) | const UPGRADE_GUID: &str = "0631BBDF-4079-4C20-823F-7EA8DE40BF08";
function defaults_are_correct (line 541) | fn defaults_are_correct() {
function banner_works (line 564) | fn banner_works() {
function binaries_works (line 572) | fn binaries_works() {
function copyright_holder_works (line 580) | fn copyright_holder_works() {
function copyright_year_works (line 588) | fn copyright_year_works() {
function description_works (line 596) | fn description_works() {
function dialog_works (line 604) | fn dialog_works() {
function eula_works (line 612) | fn eula_works() {
function force_works (line 620) | fn force_works() {
function help_url_works (line 627) | fn help_url_works() {
function input_works (line 635) | fn input_works() {
function license_works (line 643) | fn license_works() {
function manufacturer_works (line 651) | fn manufacturer_works() {
function output_works (line 659) | fn output_works() {
function path_guid_works (line 667) | fn path_guid_works() {
function product_icon_works (line 674) | fn product_icon_works() {
function product_name_works (line 682) | fn product_name_works() {
function upgrade_guid_works (line 690) | fn upgrade_guid_works() {
function schema_works (line 697) | fn schema_works() {
function schema_defaults_to_legacy_in_execution (line 705) | fn schema_defaults_to_legacy_in_execution() {
function build_with_defaults_works (line 713) | fn build_with_defaults_works() {
function build_with_all_works (line 737) | fn build_with_all_works() {
constant MIN_PACKAGE (line 818) | const MIN_PACKAGE: &str = r#"[package]
function destination_is_correct_with_defaults (line 825) | fn destination_is_correct_with_defaults() {
function destination_is_correct_with_output (line 844) | fn destination_is_correct_with_output() {
FILE: src/lib.rs
constant BINARY_FOLDER_NAME (line 98) | pub const BINARY_FOLDER_NAME: &str = "bin";
constant CARGO_MANIFEST_FILE (line 101) | pub const CARGO_MANIFEST_FILE: &str = "Cargo.toml";
constant CARGO (line 104) | pub const CARGO: &str = "cargo";
constant EXE_FILE_EXTENSION (line 107) | pub const EXE_FILE_EXTENSION: &str = "exe";
constant LICENSE_FILE_NAME (line 110) | pub const LICENSE_FILE_NAME: &str = "License";
constant MSI_FILE_EXTENSION (line 113) | pub const MSI_FILE_EXTENSION: &str = "msi";
constant RTF_FILE_EXTENSION (line 116) | pub const RTF_FILE_EXTENSION: &str = "rtf";
constant SIGNTOOL (line 119) | pub const SIGNTOOL: &str = "signtool";
constant SIGNTOOL_PATH_KEY (line 123) | pub const SIGNTOOL_PATH_KEY: &str = "SIGNTOOL_PATH";
constant WIX (line 126) | pub const WIX: &str = "wix";
constant WIX_COMPILER (line 130) | pub const WIX_COMPILER: &str = "candle";
constant WIX_LINKER (line 134) | pub const WIX_LINKER: &str = "light";
constant WIX_MODERN_TOOLSET (line 137) | pub const WIX_MODERN_TOOLSET: &str = "wix";
constant MSIEXEC (line 140) | pub const MSIEXEC: &str = "msiexec";
constant WIX_OBJECT_FILE_EXTENSION (line 144) | pub const WIX_OBJECT_FILE_EXTENSION: &str = "wixobj";
constant WIX_PATH_KEY (line 149) | pub const WIX_PATH_KEY: &str = "WIX";
constant WIX_SOURCE_FILE_EXTENSION (line 153) | pub const WIX_SOURCE_FILE_EXTENSION: &str = "wxs";
constant WIX_SOURCE_FILE_NAME (line 157) | pub const WIX_SOURCE_FILE_NAME: &str = "main";
type Result (line 162) | pub type Result<T> = std::result::Result<T, Error>;
function cargo_toml_file (line 164) | fn cargo_toml_file(input: Option<&PathBuf>) -> Result<PathBuf> {
function description (line 188) | fn description(description: Option<String>, manifest: &Package) -> Optio...
function manifest (line 192) | fn manifest(input: Option<&PathBuf>) -> Result<Metadata> {
function package (line 201) | fn package(manifest: &Metadata, package: Option<&str>) -> Result<Package> {
function product_name (line 222) | fn product_name(product_name: Option<&String>, manifest: &Package) -> St...
type Error (line 235) | pub enum Error {
method code (line 275) | pub fn code(&self) -> i32 {
method already_exists (line 311) | pub fn already_exists(p: &Utf8Path) -> Self {
method not_found (line 335) | pub fn not_found(p: &Path) -> Self {
method not_a_file (line 359) | pub fn not_a_file(p: &Path) -> Self {
method not_a_manifest (line 391) | pub fn not_a_manifest(p: &Path) -> Self {
method as_str (line 412) | pub fn as_str(&self) -> &str {
method fmt (line 449) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
method from (line 508) | fn from(s: &str) -> Self {
method from (line 514) | fn from(err: cargo_metadata::Error) -> Self {
method from (line 520) | fn from(err: io::Error) -> Self {
method from (line 526) | fn from(err: mustache::Error) -> Self {
method from (line 532) | fn from(err: semver::Error) -> Self {
method from (line 538) | fn from(err: std::path::StripPrefixError) -> Self {
method from (line 544) | fn from(err: sxd_document::parser::Error) -> Self {
method from (line 550) | fn from(err: uuid::Error) -> Self {
method from (line 556) | fn from(value: sxd_xpath::Error) -> Self {
method from (line 562) | fn from(value: sxd_xpath::ExecutionError) -> Self {
method from (line 568) | fn from(value: std::string::FromUtf8Error) -> Self {
method description (line 430) | fn description(&self) -> &str {
method source (line 434) | fn source(&self) -> Option<&(dyn StdError + 'static)> {
method eq (line 502) | fn eq(&self, other: &Error) -> bool {
type WixArch (line 578) | pub enum WixArch {
type Error (line 601) | type Error = crate::Error;
method try_from (line 603) | fn try_from(c: &Cfg) -> std::result::Result<Self, Self::Error> {
method fmt (line 590) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type Err (line 623) | type Err = crate::Error;
method from_str (line 625) | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
type TimestampServer (line 632) | pub enum TimestampServer {
method url (line 663) | pub fn url(&self) -> &str {
method fmt (line 673) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
type Err (line 679) | type Err = Error;
method from_str (line 681) | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
type Cultures (line 696) | pub enum Cultures {
method language (line 826) | pub fn language(&self) -> &'static str {
method location (line 917) | pub fn location(&self) -> &'static str {
method fmt (line 963) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
type Err (line 1009) | type Err = Error;
method from_str (line 1011) | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
function setup_project (line 1065) | pub fn setup_project(toml: &str) -> TempDir {
function from_str_is_correct_for_dash_russian (line 1080) | fn from_str_is_correct_for_dash_russian() {
function from_str_fails_for_underscore_russian (line 1086) | fn from_str_fails_for_underscore_russian() {
function display_is_correct_for_russian (line 1091) | fn display_is_correct_for_russian() {
function from_str_is_correct_for_lowercase_slovak (line 1096) | fn from_str_is_correct_for_lowercase_slovak() {
function from_str_is_correct_for_uppercase_slovak (line 1101) | fn from_str_is_correct_for_uppercase_slovak() {
function from_str_fails_for_underscore_slovak (line 1107) | fn from_str_fails_for_underscore_slovak() {
function display_is_correct_for_slovak (line 1112) | fn display_is_correct_for_slovak() {
function try_from_x86_64_pc_windows_msvc_is_correct (line 1121) | fn try_from_x86_64_pc_windows_msvc_is_correct() {
function try_from_x86_64_pc_windows_gnu_is_correct (line 1128) | fn try_from_x86_64_pc_windows_gnu_is_correct() {
function try_from_x86_64_uwp_windows_msvc_is_correct (line 1135) | fn try_from_x86_64_uwp_windows_msvc_is_correct() {
function try_from_x86_64_uwp_windows_gnu_is_correct (line 1142) | fn try_from_x86_64_uwp_windows_gnu_is_correct() {
function try_from_i686_pc_windows_msvc_is_correct (line 1149) | fn try_from_i686_pc_windows_msvc_is_correct() {
function try_from_i686_pc_windows_gnu_is_correct (line 1156) | fn try_from_i686_pc_windows_gnu_is_correct() {
function try_from_i686_uwp_windows_msvc_is_correct (line 1163) | fn try_from_i686_uwp_windows_msvc_is_correct() {
function try_from_i686_uwp_windows_gnu_is_correct (line 1170) | fn try_from_i686_uwp_windows_gnu_is_correct() {
function try_from_aarch64_pc_windows_msvc_is_correct (line 1177) | fn try_from_aarch64_pc_windows_msvc_is_correct() {
function try_from_aarch64_uwp_windows_msvc_is_correct (line 1184) | fn try_from_aarch64_uwp_windows_msvc_is_correct() {
function try_from_thumbv7a_pc_windows_msvc_is_correct (line 1192) | fn try_from_thumbv7a_pc_windows_msvc_is_correct() {
function try_from_thumbv7a_uwp_windows_msvc_is_correct (line 1200) | fn try_from_thumbv7a_uwp_windows_msvc_is_correct() {
function from_str_is_correct (line 1208) | fn from_str_is_correct() {
FILE: src/licenses.rs
type Licenses (line 33) | pub struct Licenses {
method new (line 80) | pub fn new(
method find_source_license (line 97) | fn find_source_license(
method find_end_user_license (line 195) | fn find_end_user_license(
type License (line 45) | pub struct License {
method from_stored_path (line 69) | fn from_stored_path(path: &StoredPath) -> Self {
FILE: src/main.rs
constant PKG_DESCRIPTION (line 1277) | pub const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
constant PKG_NAME (line 1278) | pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
constant PKG_VERSION (line 1279) | pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
constant SUBCOMMAND_NAME (line 1281) | const SUBCOMMAND_NAME: &str = "wix";
function main (line 1283) | fn main() {
FILE: src/migrate.rs
type Builder (line 28) | pub struct Builder<'a> {
function new (line 37) | pub fn new() -> Self {
function input (line 55) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function includes (line 69) | pub fn includes(&mut self, i: Option<Vec<&'a str>>) -> &mut Self {
function package (line 79) | pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
function vendor (line 85) | pub fn vendor(&mut self, vendored: bool) -> &mut Self {
function build (line 93) | pub fn build(self) -> Execution {
type Execution (line 112) | pub struct Execution {
method run (line 121) | pub fn run(self) -> crate::Result<()> {
method includes (line 140) | fn includes(&self) -> Option<&Vec<PathBuf>> {
method default (line 146) | fn default() -> Self {
function builder_defaults (line 156) | fn builder_defaults() {
function builder_input_works (line 165) | fn builder_input_works() {
function builder_package_works (line 172) | fn builder_package_works() {
function builder_includes_works (line 179) | fn builder_includes_works() {
function builder_vendor_works (line 186) | fn builder_vendor_works() {
function build_sets_global_mode_by_default (line 193) | fn build_sets_global_mode_by_default() {
function build_sets_vendor_mode_when_vendor_enabled (line 202) | fn build_sets_vendor_mode_when_vendor_enabled() {
function build_converts_input_to_pathbuf (line 213) | fn build_converts_input_to_pathbuf() {
function build_converts_includes_to_pathbufs (line 221) | fn build_converts_includes_to_pathbufs() {
FILE: src/print/license.rs
type Builder (line 37) | pub struct Builder<'a> {
function new (line 47) | pub fn new() -> Self {
function copyright_holder (line 63) | pub fn copyright_holder(&mut self, h: Option<&'a str>) -> &mut Self {
function copyright_year (line 74) | pub fn copyright_year(&mut self, y: Option<&'a str>) -> &mut Self {
function input (line 86) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function output (line 96) | pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
function package (line 103) | pub fn package(&mut self, o: Option<&'a str>) -> &mut Self {
function build (line 109) | pub fn build(&self) -> Execution {
method default (line 121) | fn default() -> Self {
type Execution (line 128) | pub struct Execution {
method run (line 138) | pub fn run(self, template: &Template) -> Result<()> {
method render (line 144) | pub fn render(self, template: &Template) -> Result<RenderOutput> {
method copyright_holder (line 164) | fn copyright_holder(&self, manifest: &Package) -> Result<String> {
method copyright_year (line 172) | fn copyright_year(&self) -> String {
method default (line 180) | fn default() -> Self {
function copyright_holder_works (line 193) | fn copyright_holder_works() {
function copyright_year_works (line 201) | fn copyright_year_works() {
function input_works (line 209) | fn input_works() {
function output_works (line 217) | fn output_works() {
constant MIN_MANIFEST (line 228) | const MIN_MANIFEST: &str = r#"{
function copyright_holder_works (line 241) | fn copyright_holder_works() {
function copyright_holder_with_override_works (line 248) | fn copyright_holder_with_override_works() {
function copyright_year_works (line 260) | fn copyright_year_works() {
function copyright_year_with_override_works (line 266) | fn copyright_year_with_override_works() {
FILE: src/print/mod.rs
type RenderOutput (line 38) | pub struct RenderOutput {
method write (line 53) | pub fn write(&self) -> Result<()> {
method write_disk_only (line 67) | pub fn write_disk_only(&self) -> Result<()> {
function destination (line 76) | fn destination(output: Option<&PathBuf>) -> Result<Box<dyn Write>> {
function authors (line 90) | fn authors(package: &Package) -> Result<String> {
constant SINGLE_AUTHOR_MANIFEST (line 114) | const SINGLE_AUTHOR_MANIFEST: &str = r#"{
constant MULTIPLE_AUTHORS_MANIFEST (line 126) | const MULTIPLE_AUTHORS_MANIFEST: &str = r#"{
function authors_with_single_author_works (line 139) | fn authors_with_single_author_works() {
function authors_with_multiple_authors_works (line 146) | fn authors_with_multiple_authors_works() {
FILE: src/print/wxs.rs
type Builder (line 45) | pub struct Builder<'a> {
function new (line 68) | pub fn new() -> Self {
function banner (line 99) | pub fn banner(&mut self, b: Option<&'a str>) -> &mut Self {
function binaries (line 118) | pub fn binaries(&mut self, b: Option<Vec<&'a str>>) -> &mut Self {
function copyright_holder (line 137) | pub fn copyright_holder(&mut self, h: Option<&'a str>) -> &mut Self {
function copyright_year (line 155) | pub fn copyright_year(&mut self, y: Option<&'a str>) -> &mut Self {
function description (line 164) | pub fn description(&mut self, d: Option<&'a str>) -> &mut Self {
function dialog (line 177) | pub fn dialog(&mut self, d: Option<&'a str>) -> &mut Self {
function eula (line 195) | pub fn eula(&mut self, e: Option<&'a str>) -> &mut Self {
function help_url (line 208) | pub fn help_url(&mut self, h: Option<&'a str>) -> &mut Self {
function input (line 219) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function license (line 237) | pub fn license(&mut self, l: Option<&'a str>) -> &mut Self {
function manufacturer (line 246) | pub fn manufacturer(&mut self, m: Option<&'a str>) -> &mut Self {
function output (line 255) | pub fn output(&mut self, o: Option<&'a str>) -> &mut Self {
function package (line 265) | pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
function path_guid (line 280) | pub fn path_guid(&mut self, p: Option<&'a str>) -> &mut Self {
function product_icon (line 291) | pub fn product_icon(&mut self, p: Option<&'a str>) -> &mut Self {
function product_name (line 309) | pub fn product_name(&mut self, p: Option<&'a str>) -> &mut Self {
function upgrade_guid (line 328) | pub fn upgrade_guid(&mut self, u: Option<&'a str>) -> &mut Self {
function schema (line 336) | pub fn schema(&mut self, schema: Option<WxsSchema>) -> &mut Self {
function build (line 342) | pub fn build(&self) -> Execution {
method default (line 370) | fn default() -> Self {
type Execution (line 377) | pub struct Execution {
method run (line 419) | pub fn run(self) -> Result<()> {
method render (line 434) | pub fn render(self) -> Result<WxsRenders> {
method render_license_string (line 534) | fn render_license_string(&self, license: Option<&License>) -> Result<O...
method binaries (line 558) | fn binaries(&self, package: &Package) -> Result<Vec<HashMap<&'static s...
method default_binary_path (line 603) | fn default_binary_path(name: &str) -> StoredPathBuf {
method help_url (line 610) | fn help_url(&self, manifest: &Package) -> Option<String> {
method licenses (line 619) | fn licenses(&self, manifest: &Package) -> Result<Licenses> {
method manufacturer (line 634) | fn manufacturer(&self, manifest: &Package) -> Result<String> {
method path_guid (line 642) | fn path_guid(&self, manifest: &Package) -> Result<String> {
method upgrade_guid (line 661) | fn upgrade_guid(&self, manifest: &Package) -> Result<String> {
method banner_image (line 680) | fn banner_image(&self, manifest: &Package) -> Option<StoredPathBuf> {
method dialog_image (line 695) | fn dialog_image(&self, manifest: &Package) -> Option<StoredPathBuf> {
method product_icon (line 710) | fn product_icon(&self, manifest: &Package) -> Option<StoredPathBuf> {
method for_test (line 726) | pub fn for_test(input: &Path) -> Self {
type WxsRenders (line 403) | pub struct WxsRenders {
function banner_works (line 754) | fn banner_works() {
function binaries_name_works (line 762) | fn binaries_name_works() {
function description_works (line 770) | fn description_works() {
function dialog_work (line 778) | fn dialog_work() {
function eula_works (line 786) | fn eula_works() {
function help_url_works (line 794) | fn help_url_works() {
function input_works (line 802) | fn input_works() {
function license_works (line 810) | fn license_works() {
function manufacturer_works (line 818) | fn manufacturer_works() {
function output_works (line 826) | fn output_works() {
function path_guid_works (line 834) | fn path_guid_works() {
function product_icon_works (line 842) | fn product_icon_works() {
function product_name_works (line 850) | fn product_name_works() {
function upgrade_guid_works (line 858) | fn upgrade_guid_works() {
function schema_default_is_none (line 866) | fn schema_default_is_none() {
function schema_v4_works (line 872) | fn schema_v4_works() {
function schema_defaults_to_legacy_in_execution (line 880) | fn schema_defaults_to_legacy_in_execution() {
constant MIN_MANIFEST (line 895) | const MIN_MANIFEST: &str = r#"[package]
constant MIT_MANIFEST (line 901) | const MIT_MANIFEST: &str = r#"[package]
constant GPL3_MANIFEST (line 908) | const GPL3_MANIFEST: &str = r#"[package]
constant APACHE2_MANIFEST (line 915) | const APACHE2_MANIFEST: &str = r#"[package]
constant UNKNOWN_MANIFEST (line 922) | const UNKNOWN_MANIFEST: &str = r#"[package]
constant MIT_MANIFEST_BIN (line 929) | const MIT_MANIFEST_BIN: &str = r#"[package]
constant MULTIPLE_BIN_MANIFEST (line 939) | const MULTIPLE_BIN_MANIFEST: &str = r#"[package]
constant DOCUMENTATION_MANIFEST (line 958) | const DOCUMENTATION_MANIFEST: &str = r#"[package]
constant HOMEPAGE_MANIFEST (line 966) | const HOMEPAGE_MANIFEST: &str = r#"[package]
constant REPOSITORY_MANIFEST (line 974) | const REPOSITORY_MANIFEST: &str = r#"[package]
constant LICENSE_FILE_RTF_MANIFEST (line 982) | const LICENSE_FILE_RTF_MANIFEST: &str = r#"[package]
constant LICENSE_FILE_TXT_MANIFEST (line 989) | const LICENSE_FILE_TXT_MANIFEST: &str = r#"[package]
constant LICENSE_FALSE_MANIFEST (line 996) | const LICENSE_FALSE_MANIFEST: &str = r#"[package]
constant EULA_FALSE_MANIFEST (line 1007) | const EULA_FALSE_MANIFEST: &str = r#"[package]
constant EULA_AND_LICENSE_FALSE_MANIFEST (line 1018) | const EULA_AND_LICENSE_FALSE_MANIFEST: &str = r#"[package]
constant LICENSE_PATH_RTF_MANIFEST (line 1029) | const LICENSE_PATH_RTF_MANIFEST: &str = r#"[package]
constant LICENSE_PATH_TXT_MANIFEST (line 1039) | const LICENSE_PATH_TXT_MANIFEST: &str = r#"[package]
constant EULA_PATH_RTF_MANIFEST (line 1049) | const EULA_PATH_RTF_MANIFEST: &str = r#"[package]
constant EULA_PATH_TXT_MANIFEST (line 1059) | const EULA_PATH_TXT_MANIFEST: &str = r#"[package]
constant EULA_AND_LICENSE_PATH_RTF_MANIFEST (line 1069) | const EULA_AND_LICENSE_PATH_RTF_MANIFEST: &str = r#"[package]
constant EULA_BAD_PATH_MANIFEST (line 1080) | const EULA_BAD_PATH_MANIFEST: &str = r#"[package]
constant LICENSE_BAD_PATH_MANIFEST (line 1090) | const LICENSE_BAD_PATH_MANIFEST: &str = r#"[package]
constant LICENSE_FILE_BAD_PATH_MANIFEST (line 1100) | const LICENSE_FILE_BAD_PATH_MANIFEST: &str = r#"[package]
constant PATH_GUID (line 1107) | const PATH_GUID: &str = "C38A18DB-12CC-4BDC-8A05-DFCB981A0F33";
constant UPGRADE_GUID (line 1108) | const UPGRADE_GUID: &str = "71C1A58D-3FD2-493D-BB62-4B27C66FCCF9";
constant PATH_GUID_MANIFEST (line 1110) | const PATH_GUID_MANIFEST: &str = r#"[package]
constant UPGRADE_GUID_MANIFEST (line 1119) | const UPGRADE_GUID_MANIFEST: &str = r#"[package]
constant IMAGES_MANIFEST (line 1128) | const IMAGES_MANIFEST: &str = r#"[package]
function license_name_with_mit_license_field_works (line 1140) | fn license_name_with_mit_license_field_works() {
function license_name_with_gpl3_license_field_works (line 1159) | fn license_name_with_gpl3_license_field_works() {
function license_name_with_apache2_license_field_works (line 1178) | fn license_name_with_apache2_license_field_works() {
function license_name_with_unknown_license_field_works (line 1197) | fn license_name_with_unknown_license_field_works() {
function license_source_with_mit_license_field_works (line 1211) | fn license_source_with_mit_license_field_works() {
function license_source_with_gpl3_license_field_works (line 1230) | fn license_source_with_gpl3_license_field_works() {
function license_source_with_apache2_license_field_works (line 1249) | fn license_source_with_apache2_license_field_works() {
function license_source_with_unknown_license_field_works (line 1268) | fn license_source_with_unknown_license_field_works() {
function license_false_works (line 1282) | fn license_false_works() {
function eula_false_works (line 1296) | fn eula_false_works() {
function eula_and_license_false_works (line 1313) | fn eula_and_license_false_works() {
function license_path_rtf_works (line 1327) | fn license_path_rtf_works() {
function license_path_txt_works (line 1349) | fn license_path_txt_works() {
function eula_path_rtf_works (line 1368) | fn eula_path_rtf_works() {
function eula_path_txt_works (line 1390) | fn eula_path_txt_works() {
function eula_and_license_path_rtf_works (line 1412) | fn eula_and_license_path_rtf_works() {
function eula_bad_path_errors (line 1436) | fn eula_bad_path_errors() {
function license_bad_path_errors (line 1447) | fn license_bad_path_errors() {
function license_file_bad_path_errors (line 1458) | fn license_file_bad_path_errors() {
function binaries_with_no_bin_section_works (line 1469) | fn binaries_with_no_bin_section_works() {
function binaries_with_single_bin_section_works (line 1487) | fn binaries_with_single_bin_section_works() {
function binaries_with_multiple_bin_sections_works (line 1505) | fn binaries_with_multiple_bin_sections_works() {
function manufacturer_with_defaults_works (line 1535) | fn manufacturer_with_defaults_works() {
function manufacturer_with_override_works (line 1548) | fn manufacturer_with_override_works() {
function help_url_with_defaults_works (line 1564) | fn help_url_with_defaults_works() {
function help_url_with_documentation_works (line 1574) | fn help_url_with_documentation_works() {
function help_url_with_homepage_works (line 1586) | fn help_url_with_homepage_works() {
function help_url_with_repository_works (line 1598) | fn help_url_with_repository_works() {
function eula_with_defaults_works (line 1610) | fn eula_with_defaults_works() {
function eula_with_mit_license_field_works (line 1624) | fn eula_with_mit_license_field_works() {
function eula_with_apache2_license_field_works (line 1648) | fn eula_with_apache2_license_field_works() {
function eula_with_gpl3_license_field_works (line 1672) | fn eula_with_gpl3_license_field_works() {
function eula_with_unknown_license_field_works (line 1696) | fn eula_with_unknown_license_field_works() {
function eula_with_override_works (line 1712) | fn eula_with_override_works() {
function eula_with_license_file_field_works (line 1735) | fn eula_with_license_file_field_works() {
function eula_with_license_file_extension_works (line 1754) | fn eula_with_license_file_extension_works() {
function eula_with_wrong_file_extension_override_works (line 1774) | fn eula_with_wrong_file_extension_override_works() {
function path_guid_with_override_works (line 1797) | fn path_guid_with_override_works() {
function path_guid_metadata_works (line 1813) | fn path_guid_metadata_works() {
function path_guid_metadata_and_override_works (line 1823) | fn path_guid_metadata_and_override_works() {
function upgrade_guid_with_override_works (line 1839) | fn upgrade_guid_with_override_works() {
function upgrade_guid_metadata_works (line 1855) | fn upgrade_guid_metadata_works() {
function upgrade_guid_metadata_and_override_works (line 1865) | fn upgrade_guid_metadata_and_override_works() {
function image_metadata_works (line 1881) | fn image_metadata_works() {
FILE: src/purge.rs
type Builder (line 34) | pub struct Builder<'a> {
function new (line 40) | pub fn new() -> Self {
function input (line 48) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function build (line 55) | pub fn build(&mut self) -> Execution {
method default (line 63) | fn default() -> Self {
type Execution (line 71) | pub struct Execution {
method run (line 80) | pub fn run(self) -> Result<()> {
method wix (line 98) | fn wix(&self) -> Result<PathBuf> {
method default (line 139) | fn default() -> Self {
function input_works (line 152) | fn input_works() {
function wix_works (line 167) | fn wix_works() {
function wix_with_nonexistent_manifest_fails (line 176) | fn wix_with_nonexistent_manifest_fails() {
function wix_with_existing_file_but_not_cargo_toml_fails (line 182) | fn wix_with_existing_file_but_not_cargo_toml_fails() {
function wix_with_existing_cargo_toml_works (line 194) | fn wix_with_existing_cargo_toml_works() {
FILE: src/sign.rs
type Builder (line 42) | pub struct Builder<'a> {
function new (line 56) | pub fn new() -> Self {
function package (line 71) | pub fn package(&mut self, p: Option<&'a str>) -> &mut Self {
function bin_path (line 80) | pub fn bin_path(&mut self, b: Option<&'a str>) -> &mut Self {
function capture_output (line 90) | pub fn capture_output(&mut self, c: bool) -> &mut Self {
function description (line 101) | pub fn description(&mut self, d: Option<&'a str>) -> &mut Self {
function homepage (line 111) | pub fn homepage(&mut self, h: Option<&'a str>) -> &mut Self {
function input (line 117) | pub fn input(&mut self, i: Option<&'a str>) -> &mut Self {
function installer (line 123) | pub fn installer(&mut self, i: Option<&'a str>) -> &mut Self {
function product_name (line 132) | pub fn product_name(&mut self, p: Option<&'a str>) -> &mut Self {
function timestamp (line 141) | pub fn timestamp(&mut self, t: Option<&'a str>) -> &mut Self {
function build (line 147) | pub fn build(&mut self) -> Execution {
method default (line 163) | fn default() -> Self {
type Execution (line 170) | pub struct Execution {
method run (line 184) | pub fn run(self) -> Result<()> {
method homepage (line 261) | fn homepage(&self, manifest: &Package) -> Option<String> {
method msi (line 268) | fn msi(&self, target_directory: &Path) -> Result<PathBuf> {
method signer (line 297) | fn signer(&self) -> Result<Command> {
method default (line 352) | fn default() -> Self {
function bin_path_works (line 365) | fn bin_path_works() {
function capture_output_works (line 373) | fn capture_output_works() {
function description_works (line 380) | fn description_works() {
function homepage_works (line 388) | fn homepage_works() {
function input_works (line 396) | fn input_works() {
function product_name_works (line 404) | fn product_name_works() {
function timestamp_works (line 412) | fn timestamp_works() {
constant MIN_MANIFEST (line 428) | const MIN_MANIFEST: &str = r#"[package]
constant HOMEPAGE_MANIFEST (line 434) | const HOMEPAGE_MANIFEST: &str = r#"[package]
function homepage_without_homepage_field_works (line 442) | fn homepage_without_homepage_field_works() {
function homepage_with_homepage_field_works (line 452) | fn homepage_with_homepage_field_works() {
function homepage_with_override_works (line 462) | fn homepage_with_override_works() {
function msi_with_nonexistent_installer_fails (line 477) | fn msi_with_nonexistent_installer_fails() {
function msi_with_existing_file_works (line 483) | fn msi_with_existing_file_works() {
function signer_works (line 497) | fn signer_works() {
function signer_with_nonexisting_path_fails (line 503) | fn signer_with_nonexisting_path_fails() {
function signer_with_nonexistent_environment_path_fails (line 512) | fn signer_with_nonexistent_environment_path_fails() {
FILE: src/stored_path.rs
type StoredPathBuf (line 63) | pub struct StoredPathBuf(String);
method new (line 71) | pub fn new(v: String) -> Self {
method from_std_path (line 81) | pub fn from_std_path(path: &Path) -> Option<Self> {
method from_utf8_path (line 96) | pub fn from_utf8_path(path: &Utf8Path) -> Self {
type Target (line 220) | type Target = StoredPath;
method deref (line 221) | fn deref(&self) -> &Self::Target {
method borrow (line 232) | fn borrow(&self) -> &StoredPath {
method from (line 244) | fn from(v: String) -> Self {
method from (line 259) | fn from(v: &'a str) -> Self {
method fmt (line 274) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method fmt (line 279) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
type StoredPath (line 67) | pub struct StoredPath(str);
method new (line 128) | pub fn new(v: &str) -> &Self {
method as_str (line 135) | pub fn as_str(&self) -> &str {
method extension (line 140) | pub fn extension(&self) -> Option<&str> {
method file_stem (line 145) | pub fn file_stem(&self) -> Option<&str> {
method stem_and_extension (line 150) | fn stem_and_extension(&self) -> (Option<&str>, Option<&str>) {
method file_name (line 173) | pub fn file_name(&self) -> Option<&str> {
type Target (line 226) | type Target = str;
method deref (line 227) | fn deref(&self) -> &Self::Target {
type Owned (line 237) | type Owned = StoredPathBuf;
method to_owned (line 239) | fn to_owned(&self) -> StoredPathBuf {
method fmt (line 264) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method fmt (line 269) | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
method from (line 249) | fn from(v: StoredPathBuf) -> Self {
method from (line 254) | fn from(v: &'a StoredPathBuf) -> Self {
function absolute_windows_path_conversion (line 290) | fn absolute_windows_path_conversion() {
function verbatim_absolute_windows_path_conversion (line 301) | fn verbatim_absolute_windows_path_conversion() {
function relative_windows_path_conversion (line 313) | fn relative_windows_path_conversion() {
function absolute_unix_path_conversion (line 324) | fn absolute_unix_path_conversion() {
function relative_unix_path_conversion (line 335) | fn relative_unix_path_conversion() {
function mixed_path_conversion1 (line 346) | fn mixed_path_conversion1() {
function mixed_path_conversion2 (line 357) | fn mixed_path_conversion2() {
function mixed_path_unconverted1 (line 368) | fn mixed_path_unconverted1() {
function mixed_path_unconverted2 (line 380) | fn mixed_path_unconverted2() {
function empty_path (line 392) | fn empty_path() {
function just_file (line 404) | fn just_file() {
function trail_slash_1 (line 416) | fn trail_slash_1() {
function trail_slash_2 (line 427) | fn trail_slash_2() {
function trail_slash_3 (line 438) | fn trail_slash_3() {
function trail_slash_4 (line 449) | fn trail_slash_4() {
function trail_slash_5 (line 460) | fn trail_slash_5() {
function trail_slash_6 (line 471) | fn trail_slash_6() {
function trail_slash_7 (line 482) | fn trail_slash_7() {
function trail_slash_8 (line 493) | fn trail_slash_8() {
function just_dot (line 504) | fn just_dot() {
function just_dotfile (line 515) | fn just_dotfile() {
function just_dotfile_txt (line 526) | fn just_dotfile_txt() {
function just_dotdot (line 537) | fn just_dotdot() {
function opaque_dotdot1 (line 548) | fn opaque_dotdot1() {
function opaque_dotdot2 (line 559) | fn opaque_dotdot2() {
FILE: src/templates/mod.rs
type Template (line 39) | pub enum Template {
method id (line 101) | pub fn id(&self) -> &str {
method possible_values (line 138) | pub fn possible_values() -> &'static Vec<String> {
method license_ids (line 158) | pub fn license_ids() -> Vec<String> {
method to_str (line 167) | pub fn to_str(&self) -> &str {
method fmt (line 179) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
type Err (line 185) | type Err = Error;
method from_str (line 187) | fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
function from_str_wxs_returns_v3 (line 207) | fn from_str_wxs_returns_v3() {
function from_str_wxs_uppercase_returns_v3 (line 212) | fn from_str_wxs_uppercase_returns_v3() {
function from_str_wxs4_returns_v4 (line 217) | fn from_str_wxs4_returns_v4() {
function from_str_wxs4_uppercase_returns_v4 (line 222) | fn from_str_wxs4_uppercase_returns_v4() {
function from_str_v4_namespace_uri_returns_v4 (line 227) | fn from_str_v4_namespace_uri_returns_v4() {
function from_str_unknown_fails (line 235) | fn from_str_unknown_fails() {
function wxs_v3_to_str_is_not_empty (line 240) | fn wxs_v3_to_str_is_not_empty() {
function wxs_v4_to_str_is_not_empty (line 245) | fn wxs_v4_to_str_is_not_empty() {
function wxs_v4_to_str_contains_v4_namespace (line 250) | fn wxs_v4_to_str_contains_v4_namespace() {
function possible_values_contains_wxs4 (line 260) | fn possible_values_contains_wxs4() {
FILE: src/toolset/ext.rs
type WxsDependency (line 25) | pub type WxsDependency = Box<dyn WixExtension>;
method from (line 148) | fn from(value: &sxd_document::dom::Namespace<'a>) -> Self {
type WixExtension (line 28) | pub trait WixExtension {
method package_name (line 30) | fn package_name(&self) -> &str;
method namespace_prefix (line 32) | fn namespace_prefix(&self) -> &str;
method namespace_uri (line 34) | fn namespace_uri(&self) -> &str;
method package_name (line 134) | fn package_name(&self) -> &str {
method namespace_prefix (line 138) | fn namespace_prefix(&self) -> &str {
method namespace_uri (line 142) | fn namespace_uri(&self) -> &str {
method package_name (line 178) | fn package_name(&self) -> &str {
method namespace_prefix (line 182) | fn namespace_prefix(&self) -> &str {
method namespace_uri (line 186) | fn namespace_uri(&self) -> &str {
method package_name (line 246) | fn package_name(&self) -> &str {
method namespace_prefix (line 266) | fn namespace_prefix(&self) -> &str {
method namespace_uri (line 285) | fn namespace_uri(&self) -> &str {
type PackageCache (line 39) | pub struct PackageCache {
method add (line 51) | pub fn add(&mut self, name: impl Into<String>, version: Version) {
method installed (line 57) | pub fn installed(&self, ext: &impl WixExtension) -> bool {
method installed_package_name (line 63) | pub fn installed_package_name(&self, package_name: &str) -> bool {
method iter_missing (line 69) | pub fn iter_missing(&self) -> impl Iterator<Item = &String> {
method iter_installed (line 75) | pub fn iter_installed(&self) -> impl Iterator<Item = (&String, &Versio...
method add_missing (line 81) | pub fn add_missing(&mut self, name: impl Into<String>) {
method install_missing (line 87) | pub fn install_missing(
method from (line 124) | fn from(toolset: Toolset) -> Self {
type UnknownExtNamespace (line 172) | pub struct UnknownExtNamespace {
type WellKnownExtentions (line 200) | pub enum WellKnownExtentions {
constant BAL_EXT (line 305) | const BAL_EXT: &str = "WixToolset.BootstrapperApplications.wixext";
constant BAL_NS_PREFIX (line 306) | const BAL_NS_PREFIX: &str = "bal";
constant BAL_NS_URI (line 307) | const BAL_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/bal";
constant COMPLUS_EXT (line 309) | const COMPLUS_EXT: &str = "WixToolset.ComPlus.wixext";
constant COMPLUS_NS_PREFIX (line 310) | const COMPLUS_NS_PREFIX: &str = "complus";
constant COMPLUS_NS_URI (line 311) | const COMPLUS_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/compl...
constant DEPENDENCY_EXT (line 313) | const DEPENDENCY_EXT: &str = "WixToolset.Dependency.wixext";
constant DEPENDENCY_NS_PREFIX (line 316) | const DEPENDENCY_NS_PREFIX: &str = "dep";
constant DEPENDENCY_NS_URI (line 317) | const DEPENDENCY_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/de...
constant DIRECTX_EXT (line 319) | const DIRECTX_EXT: &str = "WixToolset.DirectX.wixext";
constant DIRECTX_NS_PREFIX (line 320) | const DIRECTX_NS_PREFIX: &str = "directx";
constant DIRECTX_NS_URI (line 321) | const DIRECTX_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/direc...
constant FIREWALL_EXT (line 323) | const FIREWALL_EXT: &str = "WixToolset.Firewall.wixext";
constant FIREWALL_NS_PREFIX (line 326) | const FIREWALL_NS_PREFIX: &str = "fw";
constant FIREWALL_NS_URI (line 327) | const FIREWALL_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/fire...
constant HTTP_EXT (line 329) | const HTTP_EXT: &str = "WixToolset.Http.wixext";
constant HTTP_NS_PREFIX (line 330) | const HTTP_NS_PREFIX: &str = "http";
constant HTTP_NS_URI (line 331) | const HTTP_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/http";
constant IIS_EXT (line 333) | const IIS_EXT: &str = "WixToolset.Iis.wixext";
constant IIS_NS_PREFIX (line 334) | const IIS_NS_PREFIX: &str = "iis";
constant IIS_NS_URI (line 335) | const IIS_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/iis";
constant MSMQ_EXT (line 337) | const MSMQ_EXT: &str = "WixToolset.Msmq.wixext";
constant MSMQ_NS_PREFIX (line 338) | const MSMQ_NS_PREFIX: &str = "msmq";
constant MSMQ_NS_URI (line 339) | const MSMQ_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/msmq";
constant NETFX_EXT (line 341) | const NETFX_EXT: &str = "WixToolset.Netfx.wixext";
constant NETFX_NS_PREFIX (line 342) | const NETFX_NS_PREFIX: &str = "netfx";
constant NETFX_NS_URI (line 343) | const NETFX_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/netfx";
constant POWERSHELL_EXT (line 345) | const POWERSHELL_EXT: &str = "WixToolset.PowerShell.wixext";
constant POWERSHELL_NS_PREFIX (line 346) | const POWERSHELL_NS_PREFIX: &str = "powershell";
constant POWERSHELL_NS_URI (line 347) | const POWERSHELL_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/po...
constant SQL_EXT (line 349) | const SQL_EXT: &str = "WixToolset.Sql.wixext";
constant SQL_NS_PREFIX (line 350) | const SQL_NS_PREFIX: &str = "sql";
constant SQL_NS_URI (line 351) | const SQL_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/sql";
constant UI_EXT (line 353) | const UI_EXT: &str = "WixToolset.UI.wixext";
constant UI_NS_PREFIX (line 354) | const UI_NS_PREFIX: &str = "ui";
constant UI_NS_URI (line 355) | const UI_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/ui";
constant UTIL_EXT (line 357) | const UTIL_EXT: &str = "WixToolset.Util.wixext";
constant UTIL_NS_PREFIX (line 358) | const UTIL_NS_PREFIX: &str = "util";
constant UTIL_NS_URI (line 359) | const UTIL_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/util";
constant VS_EXT (line 361) | const VS_EXT: &str = "WixToolset.VisualStudio.wixext";
constant VS_NS_PREFIX (line 362) | const VS_NS_PREFIX: &str = "vs";
constant VS_NS_URI (line 363) | const VS_NS_URI: &str = "http://wixtoolset.org/schemas/v4/wxs/vs";
function test_package_cache_installed (line 374) | fn test_package_cache_installed() {
function test_package_cache_install_missing_non_global (line 386) | fn test_package_cache_install_missing_non_global() {
function test_package_cache_install_missing_global (line 412) | fn test_package_cache_install_missing_global() {
function test_package_cache_install_missing_work_dir (line 438) | fn test_package_cache_install_missing_work_dir() {
FILE: src/toolset/includes.rs
type Includes (line 23) | pub trait Includes {
method includes (line 25) | fn includes(&self) -> Option<&Vec<PathBuf>>;
method wxs_sources (line 28) | fn wxs_sources(&self, package: &Package) -> crate::Result<Vec<PathBuf>> {
method includes (line 183) | fn includes(&self) -> Option<&Vec<PathBuf>> {
type ProjectProvider (line 131) | pub trait ProjectProvider: Includes {
method work_dir (line 133) | fn work_dir(&self) -> Option<PathBuf> {
method toolset (line 138) | fn toolset(&self) -> Toolset;
method create_project (line 145) | fn create_project(&self, package: &Package) -> crate::Result<Project> {
method toolset (line 165) | fn toolset(&self) -> Toolset {
method toolset (line 170) | fn toolset(&self) -> Toolset {
type NoIncludes (line 181) | struct NoIncludes;
function package_from_json (line 189) | fn package_from_json(json: &str) -> cargo_metadata::Package {
constant PACKAGE_TEMPLATE (line 193) | const PACKAGE_TEMPLATE: &str = r#"{
function non_string_metadata_include_errors (line 206) | fn non_string_metadata_include_errors() {
function mixed_valid_and_invalid_metadata_include_errors (line 237) | fn mixed_valid_and_invalid_metadata_include_errors() {
function empty_metadata_include_array_errors_no_sources (line 263) | fn empty_metadata_include_array_errors_no_sources() {
function no_metadata_no_wix_dir_errors (line 292) | fn no_metadata_no_wix_dir_errors() {
FILE: src/toolset/mod.rs
type Toolset (line 50) | pub enum Toolset {
method fmt (line 65) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
method is_legacy (line 148) | pub fn is_legacy(&self) -> bool {
method is_modern (line 158) | pub fn is_modern(&self) -> bool {
method is_legacy (line 172) | pub fn is_legacy(&self) -> bool {
method is_modern (line 178) | pub fn is_modern(&self) -> bool {
method wix (line 183) | pub fn wix(&self, action: impl Into<ToolsetAction>) -> crate::Result<T...
method compiler (line 202) | pub fn compiler(&self, bin_path: Option<PathBuf>) -> crate::Result<Too...
type ToolsetCommand (line 81) | pub struct ToolsetCommand {
method split_into_std (line 273) | pub fn split_into_std(self) -> (std::process::Command, ToolsetAction) {
method output (line 279) | pub fn output(mut self) -> crate::Result<Output> {
method test_output (line 324) | fn test_output(&self) -> Output {
method from (line 363) | fn from(value: std::process::Command) -> Self {
type Error (line 399) | type Error = crate::Error;
method try_from (line 401) | fn try_from(value: ToolsetAction) -> crate::Result<Self> {
method fmt (line 512) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type ToolsetAction (line 90) | pub enum ToolsetAction {
method from (line 384) | fn from(s: &'a str) -> Self {
type ToolsetSetupMode (line 123) | pub enum ToolsetSetupMode {
method setup (line 228) | pub fn setup(
method is_enabled (line 260) | pub fn is_enabled(&self) -> bool {
method use_global (line 266) | pub fn use_global(&self) -> bool {
method default (line 352) | fn default() -> Self {
type Err (line 376) | type Err = crate::Error;
method from_str (line 378) | fn from_str(s: &str) -> Result<Self, Self::Err> {
type Target (line 521) | type Target = std::process::Command;
method deref (line 523) | fn deref(&self) -> &Self::Target {
method deref_mut (line 529) | fn deref_mut(&mut self) -> &mut Self::Target {
function test_toolset_compiler_is_correct_with_defaults (line 547) | fn test_toolset_compiler_is_correct_with_defaults() {
function test_toolset_wix_action (line 576) | fn test_toolset_wix_action() {
function test_toolset_legacy_action (line 624) | fn test_toolset_legacy_action() {
function test_toolset_fmt_debug (line 652) | fn test_toolset_fmt_debug() {
function test_toolset_wix_command_err_on_modern (line 658) | fn test_toolset_wix_command_err_on_modern() {
function test_toolset_migrate_global_mode (line 666) | fn test_toolset_migrate_global_mode() {
function test_toolset_migrate_vendor_mode (line 695) | fn test_toolset_migrate_vendor_mode() {
FILE: src/toolset/project.rs
constant LEGACY_NAMESPACE_URI (line 34) | const LEGACY_NAMESPACE_URI: &str = "http://schemas.microsoft.com/wix/200...
constant V4_NAMESPACE_URI (line 37) | pub const V4_NAMESPACE_URI: &str = "http://wixtoolset.org/schemas/v4/wxs";
constant WIX_ROOT_ELEMENT_XPATH (line 40) | const WIX_ROOT_ELEMENT_XPATH: &str = "/*[local-name()='Wix']";
constant WIX_PACKAGE_ROOT_ELEMENT_XPATH (line 42) | const WIX_PACKAGE_ROOT_ELEMENT_XPATH: &str = "//w:Package";
type WxsSchema (line 46) | pub enum WxsSchema {
function open_wxs_source (line 61) | pub fn open_wxs_source(path: PathBuf) -> crate::Result<WixSource> {
type Project (line 129) | pub struct Project {
method try_new (line 145) | pub fn try_new(toolset: Toolset) -> crate::Result<Self> {
method configure_toolset_extensions (line 169) | pub fn configure_toolset_extensions(&self, toolset: &mut ToolsetComman...
method add_wxs (line 194) | pub fn add_wxs(&mut self, source: PathBuf) -> crate::Result<()> {
method upgrade (line 213) | pub fn upgrade(&mut self, work_dir: Option<&PathBuf>) -> crate::Result...
method restore (line 264) | pub fn restore(&mut self, use_global: bool, work_dir: Option<&PathBuf>...
method sources (line 272) | pub fn sources(&self) -> impl Iterator<Item = &WixSource> {
method package_count (line 278) | pub fn package_count(&self) -> usize {
method is_bundle (line 287) | pub fn is_bundle(&self) -> bool {
method installer_kind (line 293) | pub fn installer_kind(&self) -> InstallerKind {
method load_ext_cache (line 303) | fn load_ext_cache(&mut self) -> crate::Result<()> {
function test_open_wxs (line 374) | fn test_open_wxs() {
function test_project_create (line 385) | fn test_project_create() {
function test_project_upgrade (line 405) | fn test_project_upgrade() {
function test_project_upgrade_extension_detection (line 421) | fn test_project_upgrade_extension_detection() {
function validate_wxs_ext (line 459) | pub fn validate_wxs_ext(source: &WixSource, ext: impl WixExtension) {
type TestProject (line 471) | pub struct TestProject {
method new (line 485) | pub fn new(test_name: &str, expected_wxs_name: &'static str) -> Self {
constant MIN_MANIFEST (line 477) | const MIN_MANIFEST: &str = r#"[package]
method includes (line 510) | fn includes(&self) -> Option<&Vec<PathBuf>> {
method work_dir (line 516) | fn work_dir(&self) -> Option<PathBuf> {
method toolset (line 520) | fn toolset(&self) -> Toolset {
function test_project_package_count (line 580) | fn test_project_package_count() {
function test_project_installer_kind_msi (line 588) | fn test_project_installer_kind_msi() {
function test_project_installer_extension_from_well_known_exts (line 597) | fn test_project_installer_extension_from_well_known_exts() {
function test_upgrade_duplicate_filenames_errors (line 609) | fn test_upgrade_duplicate_filenames_errors() {
function test_project_package_count_multiple (line 681) | fn test_project_package_count_multiple() {
function test_project_fragment_only_has_zero_packages (line 704) | fn test_project_fragment_only_has_zero_packages() {
function test_project_bundle_installer_kind (line 725) | fn test_project_bundle_installer_kind() {
FILE: src/toolset/source.rs
type WixSource (line 24) | pub struct WixSource {
method can_upgrade (line 39) | pub fn can_upgrade(&self) -> bool {
method is_modern (line 57) | pub fn is_modern(&self) -> bool {
method is_bundle (line 66) | pub fn is_bundle(&self) -> bool {
method check_deps (line 71) | pub fn check_deps(&self, package_cache: &mut PackageCache) {
method upgrade (line 100) | pub fn upgrade(&self, work_dir: Option<&PathBuf>) -> crate::Result<Sel...
method try_move_to_installer_destination (line 156) | pub fn try_move_to_installer_destination(
method is_main (line 217) | fn is_main(&self) -> bool {
method fmt (line 223) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function test_source_can_upgrade (line 234) | fn test_source_can_upgrade() {
function test_skip_add_unknown_ext_to_package_cache (line 276) | fn test_skip_add_unknown_ext_to_package_cache() {
function test_source_is_bundle (line 299) | fn test_source_is_bundle() {
FILE: src/toolset/test.rs
type SharedTestShim (line 19) | pub type SharedTestShim = Arc<dyn TestShim + 'static>;
type ToolsetTest (line 24) | pub struct ToolsetTest {
function ok_stdout (line 31) | pub fn ok_stdout(out: impl Into<String>) -> ToolsetTest {
function fail_stdout (line 36) | pub fn fail_stdout(out: impl Into<String>) -> ToolsetTest {
function test (line 41) | fn test<const SUCCESS: bool>(out: impl Into<String>, err: impl Into<Stri...
type TestShim (line 50) | pub trait TestShim {
method on_output (line 52) | fn on_output(&self, action: &ToolsetAction, cmd: &std::process::Comman...
method on_output (line 75) | fn on_output(&self, action: &ToolsetAction, cmd: &std::process::Comman...
function toolset (line 56) | pub fn toolset(shim: impl TestShim + 'static) -> Toolset {
function legacy_toolset (line 64) | pub fn legacy_toolset(shim: impl TestShim + 'static) -> Toolset {
method fmt (line 130) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: tests/common/mod.rs
constant TARGET_NAME (line 30) | pub const TARGET_NAME: &str = "target";
constant PACKAGE_NAME (line 34) | pub const PACKAGE_NAME: &str = "cargowixtest";
constant NO_CAPTURE_VAR_NAME (line 36) | pub const NO_CAPTURE_VAR_NAME: &str = "CARGO_WIX_TEST_NO_CAPTURE";
constant PERSIST_VAR_NAME (line 38) | pub const PERSIST_VAR_NAME: &str = "CARGO_WIX_TEST_PERSIST";
constant MISC_NAME (line 40) | pub const MISC_NAME: &str = "misc";
constant SUBPACKAGE1_NAME (line 42) | pub const SUBPACKAGE1_NAME: &str = "subproject1";
constant SUBPACKAGE2_NAME (line 43) | pub const SUBPACKAGE2_NAME: &str = "subproject2";
function create_test_package_at_path (line 45) | fn create_test_package_at_path(path: &Path, package_name: &str) {
function add_license_to_package (line 87) | pub fn add_license_to_package(path: &Path, license: &str) {
function create_test_package (line 135) | pub fn create_test_package() -> TempDir {
function create_test_package_multiple_binaries (line 160) | pub fn create_test_package_multiple_binaries() -> TempDir {
function create_test_package_metadata (line 221) | pub fn create_test_package_metadata() -> TempDir {
function create_test_package_profile (line 256) | pub fn create_test_package_profile(profile: &str) -> TempDir {
function create_test_package_multiple_wxs_sources (line 290) | pub fn create_test_package_multiple_wxs_sources() -> TempDir {
function create_test_workspace (line 314) | pub fn create_test_workspace() -> TempDir {
function evaluate_xpath (line 339) | pub fn evaluate_xpath(wxs: &Path, xpath: &str) -> String {
function init_logging (line 400) | pub fn init_logging() {
FILE: tests/create.rs
function run (line 59) | fn run(b: &mut Builder) -> Result<()> {
function run_with_package (line 66) | fn run_with_package(b: &mut Builder, package_path: &Path) -> Result<()> {
function default_works (line 78) | fn default_works() {
function russian_culture_works (line 98) | fn russian_culture_works() {
function debug_build_works (line 118) | fn debug_build_works() {
function profile_build_works (line 138) | fn profile_build_works() {
function debug_name_works (line 160) | fn debug_name_works() {
function metadata_works (line 180) | fn metadata_works() {
function output_trailing_forwardslash_works (line 200) | fn output_trailing_forwardslash_works() {
function output_trailing_backslash_works (line 222) | fn output_trailing_backslash_works() {
function output_existing_dir_works (line 244) | fn output_existing_dir_works() {
function output_file_without_extension_works (line 266) | fn output_file_without_extension_works() {
function output_file_with_extension_works (line 288) | fn output_file_with_extension_works() {
function init_with_package_section_fields_works (line 309) | fn init_with_package_section_fields_works() {
function init_with_all_options_works (line 370) | fn init_with_all_options_works() {
function init_with_banner_option_works (line 431) | fn init_with_banner_option_works() {
function init_with_binaries_option_works (line 460) | fn init_with_binaries_option_works() {
function init_with_multiple_binaries_works (line 489) | fn init_with_multiple_binaries_works() {
function init_with_description_option_works (line 509) | fn init_with_description_option_works() {
function init_with_dialog_option_works (line 533) | fn init_with_dialog_option_works() {
function init_with_eula_in_cwd_works (line 562) | fn init_with_eula_in_cwd_works() {
function init_with_eula_in_docs_works (line 591) | fn init_with_eula_in_docs_works() {
function init_with_help_url_option_works (line 622) | fn init_with_help_url_option_works() {
function init_with_license_in_cwd_works (line 646) | fn init_with_license_in_cwd_works() {
function init_with_license_in_docs_works (line 675) | fn init_with_license_in_docs_works() {
function init_with_manufacturer_option_works (line 706) | fn init_with_manufacturer_option_works() {
function init_with_product_icon_option_works (line 730) | fn init_with_product_icon_option_works() {
function init_with_product_name_option_works (line 759) | fn init_with_product_name_option_works() {
function input_works_inside_cwd (line 788) | fn input_works_inside_cwd() {
function input_works_outside_cwd (line 809) | fn input_works_outside_cwd() {
function includes_works_with_wix_dir (line 835) | fn includes_works_with_wix_dir() {
function includes_works_without_wix_dir (line 860) | fn includes_works_without_wix_dir() {
function includes_works_with_input_outside_cwd (line 884) | fn includes_works_with_input_outside_cwd() {
function compiler_args_flags_only_works (line 915) | fn compiler_args_flags_only_works() {
function compiler_args_options_works (line 935) | fn compiler_args_options_works() {
function linker_args_flags_only_works (line 956) | fn linker_args_flags_only_works() {
function compiler_and_linker_args_works_with_metadata (line 976) | fn compiler_and_linker_args_works_with_metadata() {
function custom_target_dir_works (line 996) | fn custom_target_dir_works() {
function workspace_package_works (line 1024) | fn workspace_package_works() {
function cross_compilation_works (line 1048) | fn cross_compilation_works() {
function profile_cross_compilation_works (line 1068) | fn profile_cross_compilation_works() {
FILE: tests/initialize.rs
function default_works (line 52) | fn default_works() {
function description_works (line 76) | fn description_works() {
function help_url_works (line 93) | fn help_url_works() {
function manufacturer_works (line 110) | fn manufacturer_works() {
function product_name_works (line 130) | fn product_name_works() {
function binaries_works (line 166) | fn binaries_works() {
function input_works (line 194) | fn input_works() {
function output_works (line 212) | fn output_works() {
function input_with_output_works (line 230) | fn input_with_output_works() {
function license_with_txt_file_works (line 270) | fn license_with_txt_file_works() {
function license_with_rtf_file_works (line 301) | fn license_with_rtf_file_works() {
function eula_works (line 339) | fn eula_works() {
function mit_license_id_works (line 363) | fn mit_license_id_works() {
function apache2_license_id_works (line 431) | fn apache2_license_id_works() {
function gpl3_license_id_works (line 499) | fn gpl3_license_id_works() {
function license_file_field_with_rtf_file_works (line 567) | fn license_file_field_with_rtf_file_works() {
function license_file_field_with_txt_file_works (line 632) | fn license_file_field_with_txt_file_works() {
function banner_works (line 688) | fn banner_works() {
function dialog_works (line 713) | fn dialog_works() {
function product_icon_works (line 738) | fn product_icon_works() {
function multiple_binaries_works (line 763) | fn multiple_binaries_works() {
function workspace_no_package_fails (line 822) | fn workspace_no_package_fails() {
function workspace_package_works (line 834) | fn workspace_package_works() {
function workspace_package_with_license_works (line 861) | fn workspace_package_with_license_works() {
FILE: xtask/src/main.rs
type XTask (line 10) | enum XTask {
function copy (line 15) | fn copy<U: AsRef<Path>, V: AsRef<Path>>(from: U, to: V) -> Result<()> {
function main (line 62) | fn main() -> Result<()> {
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (764K chars).
[
{
"path": ".cargo/config.toml",
"chars": 43,
"preview": "[alias]\r\nxtask = \"run --package xtask --\"\r\n"
},
{
"path": ".github/dependabot.yml",
"chars": 312,
"preview": "# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupd"
},
{
"path": ".github/workflows/ci.yml",
"chars": 1570,
"preview": "name: CI\n\non:\n push:\n branches:\n - main\n pull_request:\n branches:\n - main\n\n workflow_dispatch:\n\njobs:"
},
{
"path": ".gitignore",
"chars": 62,
"preview": "/target/\n**/*.rs.bk\n/wix/\n.lock\n.wix/\n.test**\nwix0/\n**.profraw"
},
{
"path": "Cargo.toml",
"chars": 1249,
"preview": "[package]\nname = \"cargo-wix\"\ndescription = \"Build Windows installers using the WiX Toolset\"\nversion = \"0.3.9\"\nauthors = "
},
{
"path": "LICENSE-APACHE",
"chars": 11041,
"preview": " Apache License\r\n Version 2.0, January 2004\r\n ht"
},
{
"path": "LICENSE-MIT",
"chars": 1083,
"preview": "Copyright (c) 2017 Christopher R. Field\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy "
},
{
"path": "README.md",
"chars": 10290,
"preview": "# cargo-wix: A cargo subcommand to create Windows installers\n\nA subcommand for [Cargo] that builds a Windows installer ("
},
{
"path": "src/clean.rs",
"chars": 4374,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/create.rs",
"chars": 88978,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/initialize.rs",
"chars": 34550,
"preview": "// Copyright (C) 2018 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/lib.rs",
"chars": 41620,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/licenses.rs",
"chars": 10939,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/main.rs",
"chars": 111736,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/migrate.rs",
"chars": 7314,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/print/license.rs",
"chars": 8223,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/print/mod.rs",
"chars": 4849,
"preview": "// Copyright (C) 2017 Christopher R. Field.\r\n//\r\n// Licensed under the Apache License, Version 2.0 (the \"License\");\r\n// "
},
{
"path": "src/print/wxs.rs",
"chars": 73387,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/purge.rs",
"chars": 6481,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/sign.rs",
"chars": 17721,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/stored_path.rs",
"chars": 22486,
"preview": "//! Utilities for working with paths that need to be stored in files in a cross-platform way.\n//!\n//! Big picture: work "
},
{
"path": "src/templates/Apache-2.0.rtf.mustache",
"chars": 12153,
"preview": "{{!\r\nCopyright (C) 2017 Christopher R. Field.\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou ma"
},
{
"path": "src/templates/GPL-3.0.rtf.mustache",
"chars": 37951,
"preview": "{{!\r\nCopyright (C) 2017 Christopher R. Field.\r\n\r\nLicensed under the Apache License, Version 2.0 (the \"License\");\r\nyou ma"
},
{
"path": "src/templates/main.v3.wxs.mustache",
"chars": 13927,
"preview": "<?xml version='1.0' encoding='utf-8'?>\n<!--\n Copyright (C) 2017 Christopher R. Field.\n\n Licensed under the Apache Lice"
},
{
"path": "src/templates/main.v4.wxs.mustache",
"chars": 13366,
"preview": "<!--\n Copyright (C) 2017 Christopher R. Field.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you"
},
{
"path": "src/templates/mod.rs",
"chars": 8110,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/ext.rs",
"chars": 17404,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/includes.rs",
"chars": 10301,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/mod.rs",
"chars": 25120,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/project.rs",
"chars": 29223,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/source.rs",
"chars": 12101,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "src/toolset/test.rs",
"chars": 4801,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "tests/common/mod.rs",
"chars": 15846,
"preview": "#![allow(dead_code)]\n\nextern crate assert_fs;\nextern crate env_logger;\nextern crate log;\nextern crate sxd_document;\nexte"
},
{
"path": "tests/common/one.wxs",
"chars": 646,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n <Product Id=\"*\" \n Nam"
},
{
"path": "tests/common/post_v4/fragment.wxs",
"chars": 143,
"preview": "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\">\n <Fragment test"
},
{
"path": "tests/common/post_v4/main.wxs",
"chars": 132,
"preview": "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\">\n <Package></Pac"
},
{
"path": "tests/common/pre_v4/main.wxs",
"chars": 60,
"preview": "<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'></Wix>"
},
{
"path": "tests/common/three.wxs",
"chars": 263,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Wix xmlns=\"http://schemas.microsoft.com/wix/2006/wi\">\n <Fragment>\n <Feature "
},
{
"path": "tests/common/two.wxs",
"chars": 764,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>\n <?define PlatformP"
},
{
"path": "tests/common/well_known_exts/main.wxs",
"chars": 906,
"preview": "<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\"\n xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\"\n xmlns:bal=\""
},
{
"path": "tests/create.rs",
"chars": 39159,
"preview": "#![cfg(windows)]\n\n// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the "
},
{
"path": "tests/initialize.rs",
"chars": 30044,
"preview": "// Copyright (C) 2017 Christopher R. Field.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you"
},
{
"path": "xtask/Cargo.toml",
"chars": 169,
"preview": "[package]\nname = \"xtask\"\nversion = \"0.1.0\"\nauthors = [\"Chris Field <cfield2@gmail.com>\"]\nedition = \"2018\"\npublish = fals"
},
{
"path": "xtask/src/main.rs",
"chars": 3699,
"preview": "use anyhow::{bail, Result};\nuse structopt::StructOpt;\n\nuse std::env;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse st"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the volks73/cargo-wix GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (717.4 KB), approximately 168.2k tokens, and a symbol index with 860 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.