Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] MSI Installer #635

Merged
merged 10 commits into from
Aug 17, 2016
Merged

[WIP] MSI Installer #635

merged 10 commits into from
Aug 17, 2016

Conversation

Boddlnagg
Copy link
Contributor

@Boddlnagg Boddlnagg commented Aug 2, 2016

This is some initial setup and prototyping work for the rustup MSI installer. I don't think this is ready to be merged (*), but I wanted to collect some feedback on the general structure and see how it works with the CI (but I haven't touched appveyor.yml yet).

I created the rustup-win-installer project within src, which is the library containing the custom actions, and the msi subdirectory contains the WiX/MSI source files. The WiX tools are invoked by a Powershell script (build.ps1) and the final MSI is placed into \src\rustup-win-installer\msi\target. I'm not sure whether it should actually be the other way round, because the WiX project depends on the custom actions library (and on the rustup binary, which is expected to be found at \target\release\rustup-init.exe).

The gfx is copied from rust-packaging.

(*) It currently only installs rustup.exe into %USERPROFILE%\.rustup-test\bin, because I don't know how to easily test it without overwriting my actual rustup installation.

@Boddlnagg
Copy link
Contributor Author

There is another thing to be clarified here: I have added a feature flag msi-installed that triggers different behavior on self uninstall and (not yet) self update. This changes the SHA256 of the executable, which is currently used to check if an update is needed. I see a few ways to move forward:

  • Add a separate .sha256 file for the rustup.exe packed inside the MSI (this rustup.exe will only be available from inside the MSI).
  • Don't use a feature flag, but instead determine at runtime (e.g. by checking the registry key) whether the MSI installer has been used.
  • Change the logic to check for updates to not require a self hash.

@brson
Copy link
Contributor

brson commented Aug 6, 2016

😮 I will review soon.

@brson
Copy link
Contributor

brson commented Aug 10, 2016

OK, I'm so sorry it's taken me so long to get back to this @Boddlnagg!

This looks amazing. The directory structure you've laid out looks fine, for now at least. Perhaps at some point we can put all this stuff into a workspace so we don't end up dumping an extra target directory under src.

Once the msi installer is ready to go I expect it to be the only way to install rustup on windows. So ultimately we won't need to make a distinction. But we will need to get the msi-rustup deployed alongside non-msi-rustup in the interim while we test it and develop an upgrade path. A scheme that might work here is to name this new binary rustup-init-msi.exe, and we can call the hash rustup-init-msi.exe.sha256. Then we can modify the appveyor config to start producing this and deploying it alongside the current configuration.

It doesn't look like this iteration is invasive at all. Would it be reasonable to merge it as-is and keep iterating?

Again, I'm really sorry to block you on this for so long.

@brson
Copy link
Contributor

brson commented Aug 10, 2016

Oh, I may not understand the issues around the hash. Presumably the installation artifact once this is all done will be rustup.msi, and we would then have rustup.msi.sha256. There would be no rustup-init.exe at all. Is that right?

If that's the case then I would expect there to be no sha256 or verification of the executable at all, but instead to verify the hash of the msi. Does that make sense?

@Boddlnagg
Copy link
Contributor Author

@brson: Yes, the installation artifact will be rustup.msi, but we can only compute the hash of the currently running rustup.exe, so rustup.msi.sha256 won't be useful for checking for updates. There will be no rustup-init.exe, only rustup.exe, which is packed inside rustup.msi. Since I would like self update to only invoke msiexec with a remote URL (i.e., the new MSI will be downloaded by msiexec), we can't use the hash of rustup.msi for verification (I think MSI has a built-in verification checking whether the file is valid, to safeguard against partial up-/downloads). So rustup.msi.sha256 will not be usable for anything.

@Boddlnagg
Copy link
Contributor Author

@brson wrote:

It doesn't look like this iteration is invasive at all. Would it be reasonable to merge it as-is and keep iterating?

I would like to add the necessary changes to appveyor.yml first (without uploading artifacts, of course). But if you want to have that in a separate PR, that's fine with me.

@Boddlnagg Boddlnagg force-pushed the msi-installer branch 2 times, most recently from 80b64b5 to 5035bcc Compare August 11, 2016 09:59
@Boddlnagg
Copy link
Contributor Author

Boddlnagg commented Aug 11, 2016

Apparently the version of cargo currently used on AppVeyor does not yet know about the crate-type cdylib. Can we easily update to a newer version (both AppVeyor and Travis use a nightly from 2016-05-10)?

@brson
Copy link
Contributor

brson commented Aug 11, 2016

Ah thanks for clarification about the hashing issue. So in msi-world the only thing we need the hash for is determining whether an update is available. I'd say give it a unique name, like rustup-msi.exe.sha256. Perhaps before it's finalized we can come up with a scheme to transition it back to the existing rustup-init.exe.sha256 name. We might also (later) think about whether there is a better scheme for detecting updates since the unique value being a hash of the exe is completely arbitrary if we're not validating it.

That's funny that we're still pinned to such an old toolchain. There's no reason you shouldn't be able to switch it to stable (both travis and appveyor) in this PR.

@brson
Copy link
Contributor

brson commented Aug 11, 2016

I went ahead and opened a PR just to do the toolchain upgrade #651

@Boddlnagg
Copy link
Contributor Author

Unfortunately, cargo from 1.10 stable does not yet know about cdylib (though rustc does). I think 1.11 will be released soon, so this is probably not a big problem.

@Boddlnagg
Copy link
Contributor Author

Boddlnagg commented Aug 12, 2016

I wonder if it's really necessary to build for all four targets on AppVeyor. Which one is served at https://win.rustup.rs/ ? Right now I've configured it so that the MSI is only built on i686-pc-windows-msvc. I think MSVC is required because we're including some .lib files, and probably we don't want to distribute different MSIs for x86 and x64.

@brson wrote:

I'd say give it a unique name, like rustup-msi.exe.sha256. Perhaps before it's finalized we can come up with a scheme to transition it back to the existing rustup-init.exe.sha256 name.

Sound good to me. I thought about a possible transition strategy: When the MSI installer is ready, we can change the self update routine of the old version and have that use the MSI. I will try to make sure that the MSI installer just does the right thing in that case and won't delete already installed toolchains (should be easy).

Update: I just noticed that this won't work because we can't expect users to be using the latest pre-MSI version of rustup which contains the updated upgrade logic. But still the MSI installer should be able to deal gracefully with an existing pre-MSI rustup installation without deleting installed toolchains or the cargo cache.

@Boddlnagg
Copy link
Contributor Author

And another comment (sorry ...): I already ran into issues with diverging Cargo.lock files. I think the only solution here is to use Cargo workspaces, which currently require a nightly. Maybe we should just update to a more recent nightly?

@brson
Copy link
Contributor

brson commented Aug 12, 2016

I wonder if it's really necessary to build for all four targets on AppVeyor. Which one is served at https://win.rustup.rs/ ? Right now I've configured it so that the MSI is only built on i686-pc-windows-msvc. I think MSVC is required because we're including some .lib files, and probably we don't want to distribute different MSIs for x86 and x64.

We could stop building all but one, though I like knowing they all build. i686-pc-windows-gnu is the one served by default. We use GNU by default because MSVC requires the CRT. MSVC would be better.

Update: I just noticed that this won't work because we can't expect users to be using the latest pre-MSI version of rustup which contains the updated upgrade logic.

I think we can. The old rustup will eventually upgrade itself to a rustup-init.exe that understands how to find the msi.

Maybe we should just update to a more recent nightly?

I'll upgrade to nightly.

@retep998
Copy link
Member

retep998 commented Aug 12, 2016

We use GNU by default because MSVC requires the CRT

Rather, because MSVC dynamically links a version of the CRT that doesn't come with every version of Windows we support. The only thing that needs to not depend on a component that might not be installed is the installer itself. It can install the necessary bits though so rustup itself could dynamically link to CRT stuff just fine. If the installer has Rust code in it though, then it would be a bit tricky, possibly requiring rust-lang/libc#290 to be fixed.

@Boddlnagg
Copy link
Contributor Author

Boddlnagg commented Aug 13, 2016

Ugh ... yes, there actually will be Rust code running inside the installer. So the recommended way is to use MSVC and link statically, but that's currently not supported according to @retep998? With GNU there is the problem that we want to (statically) link the WiX Custom Actions helper library wcautil.lib, and I don't know how to get an .a for it (or if that's possible at all). This is not strictly required though, as wcautil is just a wrapper around the msi library (no bindings yet, unfortunately) with some helper functions that could be ported to Rust.

One can always embed the merge module for the VC Runtime inside the MSI, but that won't help us here, because we run Rust code quite early during the installation procedure and the Runtime would not have been installed by then (also it would probably lead to problems on uninstall, when the VC Runtime is deleted before we've finished executing the Rust code). At least this allows us to install a dynamically linked MSVC version of rustup.exe, if that's preferred.

@brson wrote:

I'll upgrade to nightly.

Thanks. Should I setup a cargo workspace right away? (Update: I just went ahead and did it, after rebasing on top of the version update.)

@retep998
Copy link
Member

So the recommended way is to use MSVC and link statically, but that's currently not supported according to @retep998?

You can statically link the CRT just fine as long as you pass to the linker libcmt.lib and /NODEFAULTLIB:msvcrt, along with compiling all C/C++ code that is statically linked in with /MT.

(no bindings yet, unfortunately)

I'm not sure how soon I'll be able to get to this due to the winapi 0.3 overhaul I'm working on.

@Boddlnagg
Copy link
Contributor Author

Boddlnagg commented Aug 13, 2016

You can statically link the CRT just fine as long as you pass to the linker libcmt.lib and /NODEFAULTLIB:msvcrt, along with compiling all C/C++ code that is statically linked in with /MT.

rustup has quite a few dependencies, some of them native dependencies using gcc-rs, but gcc-rs can't pass /MT yet (i.e. unconditionally passes /MD), right? (Before I came to Rust I was mainly using C# and don't have much experience with native toolchains ... I'm glad that everything is usually so easy with cargo.)

@retep998
Copy link
Member

but gcc-rs can't pass /MT yet, right?

I doubt gcc-rs will be taught how to pass /MT until rust-lang/rfcs#1684 is figured out.

@Boddlnagg
Copy link
Contributor Author

Boddlnagg commented Aug 13, 2016

@retep998 Okay, I was reading through that RFC thread and in search of a workaround I tried, based on a comment by @vadimcn (without actually knowing what I'm doing):

  • println!("cargo:rustc-link-lib=static=libvcruntime"); in build.rs
  • build the Custom Action cdylib with cargo rustc --release --target i686-pc-windows-msvc -- -Clink-args="/NODEFAULTLIB:vcruntime.lib"

This seems to work as DependencyWalker doesn't show a dependency on vcruntime140.dll anymore, but I don't really understand the implications. You said that "That basically breaks support for older versions of MSVC before the transition to ucrt and vcruntime" but this wouldn't be a problem here, since we can always require the latest MSVC version to build rustup on Windows.

Update: This also seems to work and gets rid of a few more dynamic dependencies:

println!("cargo:rustc-link-lib=static=libvcruntime");
println!("cargo:rustc-link-lib=static=libucrt");
println!("cargo:rustc-link-lib=static=libcmt");

and

cargo rustc --release --target i686-pc-windows-msvc -- -Clink-args="/NODEFAULTLIB:vcruntime.lib /NODEFAULTLIB:ucrt.lib /NODEFAULTLIB:msvcrt.lib"

Only overriding msvcrt.lib with libcmt does not seem to be enough.

Update 2: Okay, I added some more code to the library so that it actually does something, and now the linker complains about unresolved symbols __imp___stricmp, etc. with either of the above linking modifications. So apparently it does not work without passing /MT to all dependencies.

@Boddlnagg
Copy link
Contributor Author

@retep998 Using a local patched version of gcc-rs where /MT is passed instead of /MD is apparently not enough. I'm still getting multiple LNK4049 warnings e.g. for symbol _fclose, as well as LNK2001 errors e.g. for symbol __imp__stricmp. May this be due to dependencies on libc?

I don't know what the best way forward would be right now (as long as rust-lang/rfcs#1684 is not resolved). I could try to use the MinGW toolchain, i.e. drop the dependency on the WiX CA helper library wcautil.lib and hope that MinGW's version of libmsi.a (used by winapi) is sufficiently complete and correct.

@brson
Copy link
Contributor

brson commented Aug 17, 2016

@Boddlnagg Trying to build on msvc to see the dependency issues. How is the workspace set up (it looks to me like the top-level Cargo.toml is not a workspace but I don't know much about it)? Are your patches to do the static linking pushed? It looks like no. Can you push another commit with those? In the meantime I'll try to reproduce based on your description of the solution.

Until we come up with a solution I think it's fine for us to merge with dynamic linking and just have it break when the crt dylib isn't available. We've got more iteration yet before we have to deploy.

@Boddlnagg
Copy link
Contributor Author

How is the workspace set up (it looks to me like the top-level Cargo.toml is not a workspace but I don't know much about it)?

The top level Cargo.toml defines the workspace, see the [workspace] section. Everything else works automatically. It just means that Cargo.lock and target is shared with all the other crates.

Are your patches to do the static linking pushed? It looks like no. Can you push another commit with those?

I didn't push anything to do the static linking, because I can't get it to work. But it's really only about adding a few lines to build.rs and changing the build command to use cargo rustc with -Clink-args=...

Until we come up with a solution I think it's fine for us to merge with dynamic linking and just have it break when the crt dylib isn't available. We've got more iteration yet before we have to deploy.

So you would be okay with not being able to build on the GNU toolchain?

@brson
Copy link
Contributor

brson commented Aug 17, 2016

So you would be okay with not being able to build on the GNU toolchain?

As long as we continue to have GNU builds for the non-msi rustup-init.exe, yes. I think that's the situation here, right? It leaves all other build configurations intact, creating one new one for i686-pc-windows-msvc for the msied rustup.

@Boddlnagg
Copy link
Contributor Author

Yes, that's right. Then you could merge this as-is. AppVeyor builds fine, although it prints some output in red because cargo sends status messages to stderr and PowerShell thinks that having anything written to stderr means that an error occurred 😒

@brson
Copy link
Contributor

brson commented Aug 17, 2016

OK. I'm going to go ahead and merge this as-is then. It's great progress.

@brson brson merged commit 39a2014 into rust-lang:master Aug 17, 2016
@Boddlnagg
Copy link
Contributor Author

The build failed, but totally unrelated, probably due to a change in the latest nightly.

@brson
Copy link
Contributor

brson commented Aug 18, 2016

Looks like the breakage will be fixed in the next nightly rust-lang/rust#35721

@Boddlnagg Boddlnagg deleted the msi-installer branch August 18, 2016 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants