Here at POSSIBLE Mobile we run dynamic teams which can suddenly ramp up to ten engineers or more working on a single app. With that kind of rapid change in team composition and burst in the quantity of code pouring in, we have to be diligent about tracking the state of in-progress work. Sometimes we have to be able to merge feature branches so that concurrent interdependent efforts can move forward while we wait for items such as requirements clarifications or external dependencies to be satisfied.
Apart from project management tools, one of the constructs we have relied on is writing to Xcode Issue Navigator—#warning directives placed in Objective-C files have project-wide visibility there. Let’s say we are waiting for an icon from our design team. We can track that in Issue Navigator with a #warning directive like this: #warning TODO: We are waiting for an icon for this button. See ticket SS-4442.
Unfortunately there are two show-stopping issues with #warning directives: they are not supported in Swift files and they are not compatible with the “treat warnings as errors” build setting. What we really need is a #warning replacement.
Xcode Warnings as Broken Windows
There’s a theory in criminology called the broken windows theory1. It states: “Consider a building with a few broken windows. If the windows are not repaired, the tendency is for vandals to break a few more windows.” I like to think of Xcode warnings as broken windows—if a project has 341 warnings, if one more window breaks for 342 it’s easy to look the other way. The way we should look at it is that every additional infraction should be guarded against as if it’s the first one. One way we can combat broken windows is to turn on “treat warnings as errors” in build settings and be explicit about warnings we choose to ignore.
By saying “Yes!” to “treat warnings as errors”, warnings introduced during development are build-halting errors. We can ask the Clang compiler to be more or less strict about what types of transgressions are considered warnings—try searching for “warnings” in Build Settings to see some of those options. A challenge: turn on as many as you can! Keeping “treat warnings as errors” on requires discipline.
Apple’s annual cadence of iOS releases deprecates lots of APIs and there are times when using deprecated methods is unavoidable. We faced a situation recently where our project needed functionality from a deprecated method CNCopyCurrentNetworkInfo to get the network name so an experimental feature could be limited to a test audience on a known network. Typically when there is a deprecated method, Apple provides a replacement. In the case of CNCopyCurrentNetworkInfo, we are directed to use the NEHotspotHelper class which requires special approval from Apple—an option we did not want to pursue.
We can opt out of warnings on a file-by-file basis by adding a -w compiler flag in Compile Sources to suppress warnings. In our case with CNCopyCurrentNetworkInfo, we’ll isolate the use of that API in a helper class and we’ll opt out of warnings for that file so we can keep “treat warnings as errors” on for the project. It could be argued that allowing arbitrary files to opt out of “treat warnings as errors” via the -w flag opens the project up to another vector for broken windows, as there is nothing to limit the number of “opt out” files from growing. To address this, we track those exceptions in a way that’s visible to the team by carefully documenting each instance in README.md, noting the file, the specific warning we intend to suppress, and the rationale behind this decision. In this way, we’re being explicit about our compromises. A principal advantage to having a clean project as it relates to tracking in-progress work is that we don’t want our important reminders to be lost amongst myriad other issues.
XcodeIssueGenerator2 is an open-source OS X Command Line Tool I wrote in Swift that searches source-code files for comments which begin with tags such as // TODO: or // SERIOUS:. When it finds a match, it writes a warning or error in Issue Navigator. Because it runs post-build, it does not trigger “treat warnings as errors” allowing us to use Issue Navigator, while still blocking compilation with other project warnings. You can try the basic mechanism for writing to Issue Navigator by adding a new Run Script Build Phase with a line like this:
echo "$SRCROOT/SomeClass.swift:7: warning: // TODO: This must be fixed before shipping!”
XcodeIssueGenerator extends that basic mechanism with options that let us specify build configuration, comment tags for which to search, and exclude directories. This is a call that treats TODO’s, FIXME’s, WARNING’s, and SERIOUS’s as warnings in Debug, excluding the Vendor directory:
XcodeIssueGenerator -b Debug -w “TODO, SERIOUS, WARNING, FIXME” -x “Vendor/”
A similar call for Release builds that changes TODO’s to errors looks like this:
XcodeIssueGenerator -b Release -w “SERIOUS, WARNING, FIXME” -e “TODO” -x “Vendor/”
In the Release build configuration, TODO’s will now present with a red error marker, but this will not cause the build to fail—we also call exit(EXIT_FAILURE) within XcodeIssueGenerator.
The ability to specify the tags we care about per build configuration turns out to be an incredibly powerful and useful capability—we can now create comments that have semantic meaning within our team and which behave differently while we’re building features versus when we’re ready to release them.
How We Use XcodeIssueGenerator—It’s All About Semantics
We have “treat warnings as errors” turned on in our project so our project builds cleanly and we notice our XcodeIssueGenerator output. Any warning-triggering code that’s disabled in build settings is marked with a comment such as // WARNING: We are suppressing a warning here.. We have TODO’s set as warnings in Debug and as errors in Release. Other tags such FIXME’s and SERIOUS’s are set as warnings in both Debug and Release.
Let’s say Ada is working on a new feature and she feels as if a section of code is underdeveloped. She’ll mark that code with // TODO: We should explore a better way to do this. See ticket SS-4456. Since she has XcodeIssueGenerator installed, that comment will appear in Issue Navigator. As we approach release, the team decides that the existing implementation is good enough for now. Since TODO’s are errors in Release builds, we can’t ship until the TODO is addressed. We’ll promote that TODO to a FIXME so it still has visibility but does not stop us from making our Release build.
Meanwhile, Matt is making a debug drawer to help Mark with our QA effort. He writes conditional compilation code that disables the drawer in release builds. To help add visibility to this potentially dangerous code, and emphasize that this feature should never be turned on in release builds, he adds a comment // SERIOUS: This debug drawer code cannot be moved outside of this conditional compilation block..
Now we are tracking three issues in Issue Navigator. The TODO will cause an Archive build to fail, guaranteeing we can’t ship with it until it’s been addressed or deferred by promoting it to a FIXME.
TODO as a warning in a Debug build:
TODO as an error in a Release build:
By prescribing semantic meaning to the tags we use, we create a mini language which brings visibility across the project to important areas of code. The team understands how many SERIOUS items there are in the project which ends up to be something we can double-check before each release. We have a feeling for how many loose ends there are in a new feature that need to be handled before shipping by how many TODO’s have been left in code. FIXME’s are often deferred TODO’s, indicating, for example, that we have accepted a bug’s existence for now and will proceed with shipping. WARNING’s are softer than SERIOUS’s but still important. Through the use of semantic tags our simple tool becomes an incredibly powerful system for visibly tracking known issues, dangerous code, and preventing bugs or incomplete work from being shipped.
Try XcodeIssueGenerator on Your Project
You’ll find instructions for using XcodeIssueGenerator on the project’s GitHub page. Give it a try and send us feedback or pull requests! Searching source files in a Build Phase isn’t terribly efficient; however, we found that in practice it doesn’t have a noticeable impact. The tool takes about 1.5 seconds to run on a very large project with 1500 source files and a fraction of a second on smaller projects. While we certainly wish for a built-in Xcode feature that gives our TODO’s and FIXME’s project-wide visibility, we aren’t holding our collective breaths, and XcodeIssueGenerator serves as a highly configurable, functional stop-gap in the meantime!