Every now and again I find myself getting excited to hack something together. A fun little project that can be a quick win -whether it solves a problem for the masses or for just a few people in an organization.
Recently, I spent a couple of hours at an AT&T Hackathon with Travis Todd to create a cool little app. This app allows people to take a photo of a business card, email it to email@example.com, and automagically get a vCard back to add to their address book. It was a lot of fun and a great success in my eyes.
When we acquired our latest iOS Engineer, Duane Schleen, I felt it was time for us to formalize continuous integration and deployment around Objective-C apps. I adore TestFlight for how simple they have made iOS app ad-hoc distribution. So my goal, naturally, was to build a process that included a tie-in to them. I wanted to have a process that would allow Duane to push to a beta branch and have it out to users in a matter of minutes. So I set out to do just that: I bought us a little Mac mini and went off to the races.
Anyone who has worked with Jenkins knows how great it is and that it has a plethora of wonderful plugins to do just about anything you can dream of. Including a TestFlight plugin (Yay!) which totally made my life even easier because I didn’t have to worry about creating a bash script or writing my own plugin. It even has support for notifying testers in a distribution list! Git Plugin…check, TestFlight Plugin…check. Xcode Plugin…check! I asked myself “Wow, could this get any easier?”
With a little bit of effort, I had setup a freestyle project that pulled, compiled, archived, pushed to TestFlight, and notified the team. It was a definite win on our part and took the monotonous process of compiling, archiving, uploading to TestFlight, and selecting testers off of the shoulders of our iOS engineers. Phew…that was fun and not too much effort! Now, back to my day-to-day.
All was good and right in the world of iOS development. Duane was pushing out new builds faster than Travis’ 3GS would function, all with such little effort. Until we all realized that something was missing. With all of these builds we are producing, what changed exactly? To someone using the app the only difference between build 204 and 207 is three. It was difficult for our team to know what new cool things had been introduced and what bugs had been resolved. So I set out on my next adventure: to automate release notes to TestFlight. Should be easy enough, right? WRONG!
You see, as good as the TestFlight plugin is, there wasn’t an environment variable that I was aware of to get the change log from the last successful revision to the latest revision. So I did what any engineer does in this situation; I searched Google. What I turned up were a bunch of hacks to do each of the things required to complete the desired goal I’d set out to accomplish. One by one, I started trying to piece it all together.
First, I needed to get the last successful build revision so I could use that to compare to the HEAD revision. What is cool is that Jenkins has an API (we love APIs) that you can use to pull this sort of value, plus a lot more. With a quick script, I’ve got what I need.
Second, I needed to get the log in a variable that I can later use in the release notes field. Luckily I had found a plugin for Jenkins called EnvInject. It allows you to give it a file path that it basically calls the source command on so it’s available for that build. I thought to myself, “Wow, this is already feeling really messy”, but I pressed on and went to the next step.
I updated the script to essentially
echo GIT_CHANGE_LOG=$(git log ....) into a file that I could have EnvInject source. Ran the build and voila! We have the log in the release notes.
Well, one line of them anyway.
Sigh…exactly what I thought would happen but was hoping wouldn’t. Git log inserts line breaks (naturally) and so I needed to escape them. With some trial and error and a lot of bash scripting help from Dan Lynn, I managed to get the line breaks escaped using this:
GIT_CHANGE_LOG=$(git log --pretty="$GIT_LOG_FORMAT" $LAST_SUCCESS_REV..HEAD) echo "GIT_CHANGE_LOG=$(git log --no-merges -- pretty="$GIT_LOG_FORMAT" $LAST_SUCCESS_REV..HEAD | while read line do echo $line\\n | tr -d n done)" > env_vars
I hope by now you understand why I feel so dirty about it. I mean LOOK AT THAT CODE! It’s absurd, asinine, just plain awful!
…however, it worked!
A good engineer never wants their code to look like that, but understands that sometimes you just can’t get around it. My advice to you is choose your battles wisely and fight them well. Although this left me feeling a little low and dirty, it works – and works great. And sometimes that’s all the win you need.
As of this article, I am happy to let you all know that Travis has since upgraded to an iPhone 5 and is happy as a lark.
(Also, no Apple devices were harmed in the hacking of this feature.)