When you build a Swift Package Xcode will compile all your
.swift files to single executable file and process all other resources which includes Assets catalogue, Core Data files, Storyboards and many more including all
.metal files. All these resources will be put in package Bundle and you have access to them through
Bundle.module if you add them to
resources array in package’s target.
But it will work only for
library product type. If you want to build a Swift
executable product resources won’t be processed or copied to Bundle because there is no actual Bundle there. So Swift executable is limited to only
.swift files so you can’t build executable which will run you Metal shaders. Let’s try to fix it!
Metal allows offline library compilation and library initialisation from URL with
MTLDevice instance method
This method requires a valid URL to
.metallib file so we need a way to build Metal Library from our
.metal files, put it somewhere near our executable file and create library on the fly from it.
Let’s do it using shell script! You can do it manually on you Mac or run shell script on your CI assuming that CI is also a Mac server.
You can get the whole script here or go step by step with me.
Create intermediate representation from Metal files
First of all we need to locate all our
.metal files and create
.air file from each of them
FILE_NAME here will be the name of Metal file but without
xcrun command will build
.air file from each
.metal file and put in the same folder where all your Metal files exist. We will remove them after we compile the whole library.
If you want to put
.air files somewhere else just update
-o flag to
Also if you have only one Metal file (assume it is named
Shaders.metal) you can omit for-loop and run only
Archive AIR files to single MetalAR file
The next step will be to archive all
.air files to single
.metalar file which will be used to compile the
.metallib file later on. You can also omit this step if you have a single
.metal file which you already compiled to single
.air file. The Metal Library can be created from
.metalar file it doesn’t matter.
This step will look pretty like the previous but we will iterate through
.air files this time
default.metalar name here like Xcode do when compiles Metal Library during package or app building. But you can use the name more appropriate for you of course. And you can put it anywhere you want if you replace
metal-ar is the name of the tool which will archive
.air files and
r command will add the second file (
$path in our case) to the first one and create it if needed.
So now we have a single
.metalar file with all our Metal shaders in intermediate representation. Let’s build a Metal Library file from it.
Build Metal Library from MetalAR archive
This step is pretty straightforward. Again we need to run
xcrun command to do the thing
So now we have a
.metallib file which is a good candidate to create
MTLLibrary from. But the last but not least step is to be good citizens and clean up the shaders folder where we created all our intermediate files.
Create MTLLibrary from
When you archive you Swift executable Xcode will export it as a folder with your single executable file in
Products/usr/local/bin folder inside. You can move it anywhere you want and run it from shell using path to executable file or if you add the executable location to
$PATH you can run it from anywhere in your system. But we need a way to tell the executable file where our
.metallib is located.
There are two ways to do it. If you create Swift executable you are already familiar with Apple’s Swift Argument Parser framework which is must-have dependency for Swift executables. So you can create an
@Option for Metal Library file location which will be required for you script or if you build an executable for internal usage you can assume that
.metallib file will be placed near your executable file and create library from it.
There is one thing you need to notice if you use the second approach. If you use
in your Swift code it will return you not the executable’s location but the current location from where you run your executable. So if you open the Terminal and run
your current directory will be your user’s home directory and
FileManager won’t be able to locate
.metallib file which is placed near your executable but not in your home folder.
To get access to executable’s location you need to get process info from inside your
run() method in Swift file. So the valid approach to locate
.metallib file which is placed near executable will be
execURL here will be the full path to executable itself so we need to remove its name from the URL to get the path where it is located and where we put our
Now you’re not limited by Swift only files inside Swift executable and can build an executable which can run Metal Shaders right from the command line.
I personally used this flow to create the Swift Package with two products. The first one is a plain old Swift library which I can add as dependency to iOS apps and the second one is executable which has the first library as dependency and can be used to run the whole library functionality from command line. We use it to simplify library tests and to make the library available to run from Python scripts for example.
Here is the gist with all the previous shell code. I hope it will help you in creating awesome Swift packages with CLI availability.
If you have any questions feel free to shoot me an email to firstname.lastname@example.org or connect via Twitter @petertretyakov.