Introducing Uber Poet, an Open Source Mock App Generator for Determining Faster Swift Builds
July 25, 2019 / GlobalGiven the scope and scale of Uber’s business, our Swift applications are some of the largest in the world. Each application possesses 500,000 to 1 million lines of shipping Swift and Objective-C code and about three times more lines of code in the form of tests and auto-generated mocks.
As a result of the makeup of our iOS apps, Swift compile times are an important consideration for our engineers. Just a couple years ago, clean build times with our Swift-based rider app used to take 30 to 45 minutes, and after improvements in build configuration, the swift compiler, and build hardware, Swift builds now take about 5 to 10 minutes depending on the application. Slow build times lead to longer development times for engineers, which means slower turnaround times for new features and improvements, along with all of the other typical issues with long build times.
Before Swift 4.0, we determined building many small Swift modules (~300) built with whole module optimization (WMO) mode was the fastest overall build mode for Uber. Swift 4.0 introduced a new batch build mode, which while advertised to work faster in most cases, was about 25 percent slower to build with our ~300 module configuration.
We wanted to test if refactoring the application part of our code into a few large modules would make our overall build time faster with the new batch mode but without actually refactoring it. To test out this hypothesis, we created Uber Poet, a mock application generator to simulate our target dependency structures. To enable others to benefit from our mock application generator, we have decided to open source it.
In this article, we discuss our motivation behind the tool, its design principles, and how Swift developers can leverage the software for their own applications. After learning more about Uber Poet, we hope you will be inspired to try out the tool for yourself!
How Uber Poet works
The name Uber Poet was inspired by Android Studio Poet, which is a similar kind of app for Android we discovered after building our solution. Like Android Studio Poet, Uber Poet creates a mock application for iOS to test and benchmark your build system in various ways.
To accomplish this, Uber Poet first creates a dependency graph configured by command line options that are fed into it. The nodes of the dependency graph describe the dependencies between modules, how many lines of code each module represents, the names of modules, and which module is the root application node.
The graph generator then takes this graph and feeds it into a project generator, which then produces code for each module. The code and project metadata is then saved into a target directory. At Uber, we create Buck build files with Uber Poet. We then use Buck to generate Xcode project files with which to build the entire mock app. We can also use Buck directly to build our mock application, and Uber Poet could be extended to generate files for other build systems.
To create new dependency graphs for testing, Uber Poet enables us to create new graph generator functions. We also made a multi-suite tester in Uber Poet that tests all of our graph types along with multiple versions of Xcode and configuration options. We dump build time traces, logs, and a summary CSV file. With the build traces we can understand where bottlenecks occur for various graph types.
Graph generation types supported by Uber Poet
Uber Poet facilitates the creation of various types of graphs to depict mock apps. These graphs represent generation functions with configurable variables such as X, Y, and Z. Graph types include:
Flat graph types are X modules that don’t depend on anything. The top level app module depends on all of these modules.Layered graph types are X layers with a certain number of Y modules per layer. Each module depends on Z modules in the layers underneath its current layer. The top level app module depends on the top layer of this graph.
A Big Small Flat (bs_flat) graph is one set of X big ‘application’ modules and Y small ‘library’ modules. None of these library modules depend on each other. The top level app module depends on all of the big and small library modules.
A Big Small Layered (bs_layered) graph is three layer groups stacked on top of each other in one graph. The top layer group is the app module, which depends on a flat layer of big modules. The big modules depend on the top layer of the graph generated by the layered graph function described above.
The dot file graph generator creates a graph described by a dot file. BUCK has an output mode that will generate a dot file description of your application’s module build graph.
We use those dot files to generate a mock app with the same dependency structure of our current apps. These mock apps do not exactly correspond to the same app structure of our real apps, since each module in a dot graph mock app are the same size.
Using Uber Poet
Uber Poet is a fairly simple command line application to use; to run it, all you need is a machine with macOS 10.13 or higher, Xcode, Buck, pipenv and optionally cloc. Cloc can be installed via the homebrew command: brew install cloc .
Then, we download the project from Github and run the Python scripts from the terminal app.
For example, we can create a simple mock application with genproj.py, from the Uber Poet GitHub project:
pipenv run ./genproj.py –output_directory “>$HOME/Desktop/mockapp”
–buck_module_path “/mockapp”
–gen_type flat