We get to work with a lot of great companies. When the developers of Day One came to Macminicolo, I was especially excited. Their award winnning app for Mac and iOS is one of my favorite apps of all time. In fact, I've joked with Ben and Paul claiming that I am the head of their public relations, paid or not.
A good development workflow is critical to producing a high-quality app, especially if there is more than one developer involved. At Day One our current team is comprised of four full time developers and one designer. The great majority of our work is focused on our Mac and iOS apps. This post will share the workflow we currently use for developing Day One.
We've been using Xcode and GitHub since we began writing Day One two years ago. We would write code, commit it and then produce a build when ready to submit to the App Store. Easy. But as additional developers joined the team I quickly realized that we were making silly mistakes. We were forgetting to test shared code on both Mac and iOS. Builds would sometimes fail. We would forget to run static analysis before a release. We were all occasionally introducing logic errors. These are all natural mistakes, especially when knowledge is distributed among multiple people in a team.
I sat down and made a list of what I wanted to change: I wanted builds to happen automatically. I wanted code reviews. I wanted automated unit tests and static analysis. I wanted to catch new compiler warnings that I inadvertently introduced. I wanted to know when a build was failing. What I wanted was continuous integration.
In a nutshell, continuous integration (CI) involves a server watching for changes to a repository. When the server detects a change it automatically begins a build and runs tests. The result is real-time feedback about the state of your application.
Have you ever committed a small change without actually building it, only to scratch your head in bewilderment the next day when your app won't build? Have you ever pulled down someone else's changes and cursed them for breaking the build? CI solves these problems. A CI server doesn't forget to run the unit tests. It doesn't turn off static analysis because it gets tired of it taking too long to run. It doesn't forget to run your cross-platform unit tests on both iOS and Mac.
The best CI server I've found for Mac and and iOS projects is Jenkins. Jenkins is flexible, cross-platform, open-source build server with a very active community. This community has built an impressive array of plugins that cover everything from Xcode to IRC to GitHub integration.
In order to use Jenkins to build iOS and Mac projects you need a Mac. One option is to repurpose your local development machine as a Jenkins server, but this presents a few obvious problems: I use a laptop for development and sometimes I take it upstairs or out of my local network. I also don't want expensive builds to start running in the background right as I'm testing some new animations. Instead, I recommend a hosted solution. A hosted Mac Mini is a perfect solution: cheap, fast, available. We have a quad-core i7 with 16 GB RAM hosted at Macminicolo. The connection is insanely fast and we've had zero downtime.
Once you've got the hardware, the initial Jenkins install is really straightforward. Jenkins has a native OS X installer package that will take care of everything, including creating a new jenkins user on your system and setting up Jenkins to start automatically when your computer starts up.
In order to see where CI fits in, it might help to understand our overall development workflow for Day One. This workflow is designed around three major systems:
GitHub: Source code management. GitHub has great support for private organizations. Developers in our organization can create their own private forks and issue pull requests back to the main repository. The best part is that these private forks inherit the access permissions of the main repository, which means they are not public, but still remain accessible to everyone in the organization. I'll explain why this is great a bit later.
Grove.io: Hosted IRC. IRC is a great option for teams to communicate, but can be a pain to setup and maintain. Grove handles all of these pain points and provides some nice features such as searchable, server-side archiving of all conversations. It will also notify you by email if someone mentions you in a room and you're not in there.
Jenkins: Continuous integration server. Archives our builds, runs unit tests, integrates with GitHub and Grove.
With those systems in place, here's how we go from feature to implementation:
So far we've found that this process strikes the right balance. It automates the repetitive aspects, provides protection but is still lightweight and flexible. The last thing you want in a development process is for it to get in the way of real work. Don't waste valuable development time conforming to a complicated process. Instead, change your process to make it work for your team.
Plugins are the key to getting the most out of Jenkins. Here are some that we use:
Your Jenkins server will need to sign your builds. I recommend that you create an Apple ID specifically for your build server and then add it to your iOS/Mac team.
I also recommend doing the same for the GitHub integration. Create a new GitHub user for your build server and add that user to your organization with the appropriate permissions.
Our build process is a continual work of progress. Here are some ideas we've had for future enhancements: