Automating Cordova Workflow: xcodebuild Hangs During iOS Build
The Cordova Command Line Interface (CLI) has come a long way since it was introduced with Cordova 3.0 back in the summer of 2013. Developers can use this to create, configure, and build apps – greatly reducing the need to directly use “traditional” tools such as Xcode for iOS and Eclipse for Android.
The Cordova CLI has also enabled a much more straightforward Continuous Integration (CI) workflow for building and distributing Cordova based apps. At Modus Create, we’ve had success using the Jenkins and Circle CI products to build our Cordova apps for Android and iOS, often paired with HockeyApp to push the resulting binaries to test users for on-device testing.
Whilst the Cordova CLI gets us all of the way to a distributable .apk file on Android, we still have to use the Apple command line tool
xcodebuild to archive and sign our .ipa file for iOS apps. In doing so, we found that
xcodebuild can hang when working with projects generated by the Cordova CLI. However, there is a relatively simple fix for this that allows us to complete the iOS CI workflow without ever having to resort to opening Xcode interactively.
New Cordova Projects Make xcodebuild Hang
If we create a new Cordova project, and attempt to use
xcodebuild with it, we find that
xcodebuild hangs and never exits. To demonstrate this, try the following in a Terminal window:
cordova create someapp com.mycompany.someapp SomeApp cd someapp cordova platform add ios cordova build ios cd platforms/ios xcodebuild -list
This creates a new app, adds the iOS platform to it, builds iOS, then attempts to use
xcodebuild to list the schemes in the project. A scheme is a build configuration, and
xcodebuild -list should list all schemes in the project. However, the command hangs and never returns because the Xcode project that the Cordova CLI created for us seems to lack any schemes.
So, rather than a list of schemes, the output we see from
xcodebuild -list looks like:
$ xcodebuild -list Information about project "SomeApp": Targets: SomeApp Build Configurations: Debug Release If no build configuration is specified and -scheme is not passed then "Release" is used.
The command never returns. This also applies when we try to use
xcodebuild to archive the project and generate our .ipa file.
This causes issues with a CI workflow, as we can’t check out our project from source control then generate an .ipa for upload to HockeyApp, TestFlight, or other app testing platforms using scripts alone.
Solution: Create Schemes in Cordova’s iOS Project
There are two ways we can create the schemes that
xcodebuild needs to find in our Cordova iOS project.
1. Open the Project with the XCode IDE
platforms/ios/SomeApp.xcodeproj with the Xcode IDE, then simply quitting Xcode will fix the issue, and
xcodebuild -list now does what we would expect and returns when finished:
$ xcodebuild -list Information about project "SomeApp": Targets: SomeApp Build Configurations: Debug Release If no build configuration is specified and -scheme is not passed then "Release" is used. Schemes: SomeApp CordovaLib
This is great, but doesn’t help with our goal to have a CI workflow, as a CI server isn’t going to open Xcode interactively, then quit it. We need something that is more scriptable and performed as part of our build process.
Thankfully, we can achieve this using some relatively simple scripting, which is our second option.
2. Add Schemes Programmatically Using a Build Hook Script
Opening Xcode to have it generate the required schemes works well enough when building on a developer’s local machine, but not when using a CI server. Here, we want a repeatable, automated, “lights out” process that runs without user intervention.
It is also considered bad practice to commit the
platforms folder of a Cordova project to source control, because everything in there can — and should — be generated by the Cordova CLI. Given that the Xcode project files live in the
platforms folder, we should really use a script based approach to maintain a repeatable process.
For this, we’ll need something that allows us to perform housekeeping tasks on Xcode projects using command line tools. Fortunately, the Xcodeproj Ruby Gem was designed to do just that, and allows us to create schemes in a project.
Installation of the Xcodeproj Gem is simple:
sudo gem install xcodeproj
Then, all we need to do is write a small script to use it to add the schemes to the project, and to have that run whenever the iOS platform is added to the project. The Cordova CLI supports extensions to customize existing actions using hook scripts. We can add a script named
fix_xcode_schemes.rb to the
hooks folder of the Cordova project, and save the following into it:
#!/usr/bin/env ruby require 'xcodeproj' xcproj = Xcodeproj::Project.open("platforms/ios/schemedemo.xcodeproj") xcproj.recreate_user_schemes xcproj.save
This will modify the project to add the schemes that
xcodebuild needs to work properly. We need to make sure this script can be executed so let’s set the permissions on it appropriately from the terminal:
chmod 755 hooks/fix_xcode_schemes.rb
Then to make sure our script is run at the appropriate point (after
cordova platform add ios completes), we need to configure the hook in the project’s
config.xml file by adding the following
hook element inside the iOS platform element.
<platform name="ios"> <hook type="after_platform_add" src="hooks/fix_xcode_schemes.rb" /> ... </platform>
Now, whenever we first clone the project from source control, our custom script will fix the schemes for us as soon as the iOS platform is installed, and we can then build the project, archive and sign an .ipa all at the command line or under the control of our CI server.
Out of the box, a newly created Cordova project does not enable a Continuous Integration workflow because the auto generated Xcode project lacks scheme definitions that
xcodebuild requires. This causes
xcodebuild to freeze and stops the build process.
We have demonstrated that this can easily be fixed using the Xcodeproj Ruby Gem and a Cordova build hook script to add the missing schemes. This allows us to build the project entirely from the command line under the control of a Continuous Integration server such as Jenkins or Circle CI.
To help you get your Cordova project builds automated, I’ve created an example project with the hook script configured, and have made it available via GitHub.
Maybe you’ve hit similar issues with automating your Cordova workflow, or perhaps you’ve found some better solutions. Let us know in the comments or on Twitter; we’d love to hear from you.