Swift, Plist and Two Smoking Scripts
Starting with iOS 10 developers should provide descriptions for using user private data otherwise apps will crash. But if you leave a value field empty, everything will work until you send a new build to iTunes Connect. The build disappears and you (or your client) get an email about it:
We have discovered one or more issues with your recent delivery for “Your app”. To process your delivery, the following issues must be corrected:
Missing Info.plist key — This app attempts to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSCalendarsUsageDescription key with a string value explaining to the user how the app uses this data.
Once these issues have been corrected, you can then redeliver the corrected binary.
The App Store team
There are at least three ways to check it:
- Check manually. Definitely, no.
- Write fastlane lane. It’s a good approach if you already use fastlane and know Ruby. Fastlane supports Swift as well but with having additional Xcode project.
- Write Build Phase script. That’s my bro!
There are a lot of good Build Phase examples: linting code with SwiftLint, copying resources for CocoaPods frameworks etc. Most of them written in Bash. But wait, what about Swift? Can I use Swift for scripting?
One of the simplest implementations of our task is to check that Swift files contain classes from system frameworks. Imports are not a guarantee that there is related logic, because we can forget to remove unused ones.
Let’s open a project target and select Build Phases tab. I don’t like to write scripts here for the following reasons:
- Bad diffs. It’s hard to read code in one line:
Unless you’re a lucky owner of this device:
- No syntax highlighting and autocompletion
- Can’t reuse the script for multiple targets. You have to copy-paste script code to every target and make changes in every copy.
Let’s create a separate file for our script:
Don’t forget to make the script executable by changing its permission:
chmod +x Scripts/usage_description_check.swift
Next, add a Build Phase script with a path to our script:
Note: Check “Run script only when installing” flag to run it only during Archive action.
Our journey has started! 🚀
Add this line at the top of the script file:
#!/usr/bin/env xcrun --sdk macosx swift
The line launches Swift REPL (Read-Eval-Print-Loop) first and the rest of our script actually compiles in Swift environment. Pay attention to
--sdk option. By default
xcrun gets SDK from
SDKROOT environment variable (remember this guy, we will return to it a bit later). We should enforce
xcrun to use macOS SDK.
Next, we should get a project directory and product settings from
Info.plist. We can use environment variables for it via
Note: To get all environment variables, just enable
Show environment variables in build log under the Build Phase.
.swift file URLs:
compactMap is a replacement of
flatMap in Swift 4.1. If you use previous Swift versions, just replace it with
Next, we should add patterns for descriptions. As described above, we will find classes from system frameworks.
let patterns = ["EKEventStore": "NSCalendarsUsageDescription"]
After that we enumerate Swift files and get all the required keys for used patterns:
Next we should check that the product settings contain required keys and related non-empty descriptions:
Nice! But it’s not enough. How to show Xcode warnings and errors from scripts? That’s very easy! You only need to add
error: before your print message and Xcode will automatically annotate it as a warning or an error accordingly:
print("error: Error message")
Let’s replace our comments with the correct error messages:
// Missing key
print("error: Missing \(key)")
// Empty description
print("error: Empty description for \(key)")
Today we have learned how to use Swift for Build Phase scripts. I hope you like the tips in the article. If you have questions related to the topic, feel free to ask it in comments here or on Twitter @iosartem. Whole case project with a full script code is available on Github.
Thanks for reading!
Bearded iOS developer from Siberia 👨🏻💻. Work at Rosberry, love Swift, open-source and my cat.