Secret variables in Xcode AND your CI for fun and profit šŸ’Œ

Robin Malhotra
4 min readSep 4, 2017

--

Letā€™s preface this article with one tiny fact:

You should never have any sensitive information in your code repository.

There are no exceptions. API Keys, passwords, your email (including things like an integration test). Itā€™s really bad practice, and yet almost everyone does it. Even I still have an email or something lying around on a repo at work. Itā€™s such a rampant problem that GitHub has an official guide to remove sensitive info from a repo. Thereā€™s also 300k+ commits on GitHub on removing passwords alone (which is ineffectual but ĀÆ\_(惄)_/ĀÆ). Itā€™s such a rampant issue, that thereā€™s tons of famous open source projects dedicated to solving this problem.

Now that weā€™ve outlined the problem, we need to devise a solution thatā€™s both secure and easy. I think itā€™s as important to make The Wrong Thing Hard & The Right Thing Easy.

This would let developers (thatā€™s you and me šŸ‘‹) use secret keys for things like analytics, libraries, Integration Tests etc. And all of this without breaking our workflows (weā€™d still like to be able to hit āŒ˜+R/B/U, and to use existing workflows.)

Inspiration

Turns out, commercial CI providers have decent solutions to this problem (šŸ’Æ props to the amazing folks at BuddyBuild for thinking of this šŸ™Œ)

Huh, sounds interesting. Letā€™s try building this out on our own šŸ¤”.

We need to do the following:

  1. Get these variables from a secure source, both on our dev machines as well as on the CI.
  2. Inject them into Xcode (with code gen being one of the options).
  3. Use these variables in our code as usual. Nothing else should (ideally) be changed in our workflow/build pipeline.

Implementation

My first crack at this involved using Xcodeā€™s own environment variables, but sadly those have to be static and stored as part of your `.xcscheme`(which defeats the purpose of this article) šŸ˜¢

Most CI providers have a way to store encrypted variables (buddybuild: and travis) that are part of the parent processā€™ environment variables.

Two questions though:

  • How do you use these locally šŸ¤”?
  • How do you access environment variables in Xcode?

To figure this out, I looked at the setups of my colleagues (who usually work from the CLI and not from an IDE).

Their local setups usually involve a bash script, called `env-vars.sh` (thatā€™s in their .gitignore). It looks something like this:

The dev then `source`(imports any variables that are exported in the script) like so:

before running tests. On the CI, these variables are already provided, and so `env-vars.sh` isnā€™t even needed (which is why itā€™s in the gitignore ā€” so plain text information never reaches the repo).

Okay, awesome šŸ‘. Now, how do you get this working in Xcode? Turns out, thereā€™s one more piece left in the puzzle. Inspired from the buddybuild example, we can extract those environment variables -> Compile them into a Swift file using code generation.

Thereā€™s a bunch of solutions for codegen in Swift, but Sourcery by far seems to be the most supported. It also lets you pass in arguments, so itā€™s perfect for us šŸ™Œ

Now, the question arises: how do you run arbitrary scripts as part of your build process?

Luckily, Xcode has a feature called `Build Phases` that lets us do so.

Awesome. We can now add a build phase (a `Run script`) to our build phases. Note that you need to add this just before the `Compile Sources` step. This is because we need to add a new `.swift` file to compile and source our environment script (if it exists) and run Sourcery.

We need to add a `.stencil` template file to our project and provide Sourcery itā€™s parent directory so it can do its magic āœØ

Iā€™m not going to go over the details of how Sourcery works, because thatā€™s beyond the scope of this article.

Hereā€™s what the build script looks like:

Hereā€™s the script (for āŒ˜+Cā€™ing):

Now, run āŒ˜+B once so that a source file is generated. (Donā€™t worry if your build fails. Itā€™s because we havenā€™t added the generated file yet). Itā€™ll have the extension `.generated.swift`. Add it to your project and now youā€™ll be able to access these variables from inside your source code šŸš€.

PS: Do note that youā€™ll have to add `.generated.swift` to your `.gitignore` so your credentials wonā€™t accidentally end up in your repo šŸ˜….

Now, you can use the `testCreds` struct we declared earlier, seamlessly and easily without having to worry about the implementation details in your CI or dev machine šŸŽ‰. And your current workflow is unaffected (āŒ˜+R/B/U still works šŸŽŠ)

Thanks for reading my article! If you have any questions, feel free to contact me on twitter (Iā€™m @codeOfRobin at most places) or ask over Medium comments.

PPS: Iā€™m going to be working on some really cool stuff with Abheyraj Singh soon. Stay tuned!

--

--

Robin Malhotra

iOS dev and NBA fan. Ex- try! Swift speaker, now learning šŸ–„ dev