iOS Xcodebuild Script
Quite a departure from my normal topics, I’ve had the opportunity to work on an iOS (Swift) app recently (with IBM MobileFirst Platform Foundation handling the server-side logic). Working through various articles and StackOverflow questions to try to find a “happy medium” for the level of build process we needed, I thought what I’d written up for our team could prove useful to others working through something similar.
Goal
Our basic need is for a repeatable build script, that we can commit to Source Control, and that can handle a few differences between “Production” and “Test” builds. We have one distribution mechanism that we use internally, where we produce an ipa file, and we produce an xcarchive for Apple TestFlight or the App Store.
Useful Links
- Migrating iOS App Through Multiple Environments: more involved process and more capability than what we’re currently doing, but good detail about what you might want/need to do
- Best way to manage Development, Testing and Production iOS builds with different settings: this one offered the crucial advice that if you’re using CocoaPods, you need to re-install after creating a new Configuration
- Swift Conditional Compilation blocks
- Apparently the only current reference for the xcodebuild command-line tool we’re using is through the MacOS man pages. The ones I’ve found online are outdated.
- Using xcodebuild To Export a .ipa From an Archive: a bit outdated, but original article that enabled us to produce an earlier build script
- Read a plist in Swift
- exportOptionsPlist Q&A
- Building from the Command Line with Xcode FAQ
- Xcode 9 distribution build fails because format of exportOptions.plist has changed in new release: additional change since Xcode 9
Approach Overview
The current approach is very minimal, using only the single default Xcode Scheme, the 2 existing Debug and Release Configurations, and a new Test Configuration copied from the Release one.
A few Conditional Compilation code blocks will choose different settings or make different calls, based on which Configuration is being built.
Xcode Setup
Create a new Configuration
Under the Project’s (not the Target’s) Info tab, in the Configurations section, we’ve created an additional Test Configuration, copied from “Duplicate Release Configuration”.
Note, also, just below that section, the “Use Release for command-line builds”. We’ll explicitly override it in our build script, but it’s worth noting that it exists.
Since we’re currently using CocoaPods for the Mobile First SDK, we also had to take the steps described in the article above:
Note that if you use Cocoapods then you will need to set the configurations back to none, delete the contents of the Pods folder in your project (Not the Pods project) and re-run
pod install
.
(I don’t know what “set the configurations back to none” means. I didn’t do that.)
Configuration-specific Properties
I think under either the main Project or Target, Build Settings tab, near the bottom of the tab is the Swift Compiler - Custom Flags section. “Inside” that is an Active Compilation Conditions subsection. These flags can later be used in Conditional Compilation blocks.
By default, the Debug entry has a DEBUG flag set.
We’ve added a PROD flag to the Release entry.
Note: We’re not currently changing the application Bundle ID, name, or icons for the different Configurations. So we won’t be able to have multiple builds installed on a device at the same time. It could be useful, but would require quite a bit more effort and complication.
Change Configurations
When you run an application from within Xcode, you get the Debug Configuration by default. If you wish to run in a different Configuration, you can change this by editing the Scheme and changing the selected Configuration for the Run “task”. This is a useful way to test your Production settings from the Xcode simulator before building them to push out to a store or device.
Note: I think this setting is stored in your user-specific settings, which we’ve excluded from Git. So it won’t want a commit or affect others.
Build Script
I’m sharing a simplified version of the script itself here. I’ll note the main items below.
Build Configuration
The script currently defaults to building the Test Configuration, and you have to specify Release on the command-line if you want to build that instead. At a later phase of the project, we might default the other direction.
The script uses three different executions of the xcodebuild
command:
clean
archive
-exportArchive
(No, I don’t know why the first two are commands, and the latter is an option.)
All three have the -configuration
option explicitly specified, to use either the Configuration name passed-in from the command-line or the default defined in the script itself. I’m not 100% certain all three of them need this, but when only the clean
command had it (what the referenced article showed), the build did not do what was expected. Which makes sense. It’s probably just the archive step that also needs it, but I haven’t tested this.
The script also names the archive (the .xcarchive
directory) MyApp-_ConfigurationName_ if it’s not the Release Configuration. e.g. MyApp-Test. But the ipa file is always named MyApp.ipa
. I don’t see a way to change this with this configuration. It may be using the Scheme name.
Scheme
As mentioned above, we still have a single Scheme, so that is hardcoded in the script.
Workspace
Since CocoaPods projects use an Xcode Workspace rather than a Project, we use the -workspace
option.
exportOptionsPlist
Finally, the earlier -exportFormat
option is now gone, in its place is -exportOptionsPlist
, which uses a plist file to specify build options. That is currently the file build.plist, with the option:
method=development
I think since we only build the ipa file when we’re deploying through a non-Apple mechanism, we don’t need to worry about this when we just pass the xcarchive to Apple.
If we do ever find a need to use different settings here, I think we’d create additional build-configuration.plist
files(s) and specify the correct one in the script.
Note: It seems the only reliable way to see the options for the build plist file is to run xcodebuild -help
on a Mac.
Update for Xcode 9
For Xcode 9, it seems we now also need to add a provisioningProfiles
dictionary to the build plist file. This answer on Stack Overflow provided useful details and an example.
Code
Environment-specific Properties
We’re using the approach of putting environment-specific properties into application plist files:
- MyApp-test.plist
- MyApp-production.plist
For our application, this is currently just a couple of URLs that are different in the two environments. Other items can be added as necessary.
Access to these properties is encapsulated through static methods in a simple AppProperties class, where the next technique is used.
Conditional Code
Using Swift Conditional Compilation blocks, we can change which plist files get loaded, omit sections of code, etc.
Note that we’re currently only using flags, and we currently only have the DEBUG (created by default in the Debug Configuration) and PROD flags available. Our current logic uses only the PROD flag, which is only defined to the Release Configuration, so we only have Production vs. non-Production cases today.
Where this is used today is in choosing which application plist to load:
class AppProperties {
#if PROD
private static let sharedInstance = AppProperties("MyApp-production")
#else
private static let sharedInstance = AppProperties("MyApp-test")
#endif
...
}