Simplify Your iOS CI with Makefiles
Harness the power of the classic MAKE utility for efficient and portable iOS builds
An iOS project of any size can benefit from automating the build process. Before reaching for a more complex solution like Fastlane or GitHub Actions, consider starting with the humble MAKE utility. MAKE has been around since 1976 and is preinstalled on any UNIX-like OS, including macOS, making it easy to adopt.
The Elegant Simplicity of MAKE
MAKE is the inspiration for many modern build tools, including Fastlane, and is still an excellent option for many projects needing automation but don't want to invest in complicated build systems. Here are a few benefits of using Makefiles over some alternatives.
1. Simplicity: Makefiles use a straightforward syntax that's easy to read and write.
2. Portability: Since MAKE is preinstalled on most UNIX-like systems, there are no dependencies to install and maintain on CI servers or developer machines.
3. Consistency: They provide a single source of truth for build and test commands.
4. Version Control: Your build process is now versioned alongside your code.
Let's look at how you can implement a Makefile for your iOS CI process.
Implementing a Makefile for iOS CI
Here's a basic structure for your iOS project's Makefile:
PROJECT = $(shell ls -1 . | grep .xcodeproj)
SCHEME = ExampleProject
BUILD_DIR = .build
.DEFAULT_GOAL := help
help:
@echo Goals:
@echo "help - display this message"
@echo "clean - clean the project"
@echo "build - build the project"
@echo "test - test the project"
clean:
@echo Cleaning...
xcodebuild clean \
-project $(PROJECT) \
-scheme $(SCHEME) \
| xcbeautify
@rm -rf $(BUILD_DIR)
build: clean
@echo Building...
xcodebuild build \
-project $(PROJECT) \
-scheme $(SCHEME) \
-derivedDataPath $(BUILD_DIR) \
| xcbeautify
test: clean
@echo Testing...
xcodebuild test \
-project $(PROJECT) \
-scheme $(SCHEME) \
-derivedDataPath $(BUILD_DIR)
.PHONY: help clean build testBreaking Down the Makefile
This Makefile defines several goals: help, clean, build, and test. Let's examine each goal:
1. help: Is a convenient goal to print out the available goals.
2. clean: Cleans the build folder.
3. build: Builds the project using xcodebuild.
4. test: Runs the test suite.
This is a simple example Makefile that can easily be extended to include goals for deploying to TestFlight or running SwiftLint.
Define Prerequisites
Prerequisites of a goal are listed after the colon, ensuring that it executes the prereqs before executing the specified goal.
build: cleanSetting a Default
By convention, running make without additional parameters will execute the first goal in the file. As of MAKE 3.80, the default can be set explicitly using the .DEFAULT_GOAL variable, making it a bit clearer.
.DEFAULT_GOAL := helpVariables
Variable values can be passed in as command-line arguments to MAKE or as environment variables, adding additional flexibility in configuring a Makefile.
make build SCHEME=MySchemeWhy so .PHONY?
By default, Makefile goals are file targets — they are expected to produce a file with the same name as the goal. If that file already exists, it won't run the goal. Using .PHONY tells MAKE that a goal will not produce a file.
GitHub Actions
Turn the Makefile into a Continuous Integration process by integrating it with a GitHub Action workflow.
Keeps the GH Action workflow simple
Developers can use the same commands as CI to execute builds locally
When MAKE isn't enough
While MAKE is a straightforward solution, it can become more complex as a project grows. As a Makefile becomes more complex, there is a point of diminishing returns. To scale a build system for complex projects with many modules, consider alternatives like BUCK or BAZEL, which support concepts like remote build execution and remote build caches.
Wrapping Up
MAKE is a simple yet powerful tool that can bring consistency, efficiency, and clarity to your CI pipeline without hiding details away behind unnecessary abstractions.
As you integrate this approach into your workflow, consider:
How can you tailor the Makefile to your team's specific needs?
What other areas of your development process could benefit from similar standardization?
How might this approach evolve as your team and project grow?
Remember, the goal is to spend less time fighting with CI and more time building great iOS apps. Happy coding!


