When it comes to modularisation on Apple platforms, development teams point must deal with adding a new module at some point. In this article, I would like to present my approach to automating this work.
Why is it an important step to be automated?
I strongly believe product development teams should work as fast as possible. It is important to focus all software-related work on the product, not the stuff around project setup and configuration. Adding a new module manually takes time.
I used to copy some other module, rename directories, all the needed files, update their content, then submit a pull request that was reviewed by iOS colleagues. I guess the whole process would take around 1-2 hours when I had done it several times before already.
Is it a problem? Yes. A good modularised project should support scaling well, also when it comes to time spent adding new modules. Imagine doing it for the first time… There is a high probability that some developers would struggle for hours with a simple mistake due to manual setup.
With automatic generation, it should be super fast, easy, and human error-free, let’s see how to achieve that!
What options do we have?
- SPM
- Tuist
- XcodeGen + some manual work
- ?
As for my experience, SPM has a lot of limitations when it comes to customization. Possibly every project that uses modularisation is ‘larger’ than the average. At the same time, when you start scaling you solve more and more problems from different domains, and at some point you end up with a lot of customization… So I wouldn’t decide to use SPM because it’s too limited for such a scale.
I haven’t discovered Tuist enough to give any opinion here… I highly encourage you to give it a try, cause it looks like several projects with ‘scale’ are happy with it…
XcodeGen is a tool for generating a .xcodeproj file only, so it still requires some manual work for generating a whole module with directories, dependencies, etc… But that’s what we’re going to automate in the simplest possible way.
Using a templating system
We don’t use it as much on Apple platforms but it is a very common practice on the web frontend to use templating systems. It’s the mechanism where you can mix ‘static’ text or code such as HTML with logic code, i.e. js. There are engines such as EJS for JavaScript, EEx for Elixir (Phoenix framework), and ERB for Ruby.
Apple platform developers are probably most familiar with Ruby since there are many tools written in this technology, such as. Cocoapods, Fastlane, Xcodeproj… So I decided to use ERB.
What’s the idea?
An enabling constraint from the very beginning was to keep it simple so that every member of the team would be able to modify the generation template on the fly — easily add a new file or add a condition to the existing one.
1. Template files
As I mentioned before, in a templating system you can mix some static text with logic. Here is an example from a real file:
<% if @has_tests %>
<%= @module_name %>Tests:
dependencies:
- target: <%= @module_name %>
embed: false
- framework: Quick.framework
embed: false
- framework: Nimble.framework
embed: false
<% end %>
This is a part of the ‘project.yml’ file that is used by XcodeGen to generate ‘.xcodeproj’. As you can see besides static text like - framework: Quick.framework...
I wrote some real ruby code for unwrapping some variables such as <%= @module_name %>
or even checking some conditions. There are even more possibilities! You can use other ruby standard library functions such as the very useful gsub
to replace text with the help of regular expressions.
2. Files tree
My second thought was to represent template file structure in the same way that they would be generated. The real Xcode module has multiple directories and files, such as xcconfigs, implementation files, tests files, or even a readme. Here is what it looks like in the template directory:
Every file is represented by an erb
template. Then there is a script that renders all templates in newly constructed directories. The final module with the example name Article looks like this:
The rendering script is bundled into a Fastlane action that asks the user several questions such as module name, dependencies, etc… The file is less than 50 lines of ruby code that creates directories and walk-through templates in the loop and runs XcodeGen at the end, nothing sophisticated.
Summary
I believe this is one of the simplest possible ways of generating a new module while keeping full flexibility of the contents of the file. Now a new module generation takes 2 minutes including the time to submit a pull request. Now it doesn’t even need to be code-reviewed cause there is no place for a human mistake. With such automation, developers can focus on real coding rather than struggling with configuration. If you have modularised architecture I highly recommend you reduce the time and cost of adding a new module to a minimum!
Useful links: