Files
eskin-player/docs/cargo-wix-guide.md

21 KiB
Raw Blame History

cargo-wix 打包与 WiX 常用修改指南

本文档说明本项目如何使用 cargo-wix 生成 Windows MSI 安装包,以及常见 WiX 配置的修改方法。当前项目的安装配置文件是 wix/main.wxsRust 包信息来自 Cargo.toml

1. 当前项目打包方式

本项目使用:

  • Rust/Cargo 构建 release 可执行文件
  • cargo-wix 读取 Cargo.tomlwix/main.wxs
  • WiX Toolset v3 编译并链接生成 .msi

当前可执行文件配置在 Cargo.toml

[[bin]]
name = "ESkinPlayer"
path = "src/main.rs"

当前 WiX 元信息:

[package.metadata.wix]
eula = false

当前 MSI 默认生成到:

target\wix\eskin-model-player-<version>-x86_64.msi

例如:

target\wix\eskin-model-player-5.0.0-x86_64.msi

2. 环境准备

需要安装:

  1. Rust toolchain
  2. WiX Toolset v3
  3. cargo-wix

检查 Rust

rustc --version
cargo --version

安装 cargo-wix

cargo install cargo-wix

检查 cargo-wix

cargo wix --help

本机常见 WiX v3 路径:

C:\Program Files (x86)\WiX Toolset v3.14\bin

检查 WiX 工具是否存在:

Test-Path "C:\Program Files (x86)\WiX Toolset v3.14\bin\candle.exe"
Test-Path "C:\Program Files (x86)\WiX Toolset v3.14\bin\light.exe"

3. 常用打包命令

进入项目目录:

cd D:\eskin-player

清理旧的 WiX 输出:

cargo wix clean

正常打包:

cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --nocapture

说明:

  • --bin-path 指定 WiX Toolset 的 bin 目录。
  • --nocapture 显示 candle.exelight.exe 的详细输出,排查错误时建议加上。
  • 默认会先执行 cargo build --release
  • 默认输出到 target\wix

查看生成的 MSI

Get-ChildItem target\wix\*.msi

指定输出目录:

cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --output dist\

指定输出文件名:

cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --output dist\ESkinPlayer.msi

只打包,不重新构建 Rust

cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --no-build --target-bin-dir target\release

打包后立即安装:

cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --install

4. 重要文件说明

Cargo.toml

Cargo.toml 决定包名、版本、描述、作者、二进制名称等。

常见字段:

[package]
name = "eskin-model-player"
version = "5.0.0"
authors = ["JOYSONQUIN"]
description = "Desktop pressure sensor visualization and playback application."

这些字段会影响:

  • MSI 文件名
  • 控制面板里的产品版本
  • WiX 模板变量

修改版本号后重新打包:

version = "5.0.1"

Windows Installer 的升级判断依赖版本号。发布新的安装包时建议递增版本号。

wix/main.wxs

wix/main.wxs 是 WiX 主配置文件。它决定:

  • 安装包名称
  • 安装范围
  • 安装目录
  • 包含哪些文件
  • 是否写 PATH
  • 卸载行为
  • UI 样式
  • 升级规则
  • 图标、EULA、快捷方式等

5. Product 和 Package

典型结构:

<Product
    Id='*'
    Name='eskin-model-player'
    UpgradeCode='7CEF316B-BE23-4533-B4B2-87D06D2230B5'
    Manufacturer='JOYSONQUIN'
    Language='1033'
    Codepage='1252'
    Version='$(var.Version)'>

    <Package Id='*'
        Keywords='Installer'
        Description='Desktop pressure sensor visualization and playback application.'
        Manufacturer='JOYSONQUIN'
        InstallerVersion='450'
        Languages='1033'
        Compressed='yes'
        InstallScope='perUser'
        InstallPrivileges='limited'
        SummaryCodepage='1252'/>
</Product>

关键字段:

  • Product/@Id='*':每次构建生成新的 ProductCode。
  • UpgradeCode:同一个产品线必须保持稳定,不要随意修改。
  • Version='$(var.Version)':使用 Cargo.toml 的版本。
  • InstallScope:安装范围,常见值是 perUserperMachine
  • InstallPrivileges:权限要求,limited 表示不要求管理员权限,elevated 表示需要提升权限。

6. per-user 和 per-machine 的区别

当前用户安装perUser

适合:

  • 不希望安装时弹管理员权限
  • 应用只给当前用户使用
  • 安装到用户目录,例如 %LOCALAPPDATA%
  • 写用户 PATH 或 HKCU 注册表

推荐配置:

<Package
    ...
    InstallScope='perUser'
    InstallPrivileges='limited'/>

目录使用:

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>

环境变量使用用户级 PATH

<Environment
    Id='PATH'
    Name='PATH'
    Value='[Bin]'
    Permanent='no'
    Part='last'
    Action='set'
    System='no'/>

注意:

  • 安装到用户目录的组件需要使用 HKCU 注册表项作为 KeyPath
  • 用户目录下的安装目录建议添加 RemoveFolder,否则 ICE64 会报错。

所有用户安装perMachine

适合:

  • 应用需要给所有用户使用
  • 安装到 Program Files
  • 写系统 PATH
  • 写 HKLM 注册表
  • 公司 IT 管理员部署

推荐配置:

<Package
    ...
    InstallScope='perMachine'
    InstallPrivileges='elevated'/>

目录使用:

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFilesFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>

64 位安装包通常使用:

<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
    <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
    <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>

然后:

<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>

系统 PATH

<Environment
    Id='PATH'
    Name='PATH'
    Value='[Bin]'
    Permanent='no'
    Part='last'
    Action='set'
    System='yes'/>

注意:

  • perMachine 通常需要管理员权限。
  • 如果普通用户安装,会出现权限不足提示。
  • Program Files 和系统 PATH 都需要提升权限。

7. 当前项目的 per-user 配置要点

当前项目已经改为当前用户安装:

InstallScope='perUser'
InstallPrivileges='limited'

安装目录:

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            <Directory Id='Bin' Name='bin'>
                ...
            </Directory>
        </Directory>
    </Directory>
</Directory>

PATH 组件使用 HKCU registry key 作为 KeyPath

<Component Id='Path' Guid='FD230ABD-75B9-47D4-870B-CF2EA72B76BE'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='PathComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <RemoveFolder Id='RemoveBinFolder' Directory='Bin' On='uninstall'/>
    <RemoveFolder Id='RemoveApplicationFolder' Directory='APPLICATIONFOLDER' On='uninstall'/>
    <Environment
        Id='PATH'
        Name='PATH'
        Value='[Bin]'
        Permanent='no'
        Part='last'
        Action='set'
        System='no'/>
</Component>

程序文件组件也使用 HKCU registry key 作为 KeyPath

<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='BinaryComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <File
        Id='exe0'
        Name='ESkinPlayer.exe'
        DiskId='1'
        Source='$(var.CargoTargetBinDir)\ESkinPlayer.exe'/>
</Component>

8. Component、File、KeyPath 的基本规则

WiX 的核心概念:

  • Directory:安装目录。
  • Component:安装和卸载的最小管理单元。
  • File:要安装的文件。
  • RegistryValue:要写入的注册表项。
  • KeyPathWindows Installer 用来判断组件是否已安装的关键路径。

常见规则:

  • 一个组件通常只放一个主要文件。
  • 组件 GUID 一旦发布后不要随意修改。
  • perUser 安装到用户目录时,建议用 HKCU registry key 作为 KeyPath
  • perMachine 安装到 Program Files 时,文件通常可以作为 KeyPath
  • 如果组件同时包含文件和注册表 KeyPathWiX v3 通常不能用 Guid='*' 自动生成 GUID需要写固定 GUID。

生成 GUID

[guid]::NewGuid().ToString().ToUpper()

示例:

<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>
    ...
</Component>

9. 添加文件

假设要安装一个 README.txt 到应用目录。

目录结构:

wix\
  main.wxs
  README.txt

添加组件:

<Component Id='ReadmeFile' Guid='PUT-GUID-HERE'>
    <RegistryValue
        Root='HKCU'
        Key='Software\JOYSONQUIN\eskin-model-player'
        Name='ReadmeComponent'
        Type='integer'
        Value='1'
        KeyPath='yes'/>
    <File
        Id='ReadmeTxt'
        Name='README.txt'
        DiskId='1'
        Source='wix\README.txt'/>
</Component>

然后在 Feature 里引用:

<ComponentRef Id='ReadmeFile'/>

如果是 perMachine 安装,也可以简化为:

<Component Id='ReadmeFile' Guid='PUT-GUID-HERE'>
    <File
        Id='ReadmeTxt'
        Name='README.txt'
        DiskId='1'
        Source='wix\README.txt'
        KeyPath='yes'/>
</Component>

10. 添加资源目录

如果程序运行时需要模型、图片、配置文件等资源,建议明确安装到应用目录下。

示例:

<Directory Id='AssetsFolder' Name='assets'>
    <Component Id='AssetConfig' Guid='PUT-GUID-HERE'>
        <RegistryValue
            Root='HKCU'
            Key='Software\JOYSONQUIN\eskin-model-player'
            Name='AssetConfigComponent'
            Type='integer'
            Value='1'
            KeyPath='yes'/>
        <File
            Id='AssetConfigJson'
            Name='config.json'
            DiskId='1'
            Source='assets\config.json'/>
    </Component>
</Directory>

引用:

<ComponentRef Id='AssetConfig'/>

如果资源很多,不建议长期手写大量 <File>。可以考虑:

  • 用 WiX Heat 自动收集目录
  • 在构建脚本里生成 .wxs 片段
  • 只把稳定的少量资源手写到 main.wxs

11. 修改安装目录

改为当前用户目录

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='LocalAppDataFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>

实际路径通常类似:

C:\Users\<user>\AppData\Local\eskin-model-player

改为 Program Files

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFilesFolder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>

64 位项目推荐:

<Directory Id='TARGETDIR' Name='SourceDir'>
    <Directory Id='ProgramFiles64Folder'>
        <Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>
            ...
        </Directory>
    </Directory>
</Directory>

12. 是否添加 PATH

当前项目有一个可选 Feature把安装目录下的 bin 加入 PATH。

Feature

<Feature
    Id='Environment'
    Title='PATH Environment Variable'
    Description='Add the install location of the [ProductName] executable to the PATH system environment variable.'
    Level='1'
    Absent='allow'>
    <ComponentRef Id='Path'/>
</Feature>

如果不希望修改 PATH可以删除或注释

<ComponentRef Id='Path'/>

也可以把整个 Environment Feature 注释掉。

用户级 PATH

System='no'

系统级 PATH

System='yes'

注意:

  • 用户级 PATH 不需要管理员权限。
  • 系统级 PATH 需要管理员权限。
  • 安装后新开的终端才能看到新的 PATH。
  • 已打开的 PowerShell/CMD 通常不会自动刷新环境变量。

13. 添加开始菜单快捷方式

常见做法是在 ProgramMenuFolder 下创建快捷方式。

目录:

<Directory Id='ProgramMenuFolder'>
    <Directory Id='ApplicationProgramsFolder' Name='eskin-model-player'/>
</Directory>

在可执行文件组件里添加:

<Shortcut
    Id='ApplicationStartMenuShortcut'
    Directory='ApplicationProgramsFolder'
    Name='ESkinPlayer'
    WorkingDirectory='Bin'
    Advertise='no'
    Target='[Bin]ESkinPlayer.exe'/>

<RemoveFolder Id='RemoveApplicationProgramsFolder' Directory='ApplicationProgramsFolder' On='uninstall'/>

如果是 per-user 安装,快捷方式组件也应该使用 HKCU registry key 作为 KeyPath

14. 添加桌面快捷方式

目录:

<Directory Id='DesktopFolder' Name='Desktop'/>

快捷方式:

<Shortcut
    Id='ApplicationDesktopShortcut'
    Directory='DesktopFolder'
    Name='ESkinPlayer'
    WorkingDirectory='Bin'
    Advertise='no'
    Target='[Bin]ESkinPlayer.exe'/>

桌面快捷方式不一定适合默认创建。更推荐放到可选 Feature或者只创建开始菜单快捷方式。

15. 修改控制面板图标

准备 .ico 文件,例如:

wix\Product.ico

启用:

<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>
<Property Id='ARPPRODUCTICON' Value='ProductICO'/>

注意:

  • .ico 最好包含多个尺寸,例如 16、32、48、256。
  • 路径相对执行打包时的项目根目录。

16. 添加 EULA

如果需要安装时显示许可协议:

  1. 准备 RTF 文件,例如 wix\Eula.rtf
  2. 修改 Cargo.toml
[package.metadata.wix]
eula = true
  1. wix/main.wxs 中启用:
<WixVariable Id='WixUILicenseRtf' Value='wix\Eula.rtf'/>

如果当前使用 WixUI_FeatureTree 并跳过 EULA需要检查 <UI> 里的 Publish 逻辑,避免安装流程跳转冲突。

17. 修改安装 UI

当前项目使用:

<UI>
    <UIRef Id='WixUI_FeatureTree'/>
    ...
</UI>

常见 UI

  • WixUI_Minimal:最简单。
  • WixUI_InstallDir:允许选择安装目录。
  • WixUI_FeatureTree:允许选择 Feature。
  • WixUI_Advanced:更完整的高级安装流程。

如果要允许用户改安装路径,常用:

<UIRef Id='WixUI_InstallDir'/>
<Property Id='WIXUI_INSTALLDIR' Value='APPLICATIONFOLDER'/>

18. 升级策略

当前项目使用:

<MajorUpgrade
    Schedule='afterInstallInitialize'
    DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>

含义:

  • 安装新版本时自动卸载旧版本。
  • 不允许安装更旧版本覆盖新版本。
  • UpgradeCode 必须保持不变。
  • Version 必须递增。

Windows Installer 版本规则:

  • 通常只比较前三段版本号:Major.Minor.Build
  • 5.0.05.0.1 是升级
  • 只重打同一个 5.0.0 可能不会触发正常升级

发布新版时建议:

version = "5.0.1"

19. 签名 MSI

未签名 MSI 在 Windows 上可能触发安全提示。正式发布建议签名。

cargo-wix 支持 sign 子命令,也可以在生成 MSI 后用 signtool.exe

示例:

signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a target\wix\eskin-model-player-5.0.0-x86_64.msi

实际签名命令取决于证书来源:

  • 本机证书
  • PFX 文件
  • 硬件 token
  • 公司代码签名服务

20. 常见错误与处理

权限不足

错误示例:

You do not have sufficient privileges to complete this installation for all users of the machine.

常见原因:

  • InstallScope='perMachine'
  • 安装到 ProgramFilesFolder
  • 写系统 PATHSystem='yes'
  • 写 HKLM 注册表

解决:

  • 以管理员身份运行 MSI
  • 或改成 perUser
  • 或安装到 LocalAppDataFolder
  • 或把 PATH 改为 System='no'

ICE38

错误示例:

ICE38: Component ... installs to user profile. It must use a registry key under HKCU as its KeyPath, not a file.

原因:

  • 组件安装到用户目录
  • 但组件使用文件作为 KeyPath

解决:

<RegistryValue
    Root='HKCU'
    Key='Software\JOYSONQUIN\eskin-model-player'
    Name='BinaryComponent'
    Type='integer'
    Value='1'
    KeyPath='yes'/>

并移除 File 上的:

KeyPath='yes'

ICE64

错误示例:

ICE64: The directory Bin is in the user profile but is not listed in the RemoveFile table.

原因:

  • 用户目录下创建了目录
  • 卸载时没有明确清理

解决:

<RemoveFolder Id='RemoveBinFolder' Directory='Bin' On='uninstall'/>
<RemoveFolder Id='RemoveApplicationFolder' Directory='APPLICATIONFOLDER' On='uninstall'/>

CNDL0230

错误示例:

The Component/@Guid attribute's value '*' is not valid for this component because it does not meet the criteria for having an automatically generated guid.

原因:

  • 组件里有文件
  • 但 KeyPath 是 registry value
  • WiX v3 不能自动生成这种组件 GUID

解决:

生成固定 GUID

[guid]::NewGuid().ToString().ToUpper()

然后写入:

<Component Id='binary0' Guid='302CAFB5-6951-426B-BC5A-988C351A2CF2'>

ICE91

警告示例:

ICE91: The file 'exe0' will be installed to the per user directory ...

原因:

  • 文件安装到用户目录
  • ICE 提醒如果该包被当作 per-machine 安装,文件不会复制到每个用户的 profile

处理:

  • 如果安装包明确是 perUser,通常可以忽略。
  • 如果目标是 perMachine,应改用 ProgramFilesFolder,不要安装到用户 profile。

新 MSI 没有覆盖旧 MSI

常见原因:

  • Cargo.toml 版本号没变
  • UpgradeCode 改了
  • 旧版本是 per-machine新版本是 per-user

解决:

  • 发布时递增版本号
  • 保持 UpgradeCode 不变
  • 如果旧版本是 per-machine先用管理员权限卸载旧版本

21. 调试 MSI 安装

生成详细安装日志:

msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v install.log

静默安装:

msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /qn /l*v install.log

卸载:

msiexec /x target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v uninstall.log

如果安装失败,优先查看日志里:

Return value 3

它附近通常是真正失败原因。

22. 推荐发布流程

  1. 更新 Cargo.toml 版本号。
  2. 确认 wix/main.wxs 的安装范围符合发布目标。
  3. 执行 release 构建和 MSI 打包。
  4. 在干净机器或干净用户环境中测试安装。
  5. 测试启动程序。
  6. 测试卸载后文件、快捷方式、PATH 是否清理干净。
  7. 正式发布前对 MSI 签名。

命令:

cd D:\eskin-player
cargo wix clean
cargo wix --bin-path "C:\Program Files (x86)\WiX Toolset v3.14\bin" --nocapture

验证:

Get-ChildItem target\wix\*.msi
msiexec /i target\wix\eskin-model-player-5.0.0-x86_64.msi /l*v install.log

23. 本项目常用修改清单

改包名

修改 Product/@Name

Name='eskin-model-player'

以及 Cargo.toml

name = "eskin-model-player"

改显示厂商

修改:

Manufacturer='JOYSONQUIN'

以及:

authors = ["JOYSONQUIN"]

改安装目录名

修改:

<Directory Id='APPLICATIONFOLDER' Name='eskin-model-player'>

改可执行文件名

修改 Cargo.toml

[[bin]]
name = "ESkinPlayer"

同步修改 WiX

<File
    Id='exe0'
    Name='ESkinPlayer.exe'
    Source='$(var.CargoTargetBinDir)\ESkinPlayer.exe'/>

关闭 PATH 写入

删除或注释:

<ComponentRef Id='Path'/>

也可以删除整个 Environment Feature。

从 per-user 改回 per-machine

修改:

InstallScope='perMachine'
InstallPrivileges='elevated'

安装目录改为:

<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>

PATH 改为:

System='yes'

注意:

  • 这会重新要求管理员权限。
  • 如果已经发布过 per-user 版本,切换安装范围前需要测试升级和卸载路径。

24. 维护注意事项

  • UpgradeCode 是产品线标识,正常发布不要改。
  • 发布新版本时递增 Cargo.tomlversion
  • 已发布组件的 Guid 不要随意变更。
  • 用户目录安装使用 HKCU registry key 作为 KeyPath
  • 系统目录安装通常需要管理员权限。
  • 修改 PATH 后要测试安装和卸载。
  • 加文件后必须在 Feature 中添加 ComponentRef,否则不会被安装。
  • WiX 报错时优先看 candle 还是 light 阶段:
    • candleXML 结构、语法、GUID、路径等编译错误。
    • light链接、ICE 校验、MSI 表规则问题。