Bundle a 3rd-Party Binary into an Xcode Project

Adonis Gaitatzis
4 min readJun 9, 2024

--

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:

  1. Copy the binary into the project
  2. Verify the binary is part of the Application Bundle during runtime
  3. 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

Copy binary into 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.

Copy Items if needed to ensure the binary gets built into the Application Bundle

After this, you will see your binary or binaries in your Project structure:

Example binary has been copied into the Project

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:

Show Package Contents

When you select the option to “Show Package Contents,” your application bundle folder is revealed, including your example binary:

Location of the example binary in the Application bundle

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).

Example output of file command on MacOS terminal

--

--

Adonis Gaitatzis
Adonis Gaitatzis

Written by Adonis Gaitatzis

Is a technology nerd who can synthesize any business need into a technology solution. Fun projects have included brain imaging tech, ML, and blockchain.

No responses yet