From Cocoapods to SPM: Automate Versioning with Fastlane's New Action

If you are a Cocapods framework maintainer or SPM library creator this article is for you! Switching from Cocoapods to Swift Package Manager is inevitable, more and more third-party solutions are making such a switch. Some of us use Fastlane to automate testing, building, and releasing our work.

When it comes to releasing, Cocoapods requires three actions:

  1. Changing version in .podspec file
  2. Creating new tag matching podspec version
  3. Registering a new version with Podspec Repo (public or private)

SPM simplifies the process, there is no version in Package.swift, no repository of existing libraries, only git tag counts.

OK, so how does it affect the release process? For Cocoapods, we could rely on existing fastlane actions, like version_get_podspec or version_bump_podspec to get a new value for our release.

SPM on the other hand, doesn't have such easy-to-use actions. This is why I created new Fastlane Action, that can handle obtaining a new version using the last git tag!

To create new Fastlane Action, run [bundle exec] fastlane new_action. Creator will ask for a name, we can use get_new_version. The automated process will create a file structure to accommodate our new code and will return the path to the new action file.

Open ./fastlane/actions/get_new_version.rb with your favorite code editor and replace its content with this implementation:

module Fastlane
  module Actions
    class GetNewVersionAction < Action
      def self.run(params)
        last_tag = other_action.last_git_tag
        if last_tag.nil?
          UI.message("Returning initial tag")
          return '0.1.0'
        end

        major, minor, patch = last_tag.split('.').map(&:to_i)

        if params[:bump_major]
          new_major = major + 1
          new_tag = "#{new_major}.0.0"
        elsif params[:bump_minor]
          new_minor = minor + 1
          new_tag = "#{major}.#{new_minor}.0"
        elsif params[:bump_patch]
          new_patch = patch + 1
          new_tag = "#{major}.#{minor}.#{new_patch}"
        end

        UI.message("New tag calculated: #{new_tag}")
        return new_tag
      end

      def self.description
        "Calculate the new tag by incrementing the last tag's major, minor, or patch version by one"
      end

      def self.authors
        ["Artur Gruchala"]
      end

      def self.is_supported?(platform)
        true
      end

      def self.available_options
        [
          FastlaneCore::ConfigItem.new(key: :bump_major,
                                       description: "Bump the major version",
                                       optional: true,
                                       default_value: false,
                                       is_string: false),
          FastlaneCore::ConfigItem.new(key: :bump_minor,
                                       description: "Bump the minor version",
                                       optional: true,
                                       default_value: false,
                                       is_string: false),
          FastlaneCore::ConfigItem.new(key: :bump_patch,
                                       description: "Bump the patch version",
                                       optional: true,
                                       default_value: true,
                                       is_string: false)
        ]
      end
    end
  end
end

The Fastlane Action code has three parts:

  1. Implementation
  2. Description
  3. Action parameters definition

Code

We start our implementation by obtaining the last git tag, I use for this existing action: other_action.last_git_tag.
After that, I make a simple check if the repository already has a tag, if not we can return it early with the default tag, I've chosen 0.1.0.
The third step splits the tag into three parts, I assume the git repo has ONLY tags in the format x.y.z. If your repo has other tag formats, you have to adjust the code to find the right pattern!
The last section checks what option was selected, and calculates the new version to be returned.

Description

We can specify instructions for potential users, authors, and platforms our Action supports.

Parameters

We are using three boolean options bump_major, bump_minor, and bump_patch. All of them are optional, and all of them have default values. This way, we can use it in lanes without passing any options, and it will bump patch versions by one.

Conclusions

What we can do with it? You can use this action to calculate a new tag, use existing actions to create a new release, or even create a new release branch with a freshly obtained value. Fastlane can push changes for you, this way every release is automated, and there is no room for human error :)