Bundle a 3rd-Party Binary into an Xcode Project
Sometimes in a MacOS project, you want to run 3rd party binaries or custom integrations that were written in a language other than Swift. Especially with open source or legacy projects, there may be tools that run on MacOS but can’t be called directly from a Swift API as an Framework.
In these cases, it is useful to bundle the binary into the Application and to execute it as a standalone app from within the App UI. This allows developers to create a single application from several constituent parts without expecting end-users to download and manage versions of dependent software like some sort of 90’s-era Linux guru.
Doing this is easy. We simply need to:
- Copy the binary into the project
- Verify the binary is part of the Application Bundle during runtime
- Do something with the binary (for example executing it)
Copy the Binary Into the Project
The first step assumes you already have a pre-compiled binary in your project.
If you are only supporting Silicon chips, great. You only need to copy one binary into the project. If you are supporting Silicon and Intel chips, you should name them in a way that you can easily identify, for example examplebin.arm64.bin
and examplebin.x86_64.bin
so that you know which one you are working with at runtime.
Note that Intel binaries will run on Silicon architecture automatically thanks to Apple’s Rosetta 2 real-time binary translation engine.
Extra points if you put them in a folder to help keep the project structure organized.
Drag and drop your binary or binaries into your project
When you drop your binary, a dialog will open asking if you want to copy the items if needed. Select this option so that the binary will be copied into the project and ultimately into the finished Application bundle at build-time.
After this, you will see your binary or binaries in your Project structure:
Verify the Binary is in the Application Bundle
When compiled, the binary will be located in your App bundle, in the Contents/Resources
folder, meaning that if you right click (or CMD-click) on your Application, and select “Show Package Contents”, you will find the folder structure within your Application Bundle:
When you select the option to “Show Package Contents,” your application bundle folder is revealed, including your example binary:
Therefore, if you want to run this binary from Swift, you must first locate its path, by locating the top level Application bundle path, appending the path to the example binary, then checking if the file exists.
We import the Foundation library to access the FileManag
and other information about the Bundle:
import Foundation
func locateExampleBinary() -> String? {
guard let bundlePath = Bundle.main.bundlePath as NSString? else {
return nil
}
let binaryPath = bundlePath.appendingPathComponent("Contents/Resources/examplebin")
return FileManager.default.fileExists(atPath: binaryPath) ? binaryPath : nil
}
If you are supporting both Intel and Silicon binaries, you may need to check which architecture is running to decide which binary to work with. You can check the machine architecture like this:
func getSystemArchitecture() -> String {
let machineArchitecture = "x86_64"
#if arch(arm64)
return "arm64"
#else
return "x86_64"
#endif
}
Do Something With the Binary
Later you can run that binary if you know it exists, you can Run the process from Swift.
@main
struct RunCliAppApp: App {
runExampleBinary() {
if let exampleBinaryPath = locateExampleBinary() {
// do something with the binary, for example running it
let task = Process()
task.launchPath = exampleBinaryPath
task.launch()
task.waitUntilExit()
} else {
// handle error: binary not found
perror("Example binary not found in application bundle!")
}
}
}
If your binary is compiled for Intel but your application supports Silicon, MacOS will automatically run the binary through the Rosetta 2 translation system, so the binary will run as if it were compiled for Silicon.
However this doesn’t work the other way around. If your binary is compiled for Silicon and your application is running on Intel, the binary will not execute.
To determine the architecture of your binary, you can use the file
command, for example:
$ file examplebin
examplebin: Mach-O 64-bit executable arm64
The file
program reports the binary compile format for any MacOS app, including arm64, x86_64, and universal (both arm64 and x86_64).