build(wix): the komorebi installer ships komorebi-recorder, which has no installer of its own

This commit is contained in:
LGUG2Z committed 2026-11-05 15:03:27 -08:00
1 parent c64c71541b
commit 9854f90db7
4 files changed
+13 -242

No files matched your search

+3 -115
View File
@@ -1,17 +1,10 @@
//! The Windows installer (`wix/main.wxs`) has to ship every binary the crate builds and has to be
//! its own product: `WiX` itself only runs on Windows, so these checks are what a Mac or Linux
//! The recorder ships in the komorebi installer (`wix/main.wxs`), so that installer has to ship every
//! binary this crate builds: `WiX` itself only runs on Windows, so this check is what a Mac or Linux
//! machine can say about the installer.
use std::path::Path;
use std::path::PathBuf;
/// komorebi's own installer: reusing its `UpgradeCode` would make the two products upgrade and
/// uninstall each other.
const KOMOREBI_UPGRADE_CODE: &str = "F8B967B5-7E7B-4E3A-895B-B789EC898B54";
/// komorebi's `PATH` component, which the installer tracks the `PATH` entry by.
const KOMOREBI_PATH_COMPONENT: &str = "6C6DF276-06C4-4675-BDED-48C5C2BC9BC5";
fn repository() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
@@ -23,28 +16,6 @@ fn installer() -> String {
std::fs::read_to_string(repository().join("wix/main.wxs")).expect("wix/main.wxs")
}
/// The value of attribute `name` on the first element of `tag` in the installer.
fn attribute(text: &str, tag: &str, name: &str) -> String {
let start = text
.find(&format!("<{tag} "))
.unwrap_or_else(|| panic!("no <{tag}> in the installer"));
let element = &text[start..start + text[start..].find('>').expect("a closed tag")];
let key = format!("{name}='");
let value = element
.find(&key)
.unwrap_or_else(|| panic!("<{tag}> has no {name}"))
+ key.len();
element[value..][..element[value..].find('\'').expect("a closed value")].to_string()
}
fn is_guid(text: &str) -> bool {
let parts: Vec<&str> = text.split('-').collect();
parts.iter().map(|part| part.len()).collect::<Vec<_>>() == [8, 4, 4, 4, 12]
&& parts
.iter()
.all(|part| part.chars().all(|c| c.is_ascii_hexdigit()))
}
/// The binaries cargo builds for this crate: the package's own (`src/main.rs`) and one for each
/// file in `src/bin`.
fn binaries() -> Vec<String> {
@@ -68,7 +39,7 @@ fn binaries() -> Vec<String> {
}
#[test]
fn every_binary_the_crate_builds_is_in_the_installer() {
fn every_binary_the_crate_builds_is_in_the_komorebi_installer() {
let text = installer();
for name in binaries() {
assert!(
@@ -81,86 +52,3 @@ fn every_binary_the_crate_builds_is_in_the_installer() {
);
}
}
#[test]
fn the_installer_installs_nothing_the_crate_does_not_build() {
let text = installer();
let built = binaries();
for piece in text.split("Name='").skip(1) {
let name = &piece[..piece.find('\'').expect("a closed value")];
if let Some(binary) = name.strip_suffix(".exe") {
assert!(
built.iter().any(|built| built == binary),
"wix/main.wxs installs {name}, which this crate does not build"
);
}
}
}
#[test]
fn the_installer_is_its_own_product() {
let text = installer();
let upgrade_code = attribute(&text, "Product", "UpgradeCode");
assert!(
is_guid(&upgrade_code),
"UpgradeCode is not a GUID: {upgrade_code}"
);
assert!(
!upgrade_code.eq_ignore_ascii_case(KOMOREBI_UPGRADE_CODE),
"UpgradeCode is komorebi's: the two products would replace each other"
);
assert_eq!(attribute(&text, "Product", "Name"), env!("CARGO_PKG_NAME"));
assert!(
!text
.to_uppercase()
.contains(&KOMOREBI_PATH_COMPONENT.to_uppercase()),
"the PATH component has komorebi's GUID"
);
let path_guid = attribute(&text, "Component Id='Path'", "Guid");
assert!(
is_guid(&path_guid),
"the PATH component's Guid: {path_guid}"
);
}
#[test]
fn the_license_the_installer_shows_is_there_and_is_the_projects() {
let rtf = std::fs::read_to_string(repository().join("wix/License.rtf")).expect("License.rtf");
assert!(
rtf.contains("Komorebi License"),
"not the project's license"
);
assert!(rtf.contains("Version 2.0.0"), "not the project's license");
assert!(
installer().contains("wix\\License.rtf"),
"main.wxs does not refer to wix\\License.rtf"
);
}
#[test]
fn the_installer_shows_the_icon_the_executables_carry() {
let text = installer();
let id = attribute(&text, "Icon", "Id");
let source = attribute(&text, "Icon", "SourceFile");
assert!(
text.contains(&format!("<Property Id='ARPPRODUCTICON' Value='{id}'")),
"Apps & features is not told to show the icon {id}"
);
assert!(
repository().join(source.replace('\\', "/")).is_file(),
"wix/main.wxs takes its icon from {source}, which is not there"
);
// the resource build.rs compiles into the executables names the same file
let resource =
std::fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/watermelon.rc"))
.expect("assets/watermelon.rc");
let icon = source.rsplit('\\').next().expect("a file name");
assert!(
resource.contains(&format!("\"{icon}\"")),
"the executables carry an icon other than the installer's {icon}: {resource}"
);
}
Binary file not shown.
-127
View File
@@ -1,127 +0,0 @@
<?xml version='1.0' encoding='windows-1252'?>
<!--
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 "cargo wix" subcommand provides a variety of predefined variables available
for customization of this template. The values for each variable are set at
installer creation time. The following variables are available:
TargetTriple = The rustc target triple name.
TargetEnv = The rustc target environment. This is typically either
"msvc" or "gnu" depending on the toolchain downloaded and
installed.
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
does support other vendors, like "uwp".
CargoTargetBinDir = The complete path to the binary (exe). The default would
be "target\release\<BINARY_NAME>.exe" where
"<BINARY_NAME>" is replaced with the name of each binary
target defined in the package's manifest (Cargo.toml). If
a different rustc target triple is used than the host,
i.e. cross-compiling, then the default path would be
"target\<CARGO_TARGET>\<CARGO_PROFILE>\<BINARY_NAME>.exe",
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
variable value and "<CARGO_PROFILE>" is replaced with the
value from the `CargoProfile` variable.
CargoTargetDir = The path to the directory for the build artifacts, i.e.
"target".
CargoProfile = Either "debug" or `release` depending on the build
profile. The default is "release".
Version = The version for the installer. The default is the
"Major.Minor.Fix" semantic versioning number of the Rust
package.
-->
<!--
Please do not remove these pre-processor If-Else blocks. These are used with
the `cargo wix` subcommand to automatically determine the installation
destination for 32-bit versus 64-bit installers. Removal of these lines will
cause installation errors.
-->
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder"?>
<?else ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder"?>
<?endif ?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
<!--
The identifiers below belong to this product alone. `UpgradeCode` is what lets a newer
installer replace an older one, and the GUID of the `Path` component is what the installer
tracks the PATH entry by; reusing either from another product (komorebi has its own) would
make the two upgrade and uninstall each other.
-->
<Product Id='*' Name='komorebi-recorder' UpgradeCode='720EAF41-F769-4E8D-8C47-9BD62A92F107' Manufacturer='LGUG2Z' Language='1033' Codepage='1252' Version='$(var.Version)'>
<Package Id='*' Keywords='Installer' Description='Synchronized screen, log and trace capture for komorebi bug reproduction' Manufacturer='LGUG2Z' InstallerVersion='500' Languages='1033' Compressed='yes' InstallScope='perMachine' SummaryCodepage='1252' />
<MajorUpgrade Schedule='afterInstallInitialize' DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.' />
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1' />
<Property Id='DiskPrompt' Value='komorebi-recorder Installation' />
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
<Directory Id='APPLICATIONFOLDER' Name='komorebi-recorder'>
<Component Id='License' Guid='*'>
<File Id='LicenseFile' Name='License.rtf' DiskId='1' Source='wix\License.rtf' KeyPath='yes' />
</Component>
<Directory Id='Bin' Name='bin'>
<Component Id='Path' Guid='F8CA04B7-C3A0-42D2-8C02-B38EBAA8842F' KeyPath='yes'>
<Environment Id='PATH' Name='PATH' Value='[Bin]' Permanent='no' Part='last' Action='set' System='yes' />
</Component>
<Component Id='binary0' Guid='*'>
<File Id='exe0' Name='komorebi-recorder.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-recorder.exe' KeyPath='yes' />
</Component>
<Component Id='binary1' Guid='*'>
<File Id='exe1' Name='komorebi-recorder-gui.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-recorder-gui.exe' KeyPath='yes' />
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id='Binaries' Title='Application' Description='Installs all binaries and the license.' Level='1' ConfigurableDirectory='APPLICATIONFOLDER' AllowAdvertise='no' Display='expand' Absent='disallow'>
<ComponentRef Id='License' />
<ComponentRef Id='binary0' />
<ComponentRef Id='binary1' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' />
</Feature>
</Feature>
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize' />
<Property Id='ARPHELPLINK' Value='https://github.com/LGUG2Z/komorebi-recorder' />
<!-- the icon of the entry in Apps & features; the executables carry the same one -->
<Icon Id='watermelon.ico' SourceFile='komorebi-recorder\assets\watermelon.ico' />
<Property Id='ARPPRODUCTICON' Value='watermelon.ico' />
<UI>
<UIRef Id='WixUI_FeatureTree' />
</UI>
<WixVariable Id='WixUILicenseRtf' Value='wix\License.rtf' />
</Product>
</Wix>
+10
View File
@@ -104,6 +104,12 @@
<Component Id='binary5' Guid='*'>
<File Id='exe5' Name='komorebi-shortcuts.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-shortcuts.exe' KeyPath='yes' />
</Component>
<Component Id='binary6' Guid='*'>
<File Id='exe6' Name='komorebi-recorder.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-recorder.exe' KeyPath='yes' />
</Component>
<Component Id='binary7' Guid='*'>
<File Id='exe7' Name='komorebi-recorder-gui.exe' DiskId='1' Source='$(var.CargoTargetBinDir)\komorebi-recorder-gui.exe' KeyPath='yes' />
</Component>
</Directory>
</Directory>
</Directory>
@@ -128,6 +134,10 @@
<ComponentRef Id='binary5' />
<ComponentRef Id='binary6' />
<ComponentRef Id='binary7' />
<Feature Id='Environment' Title='PATH Environment Variable' Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.' Level='1' Absent='allow'>
<ComponentRef Id='Path' />
</Feature>