On Using Python for Scripting in iOS Projects
As an iOS engineer, my recent focus has been on platform work, involving tasks like optimizing build times, integrating CI/CD pipelines, and devising testing solutions. This has led me to explore various scripting options, and I’ve found great satisfaction in using Python as our preferred choice.
The first Python code
Initially, scripting wasn’t a prominent aspect of my team’s workflow, largely due to the simplicity of our CI/CD setup and the prevalence of Bash scripts in our project. The problem arose when a colleague was tasked with creating a script to upload tracking metrics to Datadog.
While Bash scripts were convenient for execution, their maintenance was challenging, especially when the logic became more complex. Faced with annoying bugs, the engineer sought assistance and asked whether an alternative language like Python could offer better solutions. We had a vote with several options: Bash, Swift, Ruby, and Python. The two prominent were Ruby and Python. I remember my lead said he knew a bit of Ruby but not quite sure about Python. I managed to convince him that Python’s syntax mirrored Ruby’s, and that Python was advantageous for potential analytics work. Somehow, we landed on Python.
Eventually, the engineer submitted a pull request with the first-ever Python script in our project, although the code wasn’t particularly pythonic.
The prime time of CI/CD work
Over time, our project expanded in contributors and features. The need to reduce build and pipeline times became paramount. This led to a surge in scripting tasks, predominantly using Python. As of mid-2019, our scripting breakdown looked like this:
- 65% were written in Python
- 25% were written in Ruby
- 10% were Bash code
Ruby vs. Python vs. others
As you know, Ruby is well known for tooling in iOS. This is mainly driven by CocoaPods and Fastlane, the big two rubyish open source tools. The latter, in particular, stood out for CI/CD due to its various actions and plugins. Some non-trivial tasks such as dealing with code signing, working with TestFlight and AppStore submission, were well supported by Fastlane. Thus, to utilize those powers, a hybrid approach was adopted, combining both Python and Ruby.
Python and its power in data analysis
However, Python outshined Ruby in some cases. In tasks like build time optimization, where data visualization was essential, Python excelled. For instance, when analyzing Xcode parallel build tasks, we created Gantt charts to gain insights about how target dependencies impacted the overall build time. Python’s rich data visualization libraries made this possible, while Ruby’s equivalent options were lacking. In fact, there’s a whole data community out there working on this area. By embracing Python, we seamlessly delved into visualization tasks, a pivotal aspect for driving our efforts to enhance build times. Following is the example of visualizing project build time.
Python also empowered us in data analysis tasks. For example, identifying top time-consuming targets, or analyzing app size breakdowns, or calculating test success rates to see how flaky tests were. Implementing such features in Ruby would be really troublesome. Even when you endeavor to make it work, there are plenty of cases you need to handle, for instance, when your data is not clean enough. Meanwhile, pandas
, a renowned Python package tailored for data analysis, just works smoothly and effortlessly.
Insightful CI/CD
Because Python was our first-class scripting language, I had a lot of fun incorporating data analysis into CI/CD work. Among the mobile teams in the company, ours boasted the most accessible CI/CD data for analysis. With numerous tracking metrics available, you can just batch-download them (via API, of couse) and conduct your own analysis at hand.
The elegant syntax
While I got exposed to both Ruby and Python for scripting purpose, I leaned towards Python. The simplicity of Python’s syntax appealed to me more than Ruby’s “multiple ways to do one thing” philosophy. In my defense, if we have if
then unless
becomes redundant. Similar to select
and reject
. Well, that’s just my preference. I’m not gonna argue with you about this haha.
An aspect of Python coding that truly captivates me is the “decorator” syntax. This feature adds elegance to the code and often requires minimal changes. For instance, adding the @retry
annotation to a method enabled easy retries for unstable actions. Just one-line change.
Scripting languages vs. statically-typed languages
Some teams opted for Swift scripting and tooling, leveraging their strengths. However, I preferred the flexibility of a scripting language like Python over a statically-typed one like Swift for the automation tasks. This choice minimized extra efforts, especially when distributing tools. Meanwhile, if using Swift, you need to ship the tools separately for MacOS, Linux (if adopting Docker), etc. Also, tools developed by statically-typed languages are often distributed in the form of binaries. This approach constrains the potential to further extend the usage.
Reusable code with package distribution
Initially, CI/CD code and Swift code (of the main project) were hosted in the same repo. However, running a pipeline against a change in this monolithic repo took quite a long time. We then relocated our CI/CD to a different repo for faster updates. Not long after that, I had the chance to get involved in different projects. Each required similar CI/CD integrations. This sparked the idea of packaging our CI/CD implementation for reusability. In this regard, scripting languages prove to be more efficient.
…
To sum up, while incorporating Python into iOS projects for scripting & tooling may not be conventional, it has led to remarkable achievements. Particularly, if your aspiration is to establish an insightful CI/CD ecosystem, Python emerges as an incredibly fitting choice, thanks to its capabilities in data analysis.
Given the opportunity to make technology choices for a new project, I would definitely opt for Python once more 🙂.