Skip to content

Fastlane

Fastlane is a popular tool for automating the build and release process for iOS, Android and macOS apps. Shorebird can be integrated with fastlane to automate releasing and patching.

Follow the setup instructions on the fastlane website to install fastlane (ios, android). For macOS, follow the iOS instructions from your app’s macos directory.

This section assumes that you are using fastlane on CI to release your app. If you are running fastlane locally (on your development machine), you can skip to the Using fastlane locally section.

A working example of this setup can be found in the fastlane_demo sample.

To get started, follow this guide to set up fastlane with certificate management, code signing, and submitting to the App Store/TestFlight.

Notice that the demo app’s Fastfile has a release_shorebird lane. It uses the shorebird_release action in place of the guide’s build_app to build an ipa and create a Shorebird release, then uploads it to TestFlight with upload_to_testflight.

Note that this change from build_app to shorebird_release is the only change needed to add Shorebird to your fastlane workflow.

To patch your app, you can use the shorebird_patch action. You can see an example of this in the patch_shorebird lane in the Fastfile.

This section assumes that you have Shorebird installed on the machine that will be running fastlane. If you have not already installed Shorebird, you can do so by following the Getting Started instructions.

If you are not already using fastlane with your project, navigate to your app’s ios directory in your project and run fastlane init. You will be prompted to answer several questions. For this guide, choose to manually add lanes to the Fastfile.

Run the following command to install the shorebird fastlane plugin, which exposes the shorebird_release and shorebird_patch actions.

bundle exec fastlane add_plugin shorebird

Open the Fastfile in the fastlane directory and add the following lane:

lane :release_shorebird do
  shorebird_release(platform: "ios")
  upload_to_testflight
end

To run this, execute the following command:

bundle exec fastlane release_shorebird

If you would like to provide additional arguments to the release command, you can do so using the args parameter. For example:

shorebird_release(platform: "ios", args: "--no-codesign -- --build-name=1.0.0")

Open the Fastfile in the fastlane directory and add the following lane:

lane :patch_shorebird do
  shorebird_patch(platform: "ios")
end

This will patch the iOS release whose version matches the one detected in the compiled app.

As with shorebird_release, you can provide additional arguments to the patch command using the args parameter.

shorebird_patch(platform: "ios", args: "--allow-native-diffs -- --build-name=1.0.0")

The shorebird_release and shorebird_patch actions are written for iOS and Android, so these lanes call the Shorebird CLI with sh instead. They assume the Fastfile is in your app’s macos/fastlane directory. Fastlane runs sh and other Ruby code in a lane from the fastlane directory, so File.expand_path("../..") is your Flutter project’s root.

shorebird release macos runs flutter build macos, which builds and signs the app with the signing settings of your Xcode project’s Release configuration, then creates the release. It doesn’t archive or export the app, so Xcode never re-signs it for distribution the way it does when exporting an archive. Set up the Release configuration for how you distribute the app, as described below, before releasing. The signed .app is in build/macos/Build/Products/Release.

To pass arguments to shorebird release or shorebird patch, add them to the sh call, for example sh("shorebird", "release", "macos", "--flavor", "production"). With a flavor, the app is in build/macos/Build/Products/Release-production, so change the Release/*.app glob in the release lanes to match.

The lanes are in a platform :mac block. fastlane init writes a Fastfile for iOS (default_platform(:ios) and a platform :ios block), so either change it to default_platform(:mac) or name the platform when you run a lane:

bundle exec fastlane mac release_shorebird
bundle exec fastlane mac patch_shorebird

The lanes expect your signing certificates in the keychain. If you manage them with match, as the iOS setup above does, each section names the match call to add to the lane before the release step. On CI, call setup_ci before match so it has a keychain to install into.

For an app you distribute yourself, set the Release configuration to sign manually with your Developer ID Application certificate. With match, add match(type: "developer_id", platform: "macos").

Apple’s notary service requires the app to be signed with the hardened runtime and every executable in it to carry a secure timestamp. Xcode adds the timestamp only when it exports an archive, and Flutter signs App.framework and FlutterMacOS.framework without one. So this lane re-signs the app’s frameworks and then the app with both, and notarizes the result with fastlane’s notarize action:

platform :mac do
  lane :release_shorebird do
    project_root = File.expand_path("../..")
    sh("shorebird", "release", "macos")
    app = Dir.glob(File.join(project_root, "build/macos/Build/Products/Release/*.app")).first
    codesign = ["codesign", "--force", "--timestamp", "--options", "runtime",
                "--sign", "Developer ID Application: Your Team (TEAMID)"]
    Dir.glob(File.join(app, "Contents/Frameworks/*")).each { |framework| sh(*codesign, framework) }
    sh(*codesign, "--preserve-metadata=entitlements", app)
    notarize(package: app, api_key_path: File.expand_path("api_key.json"))
  end
end

Replace the signing identity with your own; security find-identity -v lists the identities in your keychain. notarize uploads the app to Apple’s notary service and staples the ticket to it. It takes an App Store Connect API key (api_key_path or api_key; this example reads api_key.json from the fastlane directory) or an Apple ID (username). Package the stapled app however you ship it, for example in a disk image.

A build you can upload to the Mac App Store needs:

  • The Release configuration set to sign manually with an Apple Distribution certificate.
  • A Mac App Store provisioning profile for your app’s bundle ID, selected in the Release configuration. The build embeds it in the app, and App Store Connect rejects a Mac build without one (ITMS-90889).
  • A Mac Installer Distribution certificate, which productbuild uses to sign the package. It shows in your keychain as “3rd Party Mac Developer Installer”.

Automatic signing doesn’t produce this build. With it, the build signs with an Apple Development certificate, because Xcode applies the distribution certificate and profile only when it exports an archive.

With match, add match(type: "appstore", platform: "macos", additional_cert_types: ["mac_installer_distribution"]), which installs both certificates and a profile named match AppStore <bundle ID> macos to select in the Release configuration.

This lane packages the app with productbuild and uploads the package to TestFlight with upload_to_testflight, which leaves your App Store version alone:

platform :mac do
  lane :release_shorebird do
    project_root = File.expand_path("../..")
    sh("shorebird", "release", "macos")
    app = Dir.glob(File.join(project_root, "build/macos/Build/Products/Release/*.app")).first
    pkg = File.join(project_root, "build/macos/App.pkg")
    sh("productbuild", "--component", app, "/Applications",
       "--sign", "3rd Party Mac Developer Installer: Your Team (TEAMID)", pkg)
    upload_to_testflight(
      app_platform: "osx",
      pkg: pkg,
      api_key_path: File.expand_path("api_key.json")
    )
  end
end

Replace the installer identity with your own.

platform :mac do
  lane :patch_shorebird do
    sh("shorebird", "patch", "macos")
  end
end

This patches the macOS release whose version matches the one in the compiled app. shorebird patch macos builds and signs the app with the Release configuration’s signing settings, as the release did, so the machine running this lane needs the same certificate and provisioning profile. With match, add the same match call the release lane uses, with readonly: true, before shorebird patch (after setup_ci on CI). Build the patch the way you built the release: passing --no-codesign to a patch of a signed release makes Shorebird report native code changes that aren’t in your code. A patch needs no re-signing, notarization or upload; Shorebird delivers it to apps already installed from the release.