January 6th, 2019
Filed under: Mac Development | Be the first to comment!
I wanted to run a Ruby tool (packaged as a gem) in a Mac app, but I didn’t want people to have to install the Ruby gem to run my app. I knew I wanted to bundle the gem in my app bundle, but I wasn’t sure how to do so. I finally figured it out so I am sharing what I learned with you. There are five main steps to bundling a Ruby gem in a Mac app and running the gem.
Before you can copy Ruby gems to your app bundle, you must install them on your Mac. The standard gem install
command installs the gem in a location that’s difficult to find. What you should do is install the gems you want to use in a folder you can find easily, such as a gems
folder inside your project folder.
To install the gems into a custom location, use the -i
option and supply the path to the destination folder.
gem install -i /path/to/gem/folder gemName
You must add a Copy Files build phase to your app target that copies the Ruby gem to the app bundle when building the project. Select your project from the project navigator to open the project editor. Select your target from the project editor. Click the Build Phases button at the top of the project editor.
In Xcode choose Editor > Add Build Phase > Add Copy Files Build Phase.
Use the Destination menu to specify where in the app bundle to copy the files. I chose the Resources folder.
If you want to copy the files into a subfolder, enter the name of the folder in the Subpath text field.
Click the Add button to add the gems to the Copy Files build phase. A sheet will open. Click the Add Other button. Navigate to your gem folder location and select the gem folder containing the gems you want to add.
The next step is to locate your gems folder in the app bundle so you can run the gem and set up environment variables to run the gem. You are going to use the Bundle
class. The Bundle
class has accessors to get to standard parts of the bundle, such as the Resources folder.
Once you get to a standard location in the bundle, you must build a path to the folder containing your gems. The following code locates a gems
folder inside the Resources folder:
if let resourceDirectory = Bundle.main.resourceURL {
let pathString = resourceDirectory.path + "/gems"
return URL(fileURLWithPath: pathString)
}
The folder containing the gems should have a bin
folder containing the executable files. You would build a path to the executable file and use that path as the first argument when running the gem.
Before you can run the gem, you must configure the command to call it. Use the Process
class, formerly called NSTask
, to run a command-line program, such as a gem.
Start by creating a Process
object.
let taskToRun = Process()
You must set up the following items:
Use the launchPath
property to set the launch path. For Ruby gems the launch path is the path to the Ruby interpreter.
taskToRun.launchPath = "/usr/bin/ruby"
Use the environment
property to set environment variables. An environment variable is a dictionary. For Ruby gems the environment variable key is GEM_HOME
and its value is the path to your gems folder.
taskToRun.environment = ["GEM_HOME" : gemsPath]
Use the arguments
property to pass arguments to the gem. The arguments
property is an array of strings. The first argument is the path to the executable file of the gem you want to run. Add any arguments the gem requires.
When supplying options as arguments, make sure you have separate entries for the option and its value. Suppose your gem has a -o
option followed by the name of an output file. The -o
is one entry. The name of the output file is a separate entry.
let argumentList = [gemExecutablePath, inputURL.path, "-o", outputURL.path]
taskToRun.arguments = argumentList
The final step is to run the gem. On macOS 10.13 and later, call the run
function to run the task you configured.
do {
try taskToRun.run()
} catch {
fatalError("Error running command line tool.")
}
On earlier versions of macOS, call the launch
function to run the task.
taskToRun.launch()