As a tools developer, it's critical I try to empathize with developers who use the tools I help create. I am probably most intrigued by game developers. My favourite influencers are game developers. They solve some of the hardest problems in our industry and I have a lot of respect for their expertise and their pragmatism that allows them to deliver great software that demands great architectures and performance.
So I guess it wasn't surprising when a video dropped into my stream called "How to Make a 2D Game in C++ and SDL3." It is two and a half hours long and takes you from scratch through animating characters to handling shot collisions. And it's just a cute little game that you can create by hand yourself.
Now, of course, as I work on tools for the Swift package ecosystem, my first question was: where is the "How to Make a 2D Game in Swift and SDL3" video? So I set out to try and replicate this game in Swift and try to get it running on my desktop. And I wanted something that I could try out the Swift SDK for Android. Swift's powerful interoperability with C should make it a great experience.
Well, predictably, at this point in time at least, I quickly ran into hurdles with our tools.
- I could
brew installSDL3 and use a system target to hook it up to my package, or I could grab the release binaries from GitHub and create my own artifact bundle for a binary target. But I really want the experience to be much simpler. You should be able to runswift buildon your package and everything just works. - Once I did get SDL3 hooked up and started using the API, I quickly noticed that SDL3 uses a lot of opaque structure pointers that turn into
OpaquePointers in Swift and you lose the type safety you had in C. I had a great idea of adding a code generator to parse the Swift and parse the C API and generate bindings that replace them. But I'd like to do that all in one multi-language target so I can generate the module map for the C API and layer on the Swift bindings with a simple user experience. - Building that generator reminded me that Swift Syntax prebuilts still only support macros. Build times for code generators that parse Swift code could really benefit if that restriction was relaxed and supported all targets that built for the host.
- And finally, there is the problem of how to generate the APK file for the Android version of the game. I need to take the shared library with the game logic and process it with Android's command line tools to produce the APK. If only build tool plugins had the power to create their own build products for a target...
This use case was inspirational and drove me over the summer and into September to build a proof of concept that solves these issues. That POC is here and I'm really happy how it is turning out: [POC] Custom Targets, External Targets, and Swift Syntax Prebuilts by dschaefer2 · Pull Request #10374 · swiftlang/swift-package-manager · GitHub .
It's a pretty huge change that I will break up into smaller proposals over the next few weeks. The SDL example is in the Examples/swift-sdl folder. It needs SDL checked out next to it at the 3.4.x release. I'll list here the main features and will work on adding details to the design document in the PR so I can start creating evolution proposals.
- The main feature expands build tool plugins to allow them to contribute build products for the targets they are attached to. This is a powerful addition that leverages the work that was done to support plugin tools with Swift Build's CustomTasks and adds a mechanism to specify some or all of the output files as product files that get copied to the Products directory so other plugins can discover and consume them and produce their own artifacts.
- Building on the recent support added for multi-language targets, we add the ability to have targets without any language. I call these CustomTarget in the code but they essentially allow build tool plugins to define all the build products for Swift Build's aggregate targets. This is used by the ApkBuilder in the example.
- To simplify integrating C libraries like SDL, a specialization of CustomTarget is added called ExternalTarget that allows the source for that target to be external to the package. It allows that source to be at a local path, or a remote source archive SwiftPM fetches for you, or a git repo that SwiftPM will check out for you. You would then add build tool plugins that manage the build of that source. In SDL's case that's a plugin that runs the CMake configure and uses Ninja to build a static version of the SDL library. For Android, it also produces a jar file with its Android Java native bridge, both of which get added to the build products for the target.
- It occurred to me that these mechanisms would also really help with Swift Syntax prebuilts. The prebuilts are just a static library that bundles all products in Swift Syntax. These could be modelled as CustomTargets as well and starts us down the road of figuring out how to support prebuilt binaries more generally.
- And it reminded me I need to graduate the
experimentalCGenfeature and expand it to let plugins generate mixed language source for mixed language targets. This is used by the SwiftSDL target that produces the module map for the SDL3 library as well as an API notes file that facilitates the Swift bridge to wrap some of the C functions to use safer types but use the same function names.
I encourage you to take a look at the example and get a feel for the ergonomics. I've done a number of iterations on the ideas to make them feel easy to learn and simple to understand, but I could really use more eyes and fingers to ensure we're making something developers will love to use. It was one of those "woo hoo" moments when the app first built and ran with a Package manifest that felt good. As simple as it is, it just paints the window the Swift orange, it was a great feeling to see that orange window open on my desktop and then launch the same app on Android. Now all I need is to get swift run to just work everywhere too
.

