Developing a Swift command-line tool that runs on both Intel and M1 Macs can be a challenging task, especially when you consider the differences in architectures between these two types of Macs. Thankfully, Apple’s Universal Binary technology can help you achieve just that. In this blog post, we’ll walk through the process of creating a Universal Binary for a Swift Package Manager (SPM) command-line tool, which can run on both Intel (x86_64) and M1 (arm64) Macs.
Step 1: Building for Different Architectures
Make sure you have Xcode and its command-line tools installed on your machine. Open Terminal and navigate to your Swift project directory.
To create a Universal Binary, we need to build our Swift tool for both the arm64
and x86_64
architectures. Thankfully, with the latest versions of Swift, we can achieve this with a single swift build
command:
arm64:
swift build --configuration release --arch arm64
x86_64:
swift build --configuration release --arch x86_64
Step 2: Creating a Universal Binary
Having built the tool for both architectures, the next step is to use the lipo
command to combine the two into a Universal Binary. You would do this by running the following command in your terminal:
lipo -create -output "YourTool" ".build/arm64-apple-macosx/release/YourTool" ".build/x86_64-apple-macosx/release/YourTool"
Replace "YourTool"
with the name of your Swift command-line tool. This command combines the arm64 and x86_64 versions of your tool into a single Universal Binary that can run on both architectures.
You can confirm that the Universal Binary was created successfully by running:
lipo -info YourTool
If successful, this command will output something like:
Architectures in the fat file: YourTool are: x86_64 arm64
Step 3: Distributing Your Tool
Now that you’ve created a Universal Binary, the next step is distribution. One of the most popular methods of distributing command-line tools for macOS is through Homebrew. To distribute your tool through Homebrew, you’d need to create a Homebrew formula.
Before you can create a formula, however, you’ll need to host your binary somewhere. A good place to host your binary is on GitHub as part of a release. After uploading your binary to a new release on GitHub, you can create a Homebrew formula that references the binary directly.
Here’s a sample Homebrew formula:
class YourTool < Formula
desc "Your tool description"
homepage "https://github.com/yourusername/YourTool"
url "https://github.com/yourusername/YourTool/releases/download/v0.2.3/YourTool"
sha256 "sha256-of-the-binary"
def install
bin.install "YourTool"
end
test do
system "#{bin}/YourTool", "--version"
end
end
In this formula, replace the desc
, homepage
, url
, and sha256
with the appropriate values for your tool.
Remember to update the sha256 field in the formula with the new SHA-256 hash of your binary file. This can be calculated by running
shasum -a 256 /path/to/your/YourTool
in the terminal. This hash ensures the integrity of the downloaded file during the Homebrew installation process.
Once you’ve created your formula, users can install your tool with a simple brew install YourTool
command, regardless of whether they're on an Intel or M1 Mac.
Conclusion
Creating a Swift command-line tool that supports both Intel and M1 Macs might seem daunting at first, but with Apple’s Universal Binary technology, it’s relatively straightforward. By building your tool for each architecture and combining them with the lipo
command, you can ensure compatibility across different Mac models.
In this guide, we’ve walked you through creating a Universal Binary for a Swift command-line tool and distributing it via Homebrew. Whether you're building a brand-new tool or enhancing an existing one, this tutorial should assist you in ensuring your tool reaches a wider audience of Mac users.