Skip to content

Commit 114c914

Browse files
committed
Auto merge of #1234 - Boddlnagg:more-msi, r=alexcrichton
More work on MSI installer Actually implements a large part of the MSI logic (previously this was mostly stubs). With these changes, the MSI installer will install rustup to the right location, so running the installer without setting `CARGO_HOME` will overwrite the existing installation! A test script is included that installs rustup into a different location by setting `CARGO_HOME` (and `RUSTUP_HOME`). This PR also enables building/testing the MSI version on AppVeyor CI (but the tests for install and self update are disabled, because they are not compatible with the MSI version, and no MSI specific tests have been added yet).
2 parents 07fd5bf + 1a55ea0 commit 114c914

File tree

4 files changed

+121
-44
lines changed

4 files changed

+121
-44
lines changed

src/rustup-win-installer/msi/rustup.wxs

+67-17
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88
<Product Id="*" Name="rustup" Language="1033" Version="$(env.CFG_VER_MAJOR).$(env.CFG_VER_MINOR).$(env.CFG_VER_PATCH).0" Manufacturer="The Rust Project Developers" UpgradeCode="09acbb1c-7123-44ac-b2a9-4a04b28ced11">
99
<Package InstallerVersion="200" Compressed="yes" InstallScope="perUser" />
1010

11+
<Condition Message="The Rust toolchain is only supported on Windows 7, Windows Server 2008 R2, or higher.">
12+
<![CDATA[Installed OR (VersionNT >= 601)]]>
13+
</Condition>
14+
1115
<!-- TODO: How to configure updates? `AllowDowngrades` automatically removes previously installed versions, no matter what version they have -->
1216
<MajorUpgrade AllowDowngrades="yes" />
1317

1418
<!-- Specifies a single cab file to be embedded in the installer's .msi. -->
1519
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
1620

17-
<Feature Id="ProductFeature" Title="rustup" Level="1">
18-
<ComponentRef Id="CompleteInstallation" />
21+
<Feature Id="Rustup" Title="rustup" Absent="disallow" Display="1" AllowAdvertise="no">
22+
<ComponentRef Id="RustupExe" />
23+
<ComponentRef Id="RegisterProductCode" />
24+
<ComponentRef Id="RegisterInstallDir" />
25+
</Feature>
26+
<!-- TODO: Add UI to actually let the user disable this Feature -->
27+
<Feature Id="Path" Title="Add to PATH" Absent="allow" Description="Add Rust to PATH environment variable" Display="2" AllowAdvertise="no">
28+
<ComponentRef Id="ModifyPathEnv" />
1929
</Feature>
2030

2131
<!-- Set some metadata that will appear in the "Installed Programs" list -->
@@ -35,8 +45,6 @@
3545
<UIRef Id="CustomUI" />
3646
<WixVariable Id="WixUIDialogBmp" Value="dialogbg.bmp" />
3747
<WixVariable Id="WixUIBannerBmp" Value="banner.bmp" />
38-
<!-- TODO: Include/generate license file -->
39-
<!--<WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />-->
4048

4149
<Directory Id="TARGETDIR" Name="SourceDir">
4250
<!-- `INSTALLLOCATION` will be set by custom action -->
@@ -46,36 +54,78 @@
4654
</Directory>
4755

4856
<DirectoryRef Id="INSTALLLOCATION_BINARY">
49-
<Component Id="CompleteInstallation" Guid="df2ab9f7-7888-465c-98dd-bb58cbca68f7">
57+
<Component Id="RustupExe" Guid="df2ab9f7-7888-465c-98dd-bb58cbca68f7">
58+
<!-- Install the main rustup.exe binary -->
59+
<File Source="$(var.RustupExe)" Name="rustup.exe" KeyPath="yes"/>
60+
</Component>
61+
<Component Id="RegisterInstallDir" Guid="*">
62+
<!-- Remember the installation directory in the registry -->
63+
<RegistryKey Root="HKCU" Key="Software\rustup">
64+
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLLOCATION]" KeyPath="yes"/>
65+
</RegistryKey>
66+
</Component>
67+
<Component Id="RegisterProductCode" Guid="*">
5068
<!-- Write the product code to the registry, so we can use it to run the uninstaller -->
5169
<RegistryKey Root="HKCU" Key="Software\rustup">
5270
<RegistryValue Name="InstalledProductCode" Type="string" Value="[ProductCode]" KeyPath="yes" />
5371
</RegistryKey>
54-
<!-- Install the main rustup.exe binary -->
55-
<File Source="$(var.RustupExe)" Name="rustup.exe"/>
56-
<!-- Append to PATH environment variable -->
57-
<Environment Id="PATH" Name="PATH" Value="[INSTALLLOCATION_BINARY]" Permanent="no" Part="first" Action="set" System="no" />
72+
</Component>
73+
<Component Id="ModifyPathEnv" Guid="*">
74+
<!-- Prepend to PATH environment variable -->
75+
<RegistryValue Root="HKCU" Key="Software\rustup" Name="PathEnv" Type="string" Value="1" KeyPath="yes" />
76+
<Environment Id="PathEnv" Name="PATH" Value="[INSTALLLOCATION_BINARY]" Permanent="no" Part="first" Action="set" System="no" />
5877
</Component>
5978
</DirectoryRef>
6079

80+
<!-- Read installation path from the registry. This is required for uninstall and upgrade
81+
and should only find something if rustup has already been installed via MSI. -->
82+
<Property Id="INSTALLLOCATION_REGISTRY">
83+
<RegistrySearch Id="SearchRegistryInstallLocation" Root="HKCU" Key="Software\rustup" Name="InstallDir" Type="raw" />
84+
</Property>
85+
86+
<!-- Read environment variables `CARGO_HOME` and `RUSTUP_HOME`, because they won't be visible in custom actions later -->
87+
<SetProperty Action="ReadCargoHome" Id="CARGO_HOME" Value="[%CARGO_HOME]" Before="AppSearch" Sequence="first" />
88+
<SetProperty Action="SetCargoHomeOverride" Id="CARGO_HOME_OR_DEFAULT" Value="[CARGO_HOME]" After="ReadCargoHome" Sequence="first">CARGO_HOME</SetProperty>
89+
<SetProperty Action="SetCargoHomeDefault" Id="CARGO_HOME_OR_DEFAULT" Value="[%USERPROFILE]\.cargo" After="ReadCargoHome" Sequence="first">NOT CARGO_HOME</SetProperty>
90+
91+
<SetProperty Action="ReadRustupHome" Id="RUSTUP_HOME" Value="[%RUSTUP_HOME]" Before="AppSearch" Sequence="first" />
92+
<SetProperty Action="SetRustupHomeOverride" Id="RUSTUP_HOME_OR_DEFAULT" Value="[RUSTUP_HOME]" After="ReadRustupHome" Sequence="first">RUSTUP_HOME</SetProperty>
93+
<SetProperty Action="SetRustupHomeDefault" Id="RUSTUP_HOME_OR_DEFAULT" Value="[%USERPROFILE]\.rustup" After="ReadRustupHome" Sequence="first">NOT RUSTUP_HOME</SetProperty>
94+
95+
<!-- Looks for `rustup.exe` in `%USERPROFILE%\.cargo\bin` or `%CARGO_HOME%\bin` respectively -->
96+
<Property Id="RUSTUP_EXE_EXISTS">
97+
<DirectorySearch Id="ExistingRustupExeSearch" Path="[CARGO_HOME_OR_DEFAULT]\bin" Depth="0">
98+
<FileSearch Name="rustup.exe" />
99+
</DirectorySearch>
100+
</Property>
101+
102+
<!-- Looks for `%USERPROFILE%\.rustup` or `%RUSTUP_HOME%` respectively -->
103+
<Property Id="RUSTUP_DIRECTORY_EXISTS">
104+
<DirectorySearch Id="ExistingRustupDirSearch" Path="[RUSTUP_HOME_OR_DEFAULT]" Depth="0" AssignToProperty="yes" />
105+
</Property>
106+
107+
<!-- This property will be set whenever a previous installation of rustup was found, even if it was not installed via MSI -->
108+
<SetProperty Id="RUSTUP_EXISTS" Value="1" After="AppSearch" Sequence="first">RUSTUP_EXE_EXISTS AND RUSTUP_DIRECTORY_EXISTS</SetProperty>
109+
110+
<!-- Propagate correct value to INSTALLLOCATION directory -->
111+
<SetDirectory Action="AssignInstallLocationExisting" Id="INSTALLLOCATION" Value="[INSTALLLOCATION_REGISTRY]" Sequence="first">INSTALLLOCATION_REGISTRY</SetDirectory>
112+
<SetDirectory Action="AssignInstallLocationNew" Id="INSTALLLOCATION" Value="[CARGO_HOME_OR_DEFAULT]" Sequence="first">NOT INSTALLLOCATION_REGISTRY</SetDirectory>
113+
61114
<!-- Register the DLL containing the custom actions as an embedded binary -->
62115
<Binary Id="RustupCustomActionDll" SourceFile="$(var.RustupCustomActionDll)"/>
63116
<!-- Use a type 51 custom action to send options to deferred custom action `RustupInstall`
64-
(can use arbitrary value that encodes all necessary properties and will be parsed from Rust) -->
65-
<CustomAction Id="SetInstallOptions" Property="RustupInstall" Value="... we can pass arbitrary options here ..." />
66-
<CustomAction Id="RustupSetInstallLocation" BinaryKey="RustupCustomActionDll" DllEntry="RustupSetInstallLocation" Execute="immediate" Return="check" Impersonate="yes"/>
67-
<!-- Propagate the value of `RustupInstallLocation` (set by custom action) to `INSTALLLOCATION` -->
68-
<CustomAction Id="AssignInstallLocation" Directory="INSTALLLOCATION" Value="[RustupInstallLocation]"/>
117+
(can use arbitrary string that encodes all necessary properties and will be parsed from Rust) -->
118+
<CustomAction Id="SetInstallOptions" Property="RustupInstall" Value="INSTALLLOCATION=[INSTALLLOCATION];RUSTUP_HOME=[RUSTUP_HOME_OR_DEFAULT];RUSTUP_EXISTS=[RUSTUP_EXISTS]" />
119+
<CustomAction Id="SetUninstallOptions" Property="RustupUninstall" Value="INSTALLLOCATION=[INSTALLLOCATION];RUSTUP_HOME=[RUSTUP_HOME_OR_DEFAULT];RUSTUP_EXISTS=[RUSTUP_EXISTS]" />
69120
<CustomAction Id="RustupInstall" BinaryKey="RustupCustomActionDll" DllEntry="RustupInstall" Execute="deferred" Return="check" Impersonate="yes"/>
70121
<CustomAction Id="RustupUninstall" BinaryKey="RustupCustomActionDll" DllEntry="RustupUninstall" Execute="deferred" Return="check" Impersonate="yes"/>
71122

123+
<!-- Schedule our custom actions -->
72124
<InstallExecuteSequence>
73-
<DisableRollback Before="InstallInitialize"/>
74-
<Custom Action="RustupSetInstallLocation" After="CostFinalize"/>
75-
<Custom Action="AssignInstallLocation" After="RustupSetInstallLocation"/>
76125
<Custom Action="SetInstallOptions" Before="InstallInitialize">NOT Installed</Custom>
77126
<Custom Action="RustupInstall" After="InstallFiles">NOT Installed</Custom>
78127
<!-- Run RustupUninstall only on true uninstall, not on upgrade -->
128+
<Custom Action="SetUninstallOptions" Before="InstallInitialize">Installed AND (NOT UPGRADINGPRODUCTCODE)</Custom>
79129
<Custom Action="RustupUninstall" After="RemoveFiles">Installed AND (NOT UPGRADINGPRODUCTCODE)</Custom>
80130
</InstallExecuteSequence>
81131

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This script can be used for manually testing the MSI installer. It is not used for AppVeyor CI.
2+
3+
$env:RUSTFLAGS="-Zunstable-options -Ctarget-feature=+crt-static"
4+
5+
pushd ..\..\..
6+
# Build rustup.exe
7+
cargo build --release --target i686-pc-windows-msvc --features msi-installed
8+
popd
9+
if($LastExitCode -ne 0) { exit $LastExitCode }
10+
pushd ..
11+
# Build the CA library
12+
cargo build --release --target i686-pc-windows-msvc
13+
popd
14+
if($LastExitCode -ne 0) { exit $LastExitCode }
15+
# Build the MSI
16+
.\build.ps1 -Target i686-pc-windows-msvc
17+
if($LastExitCode -ne 0) { exit $LastExitCode }
18+
# Run the MSI with logging
19+
$OLD_CARGO_HOME = $env:CARGO_HOME
20+
$OLD_RUSTUP_HOME = $env:RUSTUP_HOME
21+
$env:CARGO_HOME = "$env:USERPROFILE\.cargo-test"
22+
$env:RUSTUP_HOME = "$env:USERPROFILE\.rustup-test"
23+
Start-Process msiexec -ArgumentList "/i target\rustup.msi /L*V target\Install.log" -Wait
24+
$env:CARGO_HOME = $OLD_CARGO_HOME
25+
$env:RUSTUP_HOME = $OLD_RUSTUP_HOME
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Uninstall currently installed version of rustup. Does the same thing as `rustup self uninstall`.
2+
3+
$key = 'HKCU:\SOFTWARE\rustup'
4+
$productCode = (Get-ItemProperty -Path $key -Name InstalledProductCode).InstalledProductCode
5+
6+
# No need to set CARGO_HOME, because the installation directory is stored in the registry
7+
$OLD_RUSTUP_HOME = $env:RUSTUP_HOME
8+
$env:RUSTUP_HOME = "$env:USERPROFILE\.rustup-test"
9+
msiexec /x "$productCode" /L*V "target\Uninstall.log"
10+
$env:RUSTUP_HOME = $OLD_RUSTUP_HOME

src/rustup-win-installer/src/lib.rs

+19-27
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extern crate rustup;
55

66
use std::ffi::CString;
77
use std::path::PathBuf;
8+
use std::collections::HashMap;
89
use ::winapi::{HRESULT, PCSTR, UINT, LPCWSTR, LPWSTR, LPVOID};
910

1011
pub type MSIHANDLE = u32;
@@ -17,31 +18,20 @@ pub const LOGMSG_STANDARD: i32 = 2;
1718
static TOOLS: &'static [&'static str]
1819
= &["rustc", "rustdoc", "cargo", "rust-lldb", "rust-gdb", "rls"];
1920

20-
#[no_mangle]
21-
/// This is run as an `immediate` action early in the install sequence
22-
pub unsafe extern "system" fn RustupSetInstallLocation(hInstall: MSIHANDLE) -> UINT {
23-
// TODO: error handling (get rid of unwrap)
24-
let name = CString::new("RustupSetInstallLocation").unwrap();
25-
let hr = WcaInitialize(hInstall, name.as_ptr());
26-
//let path = ::rustup::utils::cargo_home().unwrap();
27-
let path = PathBuf::from(::std::env::var_os("USERPROFILE").unwrap()).join(".rustup-test");
28-
set_property("RustupInstallLocation", path.to_str().unwrap());
29-
WcaFinalize(hr)
30-
}
31-
3221
#[no_mangle]
3322
/// This is be run as a `deferred` action after `InstallFiles` on install and upgrade
3423
pub unsafe extern "system" fn RustupInstall(hInstall: MSIHANDLE) -> UINT {
3524
let name = CString::new("RustupInstall").unwrap();
3625
let hr = WcaInitialize(hInstall, name.as_ptr());
3726
// For deferred custom actions, all data must be passed through the `CustomActionData` property
3827
let custom_action_data = get_property("CustomActionData");
39-
// TODO: use rustup_utils::cargo_home() or pass through CustomActionData
40-
let path = PathBuf::from(::std::env::var_os("USERPROFILE").unwrap()).join(".rustup-test");
28+
let parsed_ca_data = parse_custom_action_data(&custom_action_data);
29+
let path = PathBuf::from(parsed_ca_data.get("INSTALLLOCATION").unwrap());
4130
let bin_path = path.join("bin");
4231
let rustup_path = bin_path.join("rustup.exe");
4332
let exe_installed = rustup_path.exists();
4433
log(&format!("Hello World from RustupInstall, confirming that rustup.exe has been installed: {}! CustomActionData: {}", exe_installed, custom_action_data));
34+
log(&format!("Parsed CA data: {:?}", parsed_ca_data));
4535
for tool in TOOLS {
4636
let ref tool_path = bin_path.join(&format!("{}.exe", tool));
4737
::rustup::utils::hardlink_file(&rustup_path, tool_path);
@@ -57,15 +47,26 @@ pub unsafe extern "system" fn RustupUninstall(hInstall: MSIHANDLE) -> UINT {
5747
let hr = WcaInitialize(hInstall, name.as_ptr());
5848
// For deferred custom actions, all data must be passed through the `CustomActionData` property
5949
let custom_action_data = get_property("CustomActionData");
60-
// TODO: use rustup_utils::cargo_home() or pass through CustomActionData
61-
let path = PathBuf::from(::std::env::var_os("USERPROFILE").unwrap()).join(".rustup-test");
50+
let parsed_ca_data = parse_custom_action_data(&custom_action_data);
51+
let path = PathBuf::from(parsed_ca_data.get("INSTALLLOCATION").unwrap());
6252
let exe_deleted = !path.join("bin").join("rustup.exe").exists();
6353
log(&format!("Hello World from RustupUninstall, confirming that rustup.exe has been deleted: {}! CustomActionData: {}", exe_deleted, custom_action_data));
64-
// TODO: Remove .cargo and .rustup
65-
::rustup::utils::remove_dir("rustup-test", &path, &|_| {});
54+
log(&format!("Parsed CA data: {:?}", parsed_ca_data));
55+
::rustup::utils::remove_dir("cargo_home", &path, &|_| {});
56+
// TODO: also remove RUSTUP_HOME
57+
//::rustup::utils::remove_dir("rustup_home", &rustup_home, &|_| {});
6658
WcaFinalize(hr)
6759
}
6860

61+
fn parse_custom_action_data(ca_data: &str) -> HashMap<&str, &str> {
62+
let mut map = HashMap::new();
63+
for v in ca_data.split(";") {
64+
let idx = v.find('=').unwrap();
65+
map.insert(&v[..idx], &v[(idx + 1)..]);
66+
}
67+
map
68+
}
69+
6970
// wrapper for WcaGetProperty (TODO: error handling)
7071
fn get_property(name: &str) -> String {
7172
let encoded_name = to_wide_chars(name);
@@ -76,14 +77,6 @@ fn get_property(name: &str) -> String {
7677
result
7778
}
7879

79-
// wrapper for WcaSetProperty
80-
fn set_property(name: &str, value: &str) -> HRESULT {
81-
let encoded_name = to_wide_chars(name);
82-
let encoded_value = to_wide_chars(value);
83-
unsafe { WcaSetProperty(encoded_name.as_ptr(), encoded_value.as_ptr()) }
84-
}
85-
86-
8780
fn log(message: &str) {
8881
let msg = CString::new(message).unwrap();
8982
unsafe { WcaLog(LOGMSG_STANDARD, msg.as_ptr()) }
@@ -109,7 +102,6 @@ extern "system" {
109102
fn WcaInitialize(hInstall: MSIHANDLE, szCustomActionLogName: PCSTR) -> HRESULT;
110103
fn WcaFinalize(iReturnValue: HRESULT) -> UINT;
111104
fn WcaGetProperty(wzProperty: LPCWSTR, ppwzData: *mut LPWSTR) -> HRESULT; // see documentation for MsiGetProperty
112-
fn WcaSetProperty(wzPropertyName: LPCWSTR, wzPropertyValue: LPCWSTR) -> HRESULT;
113105
fn StrFree(p: LPVOID) -> HRESULT;
114106
}
115107

0 commit comments

Comments
 (0)