End of April Johannes Weiß added custom toolchain support to Swift Package Manager. Johannes also provided a script which shows how to build an Ubuntu toolchain for x86-64. So what we did is take that script and make it produce a Swift 3.1.1 cross compiler toolchain for the Raspberry Pi (armhf) Ubuntu Xenial port.
What this is good for? You can build Raspberry Pi Swift binaries on a Mac. Like this:
mkdir helloworld && cd helloworld swift package init --type=executable swift build --destination /tmp/cross-toolchain/rpi-ubuntu-xenial-destination.json file .build/debug/helloworld .build/debug/helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
(We also have a toolchain kit which does the reverse, compile macOS Swift binaries on a Raspberry Pi: macos)
The tested and working version is still Swift 3.1 w/ 32-bit Raspi Ubuntu. However, Swift 4.1 builds start popping up, so we are working on
This was blocked because SPM 4.1 b0rked support for custom target triples (PR-1546). And it still is, even Swift 4.2 won't ship this.
Building the ARM toolchain
What we are going to do is build a Swift 3.1.1 cross compilation toolchain for ARM Ubuntu Xenial. Note that we are going to use the Swift 4 package manager, but the Swift 3.1 compiler (because Swift 4 on Raspi is not in shape yet).
- Xcode 9 or later (http://developer.apple.com/)
- a Raspi 3 w/ Ubuntu Xenial
Build Toolchain using Script
First download our script and make it executable: build_rpi_ubuntu_cross_compilation_toolchain, e.g. like:
pushd /tmp curl https://raw.githubusercontent.com/AlwaysRightInstitute/swift-mac2arm-x-compile-toolchain/master/build_rpi_ubuntu_cross_compilation_toolchain \ | sed "s/$(printf '\r')\$//" \ > build_rpi_ubuntu_cross_compilation_toolchain chmod +x build_rpi_ubuntu_cross_compilation_toolchain
Next step is to download Swift 3.1.1 tarballs. We need the macOS pkg for the host compiler and a Raspberry Pi tarball for the Swift runtime. We use the one provided by Florian Friedrich for the latter:
pushd /tmp curl -o /tmp/swift-3.1.1-armv7l-ubuntu16.04.tar.gz https://cloud.sersoft.de/nextcloud/index.php/s/0qty8wJxlgfVCcx/download curl -o /tmp/swift-3.1.1-RELEASE-osx.pkg https://swift.org/builds/swift-3.1.1-release/xcode/swift-3.1.1-RELEASE/swift-3.1.1-RELEASE-osx.pkg
Those are a little heavy (~400 MB), so grab a
pushd /tmp ./build_rpi_ubuntu_cross_compilation_toolchain \ /tmp/ \ /tmp/swift-3.1.1-RELEASE-osx.pkg \ /tmp/swift-3.1.1-armv7l-ubuntu16.04.tar.gz
If everything worked fine, it'll end like that:
OK, your cross compilation toolchain for Raspi Ubuntu Xenial is now ready to be used - SDK: /tmp/cross-toolchain/rpi-ubuntu-xenial.sdk - toolchain: /tmp/cross-toolchain/swift.xctoolchain - SwiftPM destination.json: /tmp/cross-toolchain/rpi-ubuntu-xenial-destination.json
Use the Toolchain
Lets create a simple
helloworld tool first:
mkdir helloworld && cd helloworld swift package init --type=executable swift build --destination /tmp/cross-toolchain/rpi-ubuntu-xenial-destination.json
Compile Swift Module 'helloworld' (1 sources) Linking ./.build/debug/helloworld
Check whether it actually produced an ARM binary:
file .build/debug/helloworld .build/debug/helloworld: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, not stripped
Excellent! It worked. Now either copy your binary to a Raspi or test it using QEmu as described below.
If you are not using our
you may need to setup the
LD_LIBRARY_PATH, so that the dynamic linker finds
the Swift runtime. E.g. like that:
sudo cat > /etc/ld.so.conf.d/swift.conf <<EOF /usr/lib/swift/linux /usr/lib/swift/clang/lib/linux /usr/lib/swift/pm EOF sudo ldconfig
If that didn't work, you'll see an error like:
./helloworld: error while loading shared libraries: libswiftCore.so: \ cannot open shared object file: No such file or directory
Testing builds using Docker on macOS
Docker for Mac comes with QEmu support enabled, meaning that you can run simple ARM binaries without an actual Raspberry Pi.
docker run --rm --tty -i -v "$PWD/.build/debug/:/home/swift" \ helje5/rpi-swift:3.1.1 \ ./helloworld
This works for simple builds, more complex stuff does not run in QEmu. Use a proper Pi for that :-)
No README w/o
🐄 🐄 🐄
Lets build something very useful, an ASCII cow generator.
swift package init produces a Swift 4 setup by default.
We want to use 3.1, so we do the setup manually:
mkdir vaca && cd vaca cat > Package.swift <<EOF import PackageDescription let package = Package( name: "vaca", dependencies: [ .Package(url: "https://github.com/AlwaysRightInstitute/cows.git", majorVersion: 1, minor: 0) ] ) EOF cat > main.swift <<EOF import cows print(vaca()) EOF
Then build the thing:
swift build \ --destination /tmp/cross-toolchain/rpi-ubuntu-xenial-destination.json Fetching https://github.com/AlwaysRightInstitute/cows.git Cloning https://github.com/AlwaysRightInstitute/cows.git Resolving https://github.com/AlwaysRightInstitute/cows.git at 1.0.1 Compile Swift Module 'cows' (3 sources) Compile Swift Module 'vaca' (1 sources) Linking ./.build/debug/vaca
And you get the most awesome Swift tool:
docker run --rm --tty -i -v "$PWD/.build/debug/:/home/swift" \ helje5/rpi-swift:3.1.1 ./vaca (___) (o o) __\_/__ //^^*^^\ / * \ / | * | \ \ |=====| / "|_____|" | | | | | | |_|_| ^ ^ COWNT
Notes of interest
#if swift(>=4.0)will be true. Seems like a bug in Swift that the version is attached to the driver, not to the active language. In other words:
#if swiftversion checks are useless. Same BTW for
#if oschecks in the Package.swift. Those do not account for cross compilers.
- SwiftPM reuses the
.builddirectory even if you call it w/ different destinations. So make sure you clean before building for a different architecture.
- Ubuntu system headers and such for the toolchain are directly pulled from the Debian packages (which are retrieved from the Ubuntu repository)
- The cross compiler is just a regular clang/swiftc provided as part of a macOS toolchain. Yes, clang/swift are always setup as cross compilers and can produce binaries for all supported targets! (didn't know that)
- To trace filesystem calls on macOS you can use
sudo fs_usage -w -f pathname swift(I only knew
swift build --static-swift-stdlibdoes not seem to work. Presumably because the Swift 3 host compiler does not support the necessary flags.
There is the swift-arm Slack channel if you have questions about running Swift on ARM/Raspberry Pi.