Official platform support for other Linux Distributions (and a case for Amazon Linux 2)

The SSWG announced that they are looking for other Linux Distributions besides Ubuntu to support official prebuilt toolchains for in their yearly update:

Swift currently offers prebuilt toolchains for Ubuntu. There are many other distros out there and we’d love to see them supported officially. The process for adding a new Linux distro to Swift is not clear at the moment. We think that can be improved. The community could help by identifying which Linux distros are important and contributing reliable build scripts. In a perfect world, we could make this entire system self serve. Does this sound interesting to you? If so, reach out to us.

Further @tkremenek stated in "On the Road to Swift 6":

Swift is an established language on Apple platforms for app development, and it’s steadily growing in other domains. As a community there are a variety of ways we can accelerate that growth, widening Swift’s availability and impact to everyone.

Here are some concrete goals we can work on together as a community:

  • Expand the number of platforms where Swift is available and supported

After some research the only documented way I found to engage in widening the platform support is by administering a Jenkins slave that builds Swift on a given platform as part of the Community-CI effort.
Sadly administering such a slave comes with at least a monetary burden (besides time and security).

For this reason I want to ask: What will the process be to become an officially supported platform? If there is no process planned so far maybe we can start a discussion here.

A case for Amazon Linux 2

As someone trying to be involved in widening the adoption of Swift on the Server and integrating it with AWS, I created a build setup for Amazon Linux 2. One can download a prebuilt toolchain for Swift 5.1.2 and 5.1.3 on my website. Furthermore I created matching Dockerimages and an AWS Lambda Layer to run Swift code natively within Lambda. All scripts involved in building are public on GitHub.

I can imagine that Amazon Linux 2 might be a platform of interest for official Swift support. While being a proprietary Linux Distribution, we can all assume that AWS and with this Amazon Linux is here to stay. One could argue that even on AWS the most common Swift deployment should be within Docker, which is already supported today. But with the rise of "serverless" deployments I would like to state that Swift on AWS Lambda is at least a topic of high interest. For now Lambdas have to be executed in an Amazon Linux container. See discussion and number of topic views on "AWS Lambda Runtime API".

If we want to broaden adoption of Swift on the Server support for AWS Lambda should not involve downloading a Swift toolchain from someones personal website. Even I don't know if I can trust myself. :face_with_raised_eyebrow:

That's why besides asking for the process of becoming an officially supported platform, I would like to start the discussion if Amazon Linux 2 might be a worthy candidate.

Note: I'm not sure if this is the right category for this topic. I was looking for something named "platforms" but couldn't find anything. This seemed like the least bad candidate.

16 Likes

CC @mishal_shah & @tomerd

I'm very in favor of having support for Amazon Linux 2! We deploy a lot using Docker and ECS - but it would help in many cases if Swift would also natively run on AL.

Here is another vote for Amazon Linux 2. Anything that makes it easier to deploy on AWS and easier for @fabianfett to maintain his lambda would be great!

1 Like

How is this handled in other languages? For instance, I have never heard discussions about Rust or Go having issues depending on linux distribution: why is this an issue for Swift in particular?

3 Likes

TL;DR: Swift is much more exposed to the system than Go or Rust.

Go is a special snowflake, so we'll address it first: Go has essentially zero system dependencies. On Linux it makes all syscalls itself, without going through libc, and so is well protected against differences in libc versions and types. It links almost none of the binaries provided by Linux. Your average Go binary links nothing from the system, and the Go build tool itself has the following linkage:

	linux-vdso.so.1 (0x00007ffef1d47000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa1a881b000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa1a842a000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fa1a8a3a000)

That's libc, libpthread, and the vDSO: that's it. Not much! Because it depends on so little, the risk that Go will inadvertently use something from the system that is not available on all Linux distributions is very low.


Next, Rust. Unlike with Go, Rust does link some things from the system to get its standard library working. In fact, if you take a thin OS image and install Rust on it, your rust toolchain won't work! It requires that you have a compiler toolchain installed already. So we're already away from where we were with Go.

Regardless, if we build Cargo's hello world project we see the following linkage:

	linux-vdso.so.1 (0x00007ffd8cd68000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f03b67e2000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f03b65da000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f03b63bb000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f03b61a3000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f03b5db2000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f03b6c19000)

There are some more things here! libgcc_s, librt, and libdl. This is an indication that even more of Rust's standard library depends on things coming from the system. If we look at what rustc requires we see an additional dependency on libm.


Now let's look at Swift. If I do swift package init --type=executable and print the linkage of the resulting binary we get:

	linux-vdso.so.1 (0x00007ffc1a5c1000)
	libswiftSwiftOnoneSupport.so => /usr/local/swift/usr/lib/swift/linux/libswiftSwiftOnoneSupport.so (0x00007f64734a4000)
	libswiftCore.so => /usr/local/swift/usr/lib/swift/linux/libswiftCore.so (0x00007f6472d7e000)
	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f64729f5000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6472657000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f647243f000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f647204e000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6471e2f000)
	libatomic.so.1 => /usr/lib/x86_64-linux-gnu/libatomic.so.1 (0x00007f6471c27000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f6471a23000)
	libicui18nswift.so.61 => /usr/local/swift/usr/lib/swift/linux/libicui18nswift.so.61 (0x00007f647152d000)
	libicuucswift.so.61 => /usr/local/swift/usr/lib/swift/linux/libicuucswift.so.61 (0x00007f647114d000)
	libicudataswift.so.61 => /usr/local/swift/usr/lib/swift/linux/libicudataswift.so.61 (0x00007f646f5a8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f64732c2000)

This is a lot more! Many of these are part of the Linux distribution of Swift so they don't count, but we can add libatomic and libstdc++ to the mix.

But this is still not too bad, right? Right, except that we have way more libraries than that. Swift ships at least the following libraries:

libBlocksRuntime.so
libFoundation.so
libFoundationNetworking.so
libFoundationXML.so
libXCTest.so
lib_InternalSwiftSyntaxParser.so
libdispatch.so
libicudataswift.so
libswiftRemoteMirror.so
libswiftSwiftOnoneSupport.so
libicui18nswift.so            
libicuucswift.so
libswiftCore.so
libswiftDispatch.so
libswiftGlibc.so

If we ask them all what they need and uniqify them, we end up with the following dependencies on the system:

/lib64/ld-linux-x86-64.so.2
libasn1.so.8
libatomic.so.1
libc.so.6
libcom_err.so.2
libcrypt.so.1
libcrypto.so.1.1
libcurl.so.4
libdl.so.2
libffi.so.6
libgcc_s.so.1
libgmp.so.10
libgnutls.so.30
libgssapi.so.3
libgssapi_krb5.so.2
libhcrypto.so.4
libheimbase.so.1
libheimntlm.so.0
libhogweed.so.4
libhx509.so.5
libidn2.so.0
libk5crypto.so.3
libkeyutils.so.1
libkrb5.so.26
libkrb5.so.3
libkrb5support.so.0
liblber-2.4.so.2
libldap_r-2.4.so.2
libm.so.6
libnettle.so.6
libnghttp2.so.14
libp11-kit.so.0
libpsl.so.5
libpthread.so.0
libresolv.so.2
libroken.so.18
librt.so.1
librtmp.so.1
libsasl2.so.2
libsqlite3.so.0
libssl.so.1.1
libstdc++.so.6
libtasn1.so.6
libtinfo.so.5
libunistring.so.2
libutil.so.1
libuuid.so.1
libwind.so.0
libxml2.so.2
libz.so.1
linux-vdso.so.1

This is quite a lot! Much more than Rust, and hilariously more than Go.

This is because Swift on Linux includes Foundation, and in particular FoundationNetworking and FoundationXML, an extremely broad pair of libraries that have been built on top of the primitives provided in Linux distributions. When using another Linux distribution, some or all of these dependency libraries will be present either in different versions, or with different implementations. These require compatibility shims and version-specific code to be added to Swift's libraries in order to get them to work.

Note that this is a different goal from being able to use Swift on Linux. It's extremely possible to build a Swift binary on Linux that can easily be made to run on a wide range of platforms, if you don't rely on FoundationXML or FoundationNetworking. However, to distribute an official Swift on those platforms those two libraries must be present. The difficulty of using a different Linux distribution therefore comes down to the cost of porting to use other versions of these libraries.


Incidentally, this is not actually a problem unique to Swift: many other languages have similar issues, especially higher level interpreted ones. Go and Rust are somewhat unique in their effort to reinvent all wheels in their own programming language and without deferring to the system. Python links several libraries as well, for example, and therefore requires some porting work to run on a new system.

43 Likes

Thanks @lukasa, thats a great overview.

Wile no approach is right or wrong, there are tradeoffs to each. I've not looked into Go too much, but now I understand why Go is so portable, for better or for worse.

Thanks for overview @lukasa
What are bad sides of Go way? Why not use this type of way for Swift?

1 Like

The most prominent downside is that you have to reinvent everything yourself, and by that I truly mean everything. Consider the functions of even just libc: memory allocation, file I/O, socket I/O, selectors, DNS resolution, user management, filesystem permissions, threads, subprocesses, signal management, and more that don't even come to mind. The scope of work for a fully-functional libc replacement is very large.

But of course, that assumes that all you need to replace is libc, but as shown above, that is not all we need to replace. We need a HTTP client capable of supporting the full feature set of URLSession, which is also a large amount of work.

Other downsides occur on platforms where you cannot perform an end-run around some system libraries without breaking. For example, glibc actually needs to be dynamically linked for some user authentication processes to work properly. Another example is that Apple platforms have no stable syscall ABI, for example, so even if we wanted to build a replacement for libc we'd never use it on Apple platforms, so the work would be done for Linux and Linux alone.

None of this is impossible, but it is definitely the hardest way to do things. This is also why Rust did not go down this road: in some senses it's a boil the ocean strategy for library development. It can be done, but personally I don't believe it's a sensible way to spend time. It costs less to do the work to write compatibility code and CI on a wide range of platforms than it does to boil the ocean in this way.

7 Likes

I don't think Compiler is the right category for this, but I'm not sure what is better. In any case, something to consider: most Linux distros are derived from other distros. If you really want to see Swift get into more distros, then consider focusing your energy on the "upstream" distros. Once Swift is in an upstream distro, then convincing a downstream distro (like Amazon Linux) to adopt Swift is relatively easy.

In practice, there are two "root" distros: Debian and Fedora. If you can convince those two to add Swift, then getting downstream distros like Ubuntu, Amazon Linux, RHEL, CentOS, etc to adopt Swift will be much easier.

12 Likes

This is excellent advice. I'd like to note that we should separate the goals of getting the distribution itself to ship Swift and getting a supported version of Swift on those distributions. In some sense these two things are unrelated, though obviously it helps to do the latter if you want to do the former! (That said, I know you can in fact get a Swift on Fedora.)

I think this community should for the most part focus on getting the Swift project to support those distributions, and let the distributions themselves take the lead on shipping Swift in their package repositories. Fundamentally they're just better suited to do the work. With that said, the ideal package maintainer is someone with a foot in both communities, so if someone here feels like becoming a Debian Developer and maintaining a Swift package, I'm sure they'll find people willing to help them out.

And, just a throwaway comment here, but: with its potential for having a stable ABI, Swift on Linux has some very interesting possibilities for use building system libraries for Linux. Just a thought.

7 Likes

I'm pretty sure that most distributions (not only Linux) would already support Swift if it wouldn't be as hard to build the whole package: git clone, cd, and make might be a goal that's too ambitious, but the process could definitely be streamlined.

1 Like

Hi - Todd Varland from Amazon here. Please post to this thread if you are interested in/supportive of this topic. Also, commentary on technical findings and suggested approaches and links to additional resources is greatly appreciated.

8 Likes

I'm the person who maintains Swift for Fedora (and because of that, CentOS and RHEL), and the biggest challenges I have are:

  1. Porting Python 2-based build scripts to Python 3
  2. Making the Swift toolchain play nicely with existing LLVM/Clang/LLDB installations

While the first issue theoretically will work itself out in due time (can't stay on Python 2.x forever), the second issue is one that I believe any distro will have to handle; similar but conflicting binaries between, for example, Swift's lldb which gives us REPL functionality, and LLVM's lldb, which does not.
The nice thing about the Ubuntu tarball that Apple provides is that it's the full environment, which can be installed in /usr/local/ or $HOME or wherever and then add the bin to the path and you're off and running. Official distro packages, at least Fedora, follow the Linux filesystem standard and they forbid installation into /usr/local so swift-lang gets installed into the exact same locations as LLVM would.

I've "solved" the issue by patching the Swift source code to look in different locations for things like lldb (in Fedora's case, /usr/libexec/swift-lldb), but this takes a lot of time and every major version of Swift requires anywhere from some to a lot of work to maintain this setup.

If there was anything I'd love to see happen is that 'common' binaries be prepended with swift- so as to allow Swift to be packaged without the aforementioned patches. That would make packaging for other platforms much easier, Fedora, Amazon, whatever.

12 Likes

On Fedora I separate Swift into swift-lang and swift-lang-runtime so that a Linux-based Swift binary can be installed with the minimum number of libraries to make it run; that said yes, there is still a lot of .so files that get installed

3 Likes

This is a good and interesting idea.

Just as a +1, those are the exact issues I encountered when looking into building a Swift package for Arch Linux.

1 Like

I think major support for redhat/centos distributions is important. I don't see my colleagues doing this, they just will pick another language.

1 Like

Personally, I dislike that approach. I think that LLVM and clang are sufficiently complex and large that I would not want to have two versions on my system. This is one of the reasons that I've not yet packaged it up for exherbo either yet I develop on it.

I think that the way to approach this is to help get the changes in the LLVM "fork" merged back into the upstream repositories so that a shared LLVM/clang version can be distributed as part of the system. In the mean time, replacing the system llvm/clang with the Swift version is a possibility, but that means that it cannot be updated as frequently.

I think that pushing the Swift build of the compiler into the LLVM build process and having a single unified build (much like clang) would help ease the transition, both towards pushing the changes upstream as well as getting into a better synchronization cadence with upstream, making this more reasonable for broader Linux distribution.

3 Likes

Just an emphatic +1 on this... been awaiting better Linux portability since it went open source. I run CentOS and would love Swift to be my go-to for most things. Lack of portability has been the only reason it isn’t.

2 Likes