[PROPOSAL] Tuist Fastlane Plugin

This proposal outlines the rationale for creating a Tuist Fastlane Plugin.

Fastlane is a widely used automation tool in iOS development, supporting a plugin system that allows developers to extend its functionality. Given Tuist’s role in extending Apple tooling, it makes sense to integrate it as a Fastlane plugin.

Currently, developers who want to use Tuist via Fastlane must manually wrap Tuist command executions in Fastlane’s shell lane, like this:

sh(command: "tuist", "test", "App")

This approach lacks documentation and requires developers to remember Tuist commands and their parameters, leading to a suboptimal development experience (DX). A dedicated Tuist Fastlane Plugin would significantly improve DX and promote wider adoption of Tuist.

Tuist Fastlane Plugin

Plugin APIs

The Tuist Fastlane Plugin will be optimized for CI use, meaning user-interactive commands will not be supported.

The plugin will expose the following Tuist commands:
• build
• clean
• generate (always executed with the --no-open flag)
• install
• test
• cache, auth, and logout (to support caching features)
• plugin

Each command will be represented as a separate Fastlane action. For example:

tuist_install(
  path: "/path/to/project", # optional  
  update: true # optional  
)

Tuist CLI Installation

Since Fastlane plugins are distributed as Ruby gems, the plugin cannot automatically install command-line tools. Therefore, developers must install the Tuist CLI on their machines. Tuist can be installed using mise (recommended), Homebrew, or a script.

The plugin will check if Tuist is installed and provide an early error message if it is missing.

Alternatives Considered:

  1. Bundling the Tuist CLI:
    • This would package Tuist with the plugin, but it would require frequent updates, one for each new Tuist release (e.g., the Sentry Fastlane plugin uses this approach).

  2. Allowing developers to specify the Tuist version:
    • Since Tuist is installed globally and tied to the project directory, requiring the binary location for each lane would degrade the DX.

Action Plan

  1. Create a fastlane-plugin-tuist repository under the Tuist organization.
  2. Implement the fastlane-plugin-tuist.
  3. Distribute the plugin via RubyGems.
  4. Automate the distribution process.

Summary

The Tuist Fastlane Plugin aims to streamline the use of Tuist within Fastlane, enhancing the developer experience and encouraging broader adoption. By offering well-documented and intuitive Fastlane actions for core Tuist commands, the plugin will simplify automation in CI pipelines. With a clear installation process and thoughtful design choices, this plugin will bridge the gap between Tuist and Fastlane for iOS developers.

What do you think if the lane takes care of pulling the binary instead of making that a user responsibility? I see the value of the lane two-fold:

  • Make it convenient to install the tool by aligning it with your existing bundle install.
  • In this case, invoking it from your automation layer, Fastlane, makes it convenient.

What I’ve seen other tools doing, for example, in the web ecosystem, is pulling the binaries into a conventional path, such as ~/.cache/fastlane_tuist, where ~/.cache is defined by the environment variable XDG_CACHE_HOME.

If we do the above, then lanes could take the version as another keyword argument:

# If not provided, it uses the latest available version.
tuist_install(
  version: "4.3.2"
  path: "/path/to/project", # optional  
  update: true # optional  
)

Versioning

I wonder which versioning schema we should follow in this case. Part of me likes the idea of not having to push a new Gem version every time we release a new version of Tuist, because that makes our release process less atomic and more complex to reason about. If we did so, we wouldn’t need version: because that information would be obtained from the Gem version itself. If we didn’t, then I wonder if the versioning of the Gem will be confusing for people because they’ll expect the version of the Gem to match the version of Tuist that the lane in the Gem will use.

Thoughts?

Thanks a lot @mollyIV for writing this up!

I do think we should align the versions. The release process of the gem could be automated, so the overhead of the extra Gem release for maintainers should be low/none.

If we do the above, then lanes could take the version as another keyword argument:

Would that still make sense in case we decide to align the Gem and the CLI versions?

One thing that I find missing in the RFC proposal is what checks will we put in place, so that the Fastlane plugin command don’t lag behind the latest state in Tuist? Do you think it would be possible to generate the Ruby commands based on the Argument parser definitions? If not, then we should consider a dedicated CI check to remind authors of PRs to update the Fastlane plugin when they update the CLI commands.

If we align the versions we don’t.

What do you think if the lane takes care of pulling the binary instead of making that a user responsibility?

It’s quite unusual to install CLIs via Ruby gems in the Fastlane ecosystem. For example, the carthage Fastlane action assumes Carthage is already installed on the local machine.

What I’ve seen other tools doing, for example, in the web ecosystem, is pulling the binaries into a conventional path

If we wanted to provide a binary, I’d suggest bundling the Tuist CLI. This would package Tuist with the plugin. Please refer to the Sentry Fastlane plugin example.

We would optimize it for the CI environment. To make the plugin work, users would just need to install the Tuist Fastlane Plugin.

The downside is frequent updates if we want to keep the plugin in sync with Tuist CLI releases. However, updates could be managed through automation.

There could also be an inconvenience when running Tuist interaction commands and the Tuist Fastlane Plugin on a local machine. It’s important to ensure the plugin uses the same version of Tuist as the one used locally.

I wonder which versioning schema we should follow in this case.

My initial idea was to version the plugin and Tuist CLI separately, meaning they would be independent of each other since the plugin would require a local Tuist installation.

Right. We’d incorporate releasing the Fastlane lane to the release pipeline, so we don’t have to worry about it.

I’d add a note about version consistency somewhere in the docs. I wonder how likely it’ll be that people use Tuist through fastlane in CI and directly (e.g., installed via Homebrew) in the local environment.

What about the lane being pass-through so we automatically map Ruby arguments and keyword arguments into CLI flags and pass them. The caveat to this is that the Fastlane lane won’t have documentation, but we can link it to the documentation of the CLI.

For example:

tuist(:install, path: "...")

# Maps to tuist install --path ...

Is there any benefit of using the Fastlane plugin if all the arguments will be passthrough? My understanding is that the explicit parameters would be the primary value of the Fastlane plugin.

It’s an extra layer of convenience over how the tool is installed and interacted with. If an organization/developer has already gotten familiar with bundle install and calling a tuist lane, that’s a mental-model that we’d like to be aligned with. Can’t they achive the same by doing brew install/mise install and shelling out with process APIs in Ruby? 100%. But assuming most people automating with Fastlane might not be very versed at Ruby and its standard library, I’d decorate the installation and call.

We can go the extra mile of mapping the CLI schema to a Ruby interface, but we are stepping into code-generation, an schema that might introduce breaking changes… I dunno. Users would gain Fastlane validation of arguments? The alternative would be to defer that validation to the CLI and forward the stderr back?

One thing that I find missing in the RFC proposal is what checks will we put in place, so that the Fastlane plugin command don’t lag behind the latest state in Tuist?

Good question!

Keeping the plugin and Tuist releases in sync reminds me of the problem of keeping Fastlane Swift interface and Fastlane releases in sync. They solved this by placing the Fastlane Swift project in the same monorepo as Fastlane itself. However, I don’t think we should do that in the case of Tuist, because it pollutes the main repo and makes things harder to reason about.

Do you think it would be possible to generate the Ruby commands based on the Argument parser definitions?

Yes, I think it could be possible with a custom script. The question is, would it be worth the effort? Tuist command APIs probably won’t change frequently, so updates should be rare.

we should consider a dedicated CI check to remind authors of PRs to update the Fastlane plugin when they update the CLI commands

I think we could start with this. We could have a check triggered when a specific Tuist command source file is modified, which would prompt the author to open an issue on the Tuist Fastlane Plugin repo to update the plugin interface.

Is there any benefit of using the Fastlane plugin if all the arguments will be passthrough?

I don’t think so. IMHO, the main point of the Tuist Fastlane Plugin would be to improve the development experience and it means providing nice APIs.

I wonder how likely it’ll be that people use Tuist through fastlane in CI and directly (e.g., installed via Homebrew) in the local environment.

This use case applies to teams managing Fastlane lanes and CI workflows. It also covers situations where developers run CI automation locally to generate a build for QA testing or debug a failed CI build.

That was my understanding as well. The installation of tuist alone is not worth abstracting into a Fastlane plugin.

Then I think it would be worth exploring whether we can generate the Ruby API based on the ArgumentParser definition. Such a utility could be a nice gem in and of itself :wink:

I started working on a proof of concept (POC) and tried including the Tuist binaries. Unfortunately, the Tuist binary is quite large (~109 MB), leading to this GitHub error:

remote: error: File bin/tuist is 103.99 MB; this exceeds GitHub’s file size limit of 100.00 MB

Including Tuist binaries in the plugin doesn’t seem feasible.

How do other fastlane plugins solve this? Do they not include the binary and assume it’s installed in the environment? Or is there a way to include the binary as part of the Gem artifacts? The binary could be downloaded from GitHub releases.

How do other fastlane plugins solve this?

I talked about the common ways to install stuff in the Tuist CLI Installation section. Some plugins include the binaries right in the plugin (like the Sentry Fastlane plugin), while others just assume you’ve already installed the tool.

TBH, most plugins just make some URL requests. You don’t really see many that actually wrap a CLI.

The binary could be downloaded from GitHub releases.

Hmm, this option could be worth exploring.

I’ve got something working. If you want to play with the POC, please download a demo project at:

Then run:

$ mise install
$ bundle install
$ bundle exec fastlane foobar

You should see tuist generate in action for the project:

...

[16:10:06]: Driving the lane 'ios foobar' 🚀
[16:10:06]: ----------------------------
[16:10:06]: --- Step: tuist_generate ---
[16:10:06]: ----------------------------
[16:10:06]: Using tuist 4.34.3
[16:10:06]: Loading and constructing the graph
[16:10:06]: It might take a while if the cache is empty
[16:10:07]: Using cache binaries for the following targets:
[16:10:07]: Generating workspace App.xcworkspace
[16:10:07]: Generating project App
[16:10:07]: Project generated.
[16:10:07]: Total time taken: 0.343s

+---------------------------------------+
|           fastlane summary            |
+------+------------------+-------------+
| Step | Action           | Time (in s) |
+------+------------------+-------------+
| 1    | default_platform | 0           |
| 2    | tuist_generate   | 0           |
+------+------------------+-------------+

[16:10:07]: fastlane.tools finished successfully 🎉

The tuist binary is being downloaded as part of gem installation.

Here’s the source code of the plugin:

How did you end up including the binary?

The trick is to download a binary during the gem installation process.

This script runs automatically when the gem is installed, ensuring the binary is set up without manual intervention.

You specify a script in spec.extensions in a gemspec file.

Smart! Nokogiri must be doing something along those lines.

What should be the criteria for considering the plugin that is generally available? Here are some ideas:

  • Implementation complete
  • Some integration tests?
  • The versioning is integrated into the current release process.
  • The Tuist documentation has been updated to reflect that developers can use the Fastlane plugin.

I wonder if we should implement interface-generation from the CLI schema before we release it or start with a pass-through version of it and then invest in that based on the input from the community.

I’d also suggest that we move it to the Tuist organization on GitHub. Please, let me know if you’d have any concern with this one.

Good question! I’m leaning towards a “pass-through” approach—deliver something first, get user feedback, then iterate and improve.

Also, I think some Tuist actions (especially interactive ones) shouldn’t be part of the plugin, like tuist edit.

About the CLI schema—can we generate one for the Tuist CLI? It could be super useful for spotting public API changes.

I think for the first iteration, that’s a good approach :+1: One thing I wonder is for organizations that would use Mise and this Fastlane plugin, the Tuist version would be in two places. I wonder if eventually there’s at least some warning that we can output when those two mismatch.

About the CLI schema—can we generate one for the Tuist CLI? It could be super useful for spotting public API changes.

You can run tuist --experimental-dump-help as documented here. This is something we should be able to use to derive the commands and their arguments in an automated manner instead of having to keep the two up-to-date manually.