Automating Cordova Workflow: xcodebuild Hangs During iOS Build

   DevOps and Tools

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

Opening 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.

Conclusion

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.


Like What You See?

Got any questions?