Pedro Piñera bio photo

Pedro Piñera

Software Engineer and Open Sourcer



SpeakerDeck Email Twitter Google+ Github Stackoverflow

In the past are those days when you automated your tasks using Make, Rake or Fastlane. These tools provided an interface and tools to automate certain tasks in our projects. Each of them had their strenghs and weaknesses. If we take Make, for instance, it requires some bash knowledge, but on the other side, it is more robust than the other solution (it doesn’t depend on anything that is not in your system). Rake or Fastlane made the definition of our tasks easier using a more readable language like Ruby, but they required to install some dependencies. Fastlane, for example, comes with some dependencies that need to be installed in the system using Ruby gems.

While those tools made our lives easier as developers, I felt we could leverage Swift and its types to automate tasks, using Xcode and its editor to build and test our tasks easily. I started to work on the Swift version of Make that I’m pleased to introduce in this post, Sake. It’s in a very early stage but ready to be used. I’m eager to get feedback from you and improve the tool with your help. Please don’t hesitate to contribute to the repo or propose ideas!

Why Sake? ❓

I’m a Swift/Objective-C developer, and I’m very used to work with compilers that catch any issues with the types in our code. Types are a potent tool and used correctly can make our apps safer bringing a lot of confidence to the developer. In most of the projects that I’ve been involved, either open or closed source, the automation was mainly done using bash or Ruby and tools like Rake or Fastlane. Many times I’ve been myself trying to debug failing lanes or tasks using pry in Ruby or printing stuff in the console to analyze the execution of the task. Sometimes these issues happened on CI and I spent a lot of time debugging them until I could figure out where the issue was coming from. Does it sound familiar to you?

I read about how Swift Package Manager works and saw that Danger did something similar. After some grooming, I realized it was feasible to build a Swift Make where the definition of the tasks would be in a Swift file. What I liked about the idea is the fact that developers would be able to write the scripts in a language they are familiar with. They don’t need to know about the Ruby syntax, or how to debug Ruby execution. Moreover, they could use an IDE like Xcode that they are very used to. The tool would facilitate testing and composition by allowing developers to separate their tasks into multiple .swift files that are part of the same module:

// Sakefile
import SakefileDescription
import SakefileUtils

enum Task: String, CustomStringConvertible {
case build
var description: String {
switch self {
case .build:
return "Builds the project"
}
}
}

Sake<Task> {
$0.task(.build) { (utils) in
// Here is where you define your build task
}
}.run()

How it works 👩🏻‍💻

Sake is based on the same Swift Package Manager foundation but with some subtle differences. In case of Swift Package Manager, the tool compiles your Package.swift file exporting the variable as a TOML file (a format similar to JSON yet more human-readable). Afterward, they parse that TOML file and map it into models that define the structure of your project.

If you are interested in reading more about that, I’d recommend you to check out this blog post that explains it further.

In case of Sake it doesn’t need to generate any toml file since the tasks are not value properties but closures that contain the actions. Rather, Sake compiles the file passing some arguments that are used by the Sake class to determine what needs to be done (e.g. list all the tasks, or run a particular task). This class contains all the logic that compiles and runs the Sakefile.swift file.

Notice that we need to pass some flags that specify where the libraries are. This crucial if we don’t want the compiler to throw an error when you try to compile the Sakefile.swift because it doesn’t know where to import the SakefileDescription library from.

How to use it 🤔

Install the tool 📦

First of all, you need to install the tool. You can easily do it with Homebrew:

brew tap xcodeswift/sake git@github.com:xcodeswift/sake.git
brew install sake

Notice that you have to add the repository tap. A tap in Homebrew is a repository that contains formulas, and formulas are Ruby classes that specify how to install a given tool. I plan to add the formula to the official Homebrew tap so that you don’t have to add any external tap, but it’s pending task to do.

Generate your Sakefile.swift 📝

Once you have the tool installed you should be able to run the following command from the console:

sake init

sake init will generate a base Sakefile.swift in the current directory where you can start defining your tasks.

Notice that Sake is a generic class where the type must be an enum that contains all possible tasks. That makes the definition of your tasks more type-safe since you can define dependencies between tasks by passing the type rather than a string with its name.

Generate the Xcode project ⚙

You can edit the file above using any text editor. That’s a good thing to do if you want to practice how familiar you are with the Swift syntax and Foundation APIs but is that something that you’d like to practice? What if we could use Xcode and benefit from the syntax highlighting and the code autocompletion that the IDE offers? What if I told you that Sake could help you with that?

Run the following command on the console:

sake generate-xcodeproj

The command will generate a Sakefile.xcpdeproj project in the directory where the Sakefile.swift is. The project contains a command-line-tool target with your Sakefile.swift and the linking settings necessary for Xcode to recognize the tool libraries.

:warning: Xcode expects command-line-tool targets to contain a main.swift file that is the entry point of the command line tool. That’s an implicit value that we can’t change. If you try to compile the scheme you’ll see that Xcode complains because you cannot run code at the top level in files other than the main.swift file. Ignore the error since it will work when the Sakefile.swift is run by Sake.

Running tasks ✅

Running tasks is very easy. You just need to run the following command where name is the name of the task:

sake task name

That would run the task erroring if the task failed (by throwing a Swift error). Sake also provides a command that you can use to print all the available tasks:

sake tasks

One more thing… 🍎

Hooks

Sake supports hooks that allow you to define code that will be executed before or after of each or all the tasks:

// Sakefile
Sake<Task> {
$0.beforeEach {
// Do something
}
$0.afterAll {
// Do something
}
}.run()

Utils

When tasks or hooks are created the closure takes an object that contains some utils that are provided by Sake. The utils that are currently provided are:

  • Git Util to interact with git (e.g. get last tag, current branch, commit changes)
  • Shell: Util to run tasks in the shell.
  • Http: Synchronous API client that supports JSON parsing.

In the example below you can see how the utils are used to automate the release process of Sake:

let branch = "release/1.0.0"
try utils.git.createBranch(branch)
try utils.shell.runAndPrint(bash: "swift build")
try utils.git.addAll()
try utils.git.commitAll(message: "[\(branch)] Bump version")
try utils.git.tag(version)
try utils.git.push(remote: "origin", branch: branch, tags: true)

Limitations 😅

Although the tool is entirely functional some important limitations are worth considering:

  • Swift and ABI stability: When the tool is installed in your system it compiles some libraries with the version of Swift that you have currently installed. Because Swift doesn’t have ABI stability yet, you’ll only be able to use Sake with that version of Swift. As soon as you upgrade the version of Swift that you use in your system the tool will stop working. You’ll have to either reinstall the tool, or use a tool like Mint that allows you to run Swift Package Manager command line tools building them when it’s necessary. Apple is working on the ABI stability so we can expect this to be fixed soon.
  • Xcode and main.swift: As you might have noticed when you edit your Sakefile.swift with Xcode it doesn’t compile because we cannot run Swift code at the top level in a file other than the main.swift. However, Xcode editor features like code autocompletion or syntax highlighting seem to be working great. I’ve tried several things like creating a temporary main.swift with a symbolic link to the Sakefile.swift but I couldn’t get it working. If you have any idea of how this could be solved, I’d love to hear about it.

Upcoming features 🤗

Sake is in a very early stage and I’m eager to get feedback from you to improve the tool. Some ideas that are in the backlog are:

  • Support modules: Right now all the tasks code needs to be defined in the Sakefile.swift. That can eventually result in a large and complex file pretty quickly. The idea of modules is to support developers to define their code in multiple Swift files under the /Project/Sake folder. All the files in that folder would be part of the same module, and thus visible to each other.
  • Facilitate testing: One thing that I found developers barely do with automation code is testing. The code is mostly written in a build-and-try manner. I’d love Sake to change that and encourage developers to test their automation code as well. Sake would allow developers define their implement their tests in a new module and run them using Xcode.
  • Officially Homebrew formula: You might have noticed that in order to install the tool you need to tap the repository. That is necessary because the tool formula is not part of the Homebrew formulas repository yet. As soon as the tool becomes mature enough, I’ll propose to include the formula in the official Homebrew formulas repository.
  • Improve/Add utils: Sake comes with some basic utils to facilitate common tasks. The goal of Sake is not to solve everyone’s problems providing a large number of utils but to ease automation with as few utils as possible. As soon as developers start using Sake, I’ll see if we need to reconsider the APIs of some of them or maybe introduce new utils.
  • SakefileDescription versioning: The library that contains Sake DSL which is used to describe the tasks in the Sakefile.swift is versioned together with Sake. If developers want to use a new version of Sake that comes with a new description syntax, they are forced to update their Sakefile.swift. Having a different versioning for SakefileDescription will allow developers to update to the latest version of Sake without having to update their Sakefile.swift file. We’ll most likely implement it in the same way the Swift Package Manager does it, by adding a comment line at the top that specifies the version of the SakefileDescription that needs to be used.

I hope you liked this introductory blog post. Can’t wait to see how you use the tool and the ideas you come up with. Sake is part of xcode.swift, an open source organization on GitHub that aims to build open source tools written in Swift to facilitate developers work when working with Xcode. If you are interested in contributing don’t hesitate to do it, there’s a Slack channel that you are invited to join and talk to other contributors.