Improving the distribution of Swift on Windows

Windows is maturing, but there is a still a long path to get to the point where we should start thinking about ABI stability. Similar to how Apple platforms prior to ABI stability, Windows will require shipping the runtime as part of application distributions for some time.

There have been previous threads on the forums about the difficulty about the packaging.
A discussion with @etcwilde about Windows made me realise that one piece that we are currently missing in the toolchain installation is a MSM for the runtime. This is something Visual Studio already does for the redistributable (though it is deprecated as it simplifies updates with versioning).

I was curious if people who may be interested in distributing the redistributable, would it be more convenient to have the redistributable available as a MSM or a MSI? The latter would always require that the application redistribution is in a bundle to install the chain for the dependency (similar to the installer.exe for the Swift toolchain).

Of course, it is possible to package both. The question is more about value for the size increase. One thing that I have wanted to accomplish for a while is to change the Windows toolchain installer to have 2 variants:

  • offline installation: the current installer - bundled MSIs
  • online installation: a variant where the installer.exe is tiny (~1MiB) and dynamically fetches the MSIs that are required.

The latter is something that there is some work in progress that will make that more reasonable to accomplish. The current ongoing refactoring in the installer should allow some more control over the installed components. I know that others may be curious about some of the desired restructuring:

  • "Compilers"
    • clang
    • swift
    • binutils
    • linkers
  • Debugger & REPL
    • lldb
    • Swift REPL
  • Command Line tools
    • SPM
    • clang-format
    • swift-format
    • swift-inspect
  • IDE tools
    • clangd
    • SourcKit-LSP
    • LLDB VSCode Plugin
  • SDKs
    • Windows AMD64
    • Windows ARM
    • Windows ARM64
    • Windows x86
  • Runtime
    • Windows AMD64
    • Windows ARM
    • Windows ARM64
    • Windows x86
  • Redistributables
    • Windows AMD64
    • Windows ARM
    • Windows ARM64
    • Windows x86

* A note on the grouping here. I think that the SDK, Runtime, Redistrbutable should be the only categories that give any control over the sub-components. The remainders would be all or nothing. The runtime for the host would not be possible to unselect of course (which would be one of AMD64 or ARM64, as there is no intention to support a 32-bit toolchain).

While it may logically make some sense to split out the binutils from the rest of the toolchain, it might help with compression to keep it bundled. This should allow finer grained control for installation for development vs CI usage. It would be interesting to hear if there are use cases that would be better served by further restructuring the top-level components to allow us to have the necessary control for the use of the installer in various cases.

14 Likes

I agree with the breakdown above. I'm going to write down my ideas, musings, thoughts, and brain-dumps from conversations I've had with @compnerd offline so that folks can see where some of this comes from.

Windows today-ish doesn't ship the C++ runtime on Windows. Instead, the runtimes are distributed and installed in the application "bundle" (not familiar with Windows terminology, but that should be clear enough). Latest supported Visual C++ Redistributable downloads | Microsoft Learn.
This has some interesting benefits, like not having to worry about ABI differences, and applications that depend on buggy behavior can ensure that they're packaged with the buggy runtime that they depend on.
Meanwhile, unlike statically linking (which ensures the same things noted above), if there is a security vulnerability found in a subset of the DLLs, the individual DLLs can be updated without having to rebuild and distribute the whole program.

The Swift runtime doesn't guarantee ABI stability off of Darwin, so bundling the Swift runtime in the application bundle would make sense, and be consistent with the Windows packaging expected today. The Swift compiler and other programs would have a C++ runtime and Swift runtime distributed in its bundle.

Now where things get a little bit more interesting. Windows seems to be pushing away from this model. Redistributing Components By Using Merge Modules | Microsoft Learn. Having every application bundle the C++ runtime is kind of silly, since largely, they shouldn't need a specific (older) version of the C++ runtime. It also means that any program that doesn't update their runtime in the event of a vulnerability is still compromised. So now systems are only partially secured if some applications update the library while others don't. This comes with some challenges. The few questions I have is, where is the "central" location where libraries are "supposed" to go, and what about ABI? I'm glad you asked.

Microsoft describes three options for distributing the C++ runtimes;

  • Central deployment
  • Local deployment
  • Static linking

Central Deployment

Central deployment is what we do today for the Swift runtime. Programs share the runtime as it is installed. This saves storage space and means that anyone who updates the library can update the runtime for everyone, so it's good for safety. It does require that the library is ABI-stable though, which the Swift runtimes and standard library are not on Windows. This is the model that Microsoft is pushing toward as well, so they do have some answers to this question. Specifically, using version numbers in the DLL name/path.

It's still not without restrictions; users need administrative privileges to install libraries centrally. That makes sense from a system security standpoint, but it is a restriction on who can install the runtimes. We do want to think about folks trying to learn Swift in schools where they might not have admin rights.

Local Deployment

Local deployment is what the Microsoft C++ runtimes are transitioning away from, but has worked for them and ensures no ABI breakage. It also ensures that if you tested your application locally before deployment, then deploy it, that it will "just work". It does mean that we're potentially bundling a lot of things though. We would need to include the C++ runtimes and the Swift runtimes. If you bundle corelibs-Foundation, you would need all of its dependencies bundled as well. It doesn't have the admin privilege restriction, so you could download any Swift application and it would work.

It still gives developers the option to update pieces of their program instead of having to redeploy the entire thing, but they would have to know/remember to do that.

Static Linking

I don't have enough experience on Windows to make any certain statements here on developer experience, but from my understanding link is a bit nicer than the unix-style ld in that order and repetitions aren't important for the linking algorithm. The only thing that I see being different here vs the local distribution is that developers only have one binary to distribute, which is a double-edge sword. There's only one binary to distribute, but in the event that any of the libraries contain a vulnerability, the entire binary has to be replaced. It won't require admin privileges and we don't need to worry about ABI because everything is sealed up. (I guess there's one other face here, I don't believe statically linking Swift runtimes works on Windows at the moment, but I could be wrong.)

Summarizing my feelings

I think the ABI thing concerns me most at the moment. We don't currently version the runtime, so anyone updating it will potentially leave all other programs silently broken, which is bad. The core stdlib and runtimes do benefit from Darwin being ABI stable, but other things like corelibs-Foundation do not.
In that regard, I would be most comfortable with programs bundling the runtime they were built against directly in the application. This will ensure that ABI is not a problem and that they will "just work". Like with Microsoft's C++ runtimes, there would be a package for ARM64, X86, and X64. Folks who want to use Swift would bundle those C++ runtimes and the Swift runtime in their program and then they could deploy that wherever.

Once we have version numbers or have a reasonable story for ABI stability on Windows, then I would feel more comfortable saying that we should/could have a central system Swift runtime. This is a future-looking statement, not criticizing the work that has already been done. I recognize that time is a limited resource. :slight_smile:

10 Likes

Love the post and the overall direction here, any thoughts of shipping the swift runtime and SDKs as MSIX packages so that we can let the OS handle side-by-side versioning for the runtime and clean install/uninstalls?

That is the underlying motivation for the MSM redistributables. I'd like to get everything into place to enable the easy repackaging for the Swift runtime into an application deployment. That should be the only supported/recommended way to distribute the applications on Windows. Repackaging the libraries manually is difficult and requires additional scripting. The MSMs enable the redistributable to packaged into the application by including the merge module and co-locating the DLLs with the application.

Once we can version the runtimes, it would make using the shared library version possible, but until then, the local packaging should be preferred. The proposed mechanism sets everything up so that we are testing the functionality of the ideal solution without the concerns of stability in the mean time (though applications pay for size of the runtime). In the mean time, I think that we should even still be able to get some of the benefits of page sharing as the runtime is dynamically linked.

1 Like

What do the (few) other compiled toolchains do here? I believe Rust and Go always statically link their stdlibs; by contrast, my understanding is that game frameworks like Unity don’t bother to do anything more than “local deployment”.

Static linking is not supported on Windows for the standard library. This is a limitation of the compiler, language, and package manager. We need to explicitly demarcate the ABI boundaries and a module must be built multiply (for static and dynamic linking). There are a few issues that still exist even for static linking of non-standard library modules.

I wouldn't be surprised, and that is currently the path that we will need to follow I imagine. In theory, versioning the library is possible, but the current build system isn't very helpful to accomplishing that.

Are you referring to the runtime or the toolchain? I don't think that we can ship the toolchain as a MSIX as we currently chain a series of MSIs together. Is that something that MSIX permits? Additionally, my understanding is that the MSIX installer is not as flexible (i.e. you cannot have a UI that you control and provide control over the installed components). If it is for just the runtime, that is more interesting. I think the thing to consider there is the overall size of the distribution. In theory, we would not distribute the MSMs either as that grows the size of the installer, but this is an area that we need to tradeoff the complexity in the toolchain to ease the application deployment/distribution story. I wouldn't mind seeing this go away some day though.

Handling side-by-side versioning through the OS definitely sounds very appealing though. Do you have any references on that so that I can read up on that?

1 Like

Due to my limited knowledge, I would not make any recommendation for an installation on Windows (which of course should follow a way that is considered "the usual way" on Windows), but I would like to point that out that it would be very convenient if this would be as transparent as possible, e.g. by writing an installation log into the toolchain installation.

The reason for this is that some of my customers have high demand for distribution to be as far as possible "portable" (i.e. the installation being reconstructible by only copying some directories and setting some paths when using them). So it should be easy to see which files are actually being used (and what paths are set), so from an installation a distribution can be built that is (as far as possible) portable. (Of course, one then has to make sure that licenses are suitable for such a portable installation.)

Thanks.

.\installer.exe /log log.txt

Has been supported since day one. There were custom actions which are no longer present, so that should be pretty complete.

The installation is already portable - the environment variables that are setup are for convenience only. The only thing that you need to adjust is Path (to find the runtime as there is no LD_LIBRARY_PATH or RPATH). SDKROOT is used to autodetect the SDK rather than specifying it with -sdk.

This is all (IMO) well documented in the sources: GitHub - apple/swift-installer-scripts. The wxs files list all the files distributed and where they are installed, as well as any environment variables being setup.

The current roadblock to the desired state is that there doesn’t seem to be support for multipurpose MSIs (installing per-user or per-machine). In order to drop the administrator rights requirement I’m erroring on the side of per-user, which I believe is closer to what you’re describing.

2 Likes