Writing fastlane plugins in Swift
Writing fastlane plugins in Swift
Let’s say that you want to create a custom fastlane plugin but you don’t want
to stop using Swift. Since fastlane currently do not support plugins for Swift
in any form we will have to do all the heavy lifting ourselves. But it’s not
that difficult. As an example we are just going to make a simple swift library
that uses PersonNameComponentsFormatter
to format names and expose that to
fastlane.
1. Creating the plugin
Start by creating and initializing the plugin by running
fastlane new_plugin person_name_formatter
fastlane add_plugin
This generates all the relevant files you will need for your plugin. Make sure to also initialize a git repository for this folder.
2. Creating the Swift library
In the root folder of your new plugin. Create a new folder named SwiftActions
.
Here run swift package init --type library
. This will generate everything
that you need to compile your swift library.
Edit your Package.swift
file and make sure that you compile it to a dynamic
library. Your file should look like this:
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "SwiftActions",
products: [
.library(
name: "SwiftActions",
type: .dynamic, // <- Important!
targets: ["SwiftActions"]),
],
dependencies: [],
targets: [
.target(
name: "SwiftActions",
dependencies: []),
.testTarget(
name: "SwiftActionsTests",
dependencies: ["SwiftActions"]),
]
)
Now you can add the functionality you want in your plugin in SwiftActions/Sources/SwiftActions/SwiftAction.swift
Keep in mind that you can only interop with ruby via the C api. So you will need to use C-compatible types.
import Foundation
public typealias CString = UnsafePointer<CChar>
// Declare what the function name will be in C
@_cdecl("format_name")
public func format(name sName: CString) -> CString {
let name = String(cString: sName)
if #available(macOS 10.12, *) {
let formatter = PersonNameComponentsFormatter()
guard
let components = formatter.personNameComponents(from: name)
else {
return ("" as NSString).utf8String!
}
formatter.style = .abbreviated
return (formatter.string(from: components) as NSString).utf8String!
}
return ("" as NSString).utf8String!
}
3. Update the Rakefile
Now we will have to add a build step in our ruby build system. Edit the Rakefile and add the following build step.
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
require 'rubocop/rake_task'
RuboCop::RakeTask.new(:rubocop)
task(:build_swift) do
# Build SwiftActions
Dir.chdir('SwiftActions') do
`swift build --configuration=release`
end
# Move the dynamic library to the `bin` folder.
FileUtils.mkdir_p('./bin')
FileUtils.cp('./SwiftActions/.build/release/libSwiftActions.dylib', './bin/')
end
# Build the swift project before doing anything else.
task(default: [:build_swift, :spec, :rubocop])
If you run bundle exec rake
it should now build your swift library and
move it to bin/libSwiftActions.dylib
.
4. Update the action
First we will have to include the gem fiddle
. In fastlane-plugin-person_name_formatter.gemspec
add the line spec.add_dependency('fiddle')
and install by running bundle install
.
The last piece of the puzzle is to update our actual fastlane action.
in the file lib/fastlane/plugin/person_name_formatter/actions/person_name_formatter_action.rb
add the following:
require 'fastlane/action'
require_relative '../helper/person_name_formatter_helper'
require 'fiddle/import'
module Fastlane
module SwiftActions
extend Fiddle::Importer
dlload './bin/libSwiftActions.dylib'
extern "char* format_name(char *)"
end
module Actions
class PersonNameFormatterAction < Action
def self.run(params)
formatted = SwiftActions.format_name(params[:name])
UI.message("Hi #{formatted}")
formatted
end
def self.description
"Formats a persons name"
end
def self.authors
["Niil Öhlin"]
end
def self.return_value
"Returns the name abbrivated"
end
def self.details
""
end
def self.available_options
[
FastlaneCore::ConfigItem.new(key: :name,
description: "Name to format",
optional: false,
type: String)
]
end
def self.is_supported?(platform)
true
end
end
end
end
You can now try to run your action by running
$ bundle exec fastlane run person_name_formatter name:'Niil Öhlin'
[✔] 🚀
+---------------------------------------+---------+-----------------------+
| Used plugins |
+---------------------------------------+---------+-----------------------+
| Plugin | Version | Action |
+---------------------------------------+---------+-----------------------+
| fastlane-plugin-person_name_formatter | 0.1.0 | person_name_formatter |
+---------------------------------------+---------+-----------------------+
[18:39:18]: -----------------------------------
[18:39:18]: --- Step: person_name_formatter ---
[18:39:18]: -----------------------------------
[18:39:18]: Hi NÖ
[18:39:18]: Result: NÖ
Damn that’s a lot of work. But it works!