rustup icon indicating copy to clipboard operation
rustup copied to clipboard

Add windows gui / msi installer [difficult]

Open brson opened this issue 9 years ago • 61 comments

To complete the Rust installation experience on Windows we want to be installing rustup via an msi.

Make a proof-of-concept rustup msi installer that embeds libmultirust. The behavior of this msi will be heavily customized - all it does is the standard rustup install, but presented in a windowsy way. To start with it can be really simple:

  • On installation, present a screen that says more-or-less what the console installer says now. Have install/cancel buttons.
  • After they press install, go to another window that displays whatever status updates we reasonably can as the install is progressing.
  • After install show another screen that indicates success.
  • Register the uninstaller with windows so it works from the add/remove programs screen.

This will require a lot of refactoring of the existing install code to get it embedded in this new context.

Our options for GUI's will be limited but we can't use something heavy. I'm thinking either something rust-centric like conrod, or just a very thin win32 wrapper.

brson avatar Apr 02 '16 00:04 brson

Depending on OpenGL for Conrod might not work very well if they don't have the GPU manufacturer drivers that provide OpenGL, or if they're using Windows in a VM.

retep998 avatar Apr 02 '16 03:04 retep998

We could just use Inno Setup... It will allow us to produce a GUI installer which can call out to the command line rustup behind the scenes, and it's configured declaratively, so we won't even need any non-rust code.

Diggsey avatar Apr 02 '16 13:04 Diggsey

@Diggsey Inno Setup does not produce msi's (I believe), and msi's are the delivery format people most expect and prefer.

Rust itself uses Inno to produce .exe installers but we don't advertise them because the .msi's are nicer.

brson avatar Apr 04 '16 21:04 brson

If we use the Windows Installer stuff then that can provide a user interface for us.

retep998 avatar Apr 04 '16 21:04 retep998

@brson

and msi's are the delivery format people most expect and prefer.

.msi is a really big pain due to windows installer cache bloat :(

pravic avatar Apr 05 '16 07:04 pravic

If it's just a window with some text and [Install]/[Cancel] buttons, the built-in msi UI will do just fine. Anything more complicated/dynamic than, say, the Rust installer, would be quite annoying to do in it, and in that case I would suggest going with WinForms/WPF.

I also want to note that invoking an .exe to perform the actual system changes (like mucking with the registry to modify PATH) goes against the "best MSI practices", but I guess we'd be fine with it for cross-platform consistency's sake?

vadimcn avatar Apr 06 '16 18:04 vadimcn

@vadimcn Modifying the PATH would have to be done in a custom action anyway, even if .msi is used, so there's no real benefit in that regard. Aside from that, rustup doesn't actually make any changes to your system (even rustup itself goes in your user folder)

Diggsey avatar Apr 06 '16 19:04 Diggsey

Modifying the PATH would have to be done in a custom action anyway, even if .msi is used,

Why? MSI has direct support for modifying environment vars. But even when CAs are used, the recommended way is not to perform system changes directly, but rather to schedule them for execution by the MSI engine in elevated part of the install process (by adding ephemeral records to File/Registry/etc tables).

vadimcn avatar Apr 06 '16 19:04 vadimcn

@vadimcn I don't want to invoke an exe.

What I want to do is have the msi system call functions in the multirust dll to perform the installation actions, while presenting the UI people expect from an msi installer. Can we do that?

brson avatar Apr 07 '16 17:04 brson

What I want to do is have the msi system call functions in the multirust dll to perform the installation actions, while presenting the UI people expect from an msi installer. Can we do that?

Yes, that is possible. (This still counts as performing changes directly, though)

vadimcn avatar Apr 07 '16 19:04 vadimcn

In any cases, please keep supporting non-Administrator users installing Rust to somewhere under C:\Users\username or even C:\Users\Public

nodakai avatar Apr 12 '16 15:04 nodakai

You may also try using WiX (http://wixtoolset.org/), it's a declarative way of creating a Windows Installer (msi) that can be heavily customized. It also says that it supports custom actions written in C++, so it shouldn't be too hard to use custom actions written in Rust. On the other hand, however, this might be too big of a non-Rust build system/dependency.

Boddlnagg avatar May 13 '16 19:05 Boddlnagg

As a user, I've always preferred Inno Setup or NSIS installers over msi. Most msi installers I've used are slower and don't have as nice of a UI as the open source installers. They've also been buggier, but that might not be the Windows Installer's fault. I really don't want a UI made with conrad or .NET. Conrad does not look native and either one would add overhead.

I've written an NSIS installer before with similar screens, and I know it can call C functions. I'd be willing to work on an NSIS installer if there was a chance it would be used.

jminer avatar May 13 '16 20:05 jminer

How about this https://github.com/andlabs/libui

nxnfufunezn avatar May 23 '16 16:05 nxnfufunezn

How about using Qt Quick ? It is more mature than libui, has better documentation, the problem of high dpi (4K UHD) screens is solved too.

tiborgats avatar May 26 '16 15:05 tiborgats

@tiborgats that would add an unnecessary dependency for QtQuickControls etc for a simple installer.

nxnfufunezn avatar May 27 '16 09:05 nxnfufunezn

I'd like to keep using WiX for this. Seems to be the most 'modern' choice. It's what we're using today.

brson avatar Jun 23 '16 21:06 brson

The way to get started here is just prototyping: figure out how to make WiX, the rustup library and the Win32 GUI APIs work together to present something that looks plausibly like an installer.

brson avatar Jul 15 '16 23:07 brson

I tried to get this working and have a prototype running that ...

  • uses WixUIExtension (built-in UI that looks like standard windows installers, and can be customized e.g. with a page for rustup install settings), so there's no need to use Win32 GUI APIs.
  • runs a custom action from a Rust library (cdylib), which currently only reads a property from WiX (so we can read settings that are changeable in the UI) and logs it to the MSI log.

The only issues I encountered were the following:

  • When building the DLL with the custom action I had to link some libs from the WiX SDK and build with a 32-bit compiler.
  • Windows Installer requires something to be installed by the installer itself (either a file or a registry key), so I chose a registry key under HKCU\Software\rustup. Maybe the %USERPROFILE%\.rustup directory can be used instead (but that probably means that the installer will fail when it exists).

I have uploaded my experiments here: https://gist.github.com/Boddlnagg/9d8f01e6d844cd78473651470282ebda

I might be able to work more on this over the next weeks, but I don't want to keep anyone else from doing so, when they are faster.

Boddlnagg avatar Jul 16 '16 18:07 Boddlnagg

Further investigations led me to the MsiProcessMessage function (though it is better to use the WcaProcessMessage version from wcautil), which can be used to send progress and status updates from the custom action back to the UI. This requires the custom action to run as deferred (instead of immediate), which is the right thing to do anyway. I have updated my gist to use deferred.

The rustup installation routines will have to be refactored in such a way that status updates can be either reported to the console or via WcaProcessMessage.

Boddlnagg avatar Jul 19 '16 16:07 Boddlnagg

@Boddlnagg thanks for doing that research! Using a registry key for registration seems just fine.

When building the DLL with the custom action I had to link some libs from the WiX SDK and build with a 32-bit compiler.

This seems ok. We can use a 32-bit installer everywhere.

The rustup installation routines will have to be refactored in such a way that status updates can be either reported to the console or via WcaProcessMessage.

Makes sense.

In order to get the self-install to work from a library we're going to have to do some refactoring of the project structure. Right now there are two top-level artifacts: the rustup library and the rustup-cli binary. The rustup-cli bin is both rustup-init and rustup - it changes behavior based on how its invoked. The windows installer though is going to need access to that binary via a library. So we're going to have to create a new library that include!s the entire rustup binary. I'm not entirely sure how to make this happen but there are going to be at least a few steps.

  • To start with I'd say we just create a new project rustup-win-installer in src/. This project will depend on the main project. To start with it can just expect that the rustup binary exists in a known location, since getting a dependency to output a binary is not possible.
  • The self_update module in rustup-cli will require significant refactoring:
    • The code needs to move into the library rustup
    • The console specific bits need to be lifted out and injected from rustup-cli
    • The routine for finding the rustup binary likewise needs to be lifted out and provided by injection, since the win installer will be getting it from an included binary, not from the running executable.

With those refactorings we can whip up a basic installer that presents no options but does put the stuff in the right place.

brson avatar Jul 19 '16 20:07 brson

(I can probably help with the self_update refactoring since it could get ugly).

brson avatar Jul 19 '16 20:07 brson

To avoid duplication it's technically possible to load an executable as though it were a DLL and call functions from it - http://www.codeproject.com/Articles/1045674/Load-EXE-as-DLL-Mission-Possible there's probably a cleaner way though.

Diggsey avatar Jul 19 '16 20:07 Diggsey

I skimmed over https://github.com/rust-lang-nursery/rustup.rs/blob/master/src/rustup-cli/self_update.rs and was thinking that maybe we could remove the complex Windows-only logic to remove the running exe, by always using the MSI for uninstall/update (installed MSIs are cached, so it will exist on the system). That requires that rustup has been installed using the MSI, but if that will be the only method on Windows (i.e., rustup-init will no longer exist), it should be fine. I don't know about a possible upgrade path for already existing installations that have never used the MSI, though.

Also some other routines, that have Windows-specific codepaths (such as updating the PATH) could be handled by MSI directly. WiX provides ways of doing this easily, since it's something that installers often do.

Boddlnagg avatar Jul 19 '16 20:07 Boddlnagg

@Boddlnagg Just tell the user to delete their rustup installation and download and install using the MSI instead?

retep998 avatar Jul 19 '16 20:07 retep998

@retep998 That would be possible, of course, but will it be convenient enough?

@brson I don't quite understand these sentences:

To start with it can just expect that the rustup binary exists in a known location, since getting a dependency to output a binary is not possible. [...] The routine for finding the rustup binary likewise needs to be lifted out and provided by injection, since the win installer will be getting it from an included binary, not from the running executable.

Why are you talking about the rustup binary? I was expecting that the MSI just includes the custom action DLL (cdylib), which links everything in statically, so no binary would be included. Update: Oh well, I think I got it now ... the rustup binary is the thing that will be installed, after all, so it must be included. But then it should be extracted by the MSI itself. Is the place where it should be put known in advance or does some Rust code need to run to determine that?

Boddlnagg avatar Jul 19 '16 21:07 Boddlnagg

I skimmed over https://github.com/rust-lang-nursery/rustup.rs/blob/master/src/rustup-cli/self_update.rs and was thinking that maybe we could remove the complex Windows-only logic to remove the running exe, by always using the MSI for uninstall/update (installed MSIs are cached, so it will exist on the system).

I think this is a reasonable goal to aim for, though perhaps we can get there incrementally. If it ends up just being a lot easier to implement then maybe we can jump straight to that model. The transition story can be worked out later - there's a lot of problems to solve just to get to the point where we have a working GUI installer.

Update: Oh well, I think I got it now ... the rustup binary is the thing that will be installed, after all, so it must be included. But then it should be extracted by the MSI itself. Is the place where it should be put known in advance or does some Rust code need to run to determine that?

It's not obvious to me that it should be extracted by the msi itself since there is other logic to installation than just extracting the binary, but if we can make it work that way then that's probably best. The logic for deciding the installation is in self_update::install_bins and utils::cargo_home but it's pretty simple - if CARGO_HOME is set put it there; if not put it in a pre-determined location. This logic will likely expand somewhat in the future.

The bulk of the GUI customization work we'll need to do is for configuring the global installation options and for installing/updating/uninstalling toolchains. If the basic work of putting the rustup bin in the right place can be done by the MSI system itself that seems good.

brson avatar Jul 19 '16 22:07 brson

How are updates going to be handled with an MSI installer? If we just use the existing update system, then 1) it might confuse the uninstaller if the files are different from those it installed, and 2) windows will show incorrect version info in the list of installed software.

However, requiring the user to download a new .msi each time isn't great, but I imagine you could have rustup download and run the .msi? Or there might be some facilities for updates built into the windows installer system, I don't remember.

Diggsey avatar Jul 19 '16 22:07 Diggsey

I use some software which uses a .msi to self update. All it does is trigger the UAC prompt and then I see a small progress dialog and then its done. So it is definitely possible to have a self update using that.

retep998 avatar Jul 19 '16 22:07 retep998

The standard way of doing self-update is called ClickOnce not sure how hard it is to do now with just free tools, needs some research.

cyplo avatar Jul 20 '16 05:07 cyplo