Create a gist now

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Tutorial: Cordova iOS build with fastlane, match and Jenkins

Cordova CI with Jenkins for iOS apps

Inspired by CI server on Mac OS for iOS using GitLab and Fastlane by @v_shevchyk we decided to write down our approach. This will be extended and improved over time.

So you want to deploy your Cordova app, but you hate opening xcode manually to archive and export and sign and cry? Try this. By this we mean we try to explain how to create the following CI (Jenkins) setup:

  • Build Cordova app
  • Create & sign your ipa file
  • Upload to HockeyApp (for Enterprise distribution)

We assume you already have a Jenkins setup including a Mac server which will build the app.
Even though we mention we support multiple environments (dev, qa, live, etc.) this manual so far only supports one env. Because the config.xml needs to be modified automatically as well.

Dependencies:

  • ruby
  • fastlane
  • fastlane/match
  • xcodeproj
  • npm
    • gulp
    • run-sequence
    • gulp-shell

Setup fastlane match for cert handling

  1. Install fastlane deps on the Mac Jenkins slave

    gem install fastlane
    gem install match
    gem install xcodeproj
    
  2. Go to your apple dev portal and create the needed app id https://developer.apple.com/account/ios/identifier/bundle This app will be used for the Cordova app and the certificate handling.

    Maybe fastlane produce can be used for this as well. We haven't tried yet.

  3. Setup cert handling with fastlane match

  4. Create the dist cert for your app

    MATCH_FORCE_ENTERPRISE="1" match enterprise -a "com.company.app" -r "ssh://git@your.sweet.url.com/project/certs.git" -u "your.apple.user@domain.com"
    

    This will create a cert and a provisiong profile for you in the apple portal and save it in the cretaed cert repo.

Modify your cordova project

  1. Add a folder called ci-config.
    Create subolders for each environment you might have, i.e. ci-config/dev.
    In these folder you add a file called app.properties:

    APP_ID=com.company.app
    APP_NAME=Your app name
    CODESIGNING_IDENTITY=iPhone Distribution: Your company
    

    In the future these infos will be used to create the correct version of your app.

  2. Add IOS/scripts/fix-xcodeproj.rb

    #!/usr/bin/env ruby
     
    require 'xcodeproj'
     
    path = ARGV.first
     
    xcproj = Xcodeproj::Project.open(path)
     
    # Fix schemes not being defined
    xcproj.recreate_user_schemes
     
    xcproj.save
    
  3. Add gulpfile.js

    var runSequence = require('run-sequence');
    var gulp = require('gulp');
    var shell = require('gulp-shell');
    
    var APP_ID          = process.env.APP_ID        || 'com.company.app';
    var APP_NAME        = process.env.APP_NAME      || 'Your app name';
    var JOB_NAME        = process.env.JOB_NAME      || 'dev-job';
    var BUILD_NUMBER    = process.env.BUILD_NUMBER  || 000;
    var KEYCHAIN        = JOB_NAME + '-' + BUILD_NUMBER + '.keychain';
    
    gulp.task('cordova:create', shell.task([
        'mkdir build-cordova',
        // cordova create build-cordova [FOLDER_NAME] [APP_ID] [APP_NAME]
        'cordova create build-cordova "' + APP_ID + '" "' + APP_NAME + '"',
        'cp config.xml build-cordova/config.xml'
    ]));
    
    gulp.task('cordova:platform:ios', shell.task([
        'cordova platform add ios' // specify version if needed like ios@3.9.2
    ], {
        cwd: 'build-cordova'
    }));
    
    gulp.task('cordova:plugins:ios', shell.task([
        'cordova plugin add ../IOS/plugins/*/',
        'cordova plugin add https://github.com/brodysoft/Cordova-SQLitePlugin'
    ], {
        cwd: 'build-cordova'
    }));
    
    gulp.task('cordova:prepare:ios', shell.task([
        'cp -rf ../build/* www/',    // in build should be your built frontend code
        'cp -rf ../config.xml www/', // config.xml for your project should be in root
        'cordova prepare'
    ], {
        cwd: 'build-cordova'
    }));
    
    gulp.task('xcode:schema', shell.task([
        '../IOS/scripts/fix-xcodeproj.rb "platforms/ios/' + APP_NAME + '.xcodeproj"', //internal xcodeproj name defined by config.xml!
        'xcodebuild -list -project "platforms/ios/' + APP_NAME + '.xcodeproj"'
    ], {
        cwd: 'build-cordova'
    }));
    
    gulp.task('xcode:ipa', shell.task([
        'security -v create-keychain -p cici1234 ' + KEYCHAIN,
        
        'security list-keychains -s ' + KEYCHAIN,
        
        'security -v unlock-keychain -p cici1234 ' + KEYCHAIN,
        'fastlane dist',
        'security -v delete-keychain ' + KEYCHAIN,
    
        'rm -v ~/Library/MobileDevice/Provisioning\\ Profiles/*'
    ]));
    
    gulp.task('build:ci:ios', function(callback) {
        runSequence(
            [
            	'[YOUR_TASK_TO_BUILD_FE_CODE_INTO_BUILD_FOLDER]',
            	'[YOUR_TASK_TO_CLEAN_EXISITING_BUILD-CORDOVA_FOLDER]'
            ],
            'cordova:create',
            'cordova:platform:ios',
            'cordova:plugins:ios',
            'cordova:prepare:ios',
            'xcode:schema',
            'xcode:ipa',
            callback);
    });
    
    
  4. Add build.sh

    #!/bin/bash
    # build.sh [ENV]
    
    # echo jenkins env vars
    env
    
    # set PATH and LANG
    export PATH="/usr/local/bin:$PATH"
    export LC_ALL="en_US.UTF-8"
    export LANG="en_US.UTF-8"
    
    # general deps
    npm install
    
    # build
    gulp build:ci:ios
    
  5. Add fastlane/Appfile

    team_id "[YOU_APPLE_TEAM_ID]"  # Developer Portal Team ID
    
  6. Add fastlane/fastfile

    fastlane_version "1.64.0"
    
    default_platform :ios
    
    platform :ios do
      before_all do
        ENV["MATCH_FORCE_ENTERPRISE"] = "1"
      end
    
      desc "Deploy a new version to HockeyApp"
      lane :dist do
        match(
          git_url: ENV["GIT_CERTS"],
          type: "enterprise",
          username: ENV["APPLE_ID"],
          app_identifier: ENV["APP_ID"],
          keychain_name: ENV["JOB_NAME"] + '-' + ENV["BUILD_NUMBER"] + '.keychain',
          readonly: "true"
        )
        gym(
          project: './build-cordova/platforms/ios/' + ENV["APP_NAME"] + '.xcodeproj',
          scheme: ENV["APP_NAME"],
          use_legacy_build_api: "true",
          export_method: "enterprise",
          codesigning_identity: ENV["CODESIGNING_IDENTITY"]
        )
        hockey(
          api_token: ENV["HA_API_TOKEN"],
          ipa: ENV["APP_NAME"] + '.ipa',
          notify: "0"
        )
      end
    end
    

Setup the Mac Server

  1. Create a Jenkins user on the Mac Jenkins slave

  2. Create a default keychain This is only needed because one default keychain is needed in the system. The default keychain will not be used though.

  3. ...

Setup the Jenkins task

  1. Restrict where this project can be run
    Set to your Mac slave

  2. This build is parameterized
    Choice parameter
    Check and select to create a environment selectbox.

    Name: ENV
    Chocies:

    dev  
    qa
    live
    

    Description:
    This will create a build with a specific environment configuration. This includes mainly App ID and signing identity for xcode.

  3. Source Code Management Set to your git repo and branch

  4. Inject environment variables to the build process

    • Set Properties File Path to ./ci-config/$ENV/app.properties
    • Set Properties Content:
    MATCH_PASSWORD=[ssl password of your match cert repo]
    GIT_CERTS=ssh://git@your.sweet.url.com/project/certs.git
    APPLE_ID=your.apple.user@domain.com
    HA_API_TOKEN=[your Hockey App API token]
    
  5. Execute shell

    ./build.sh $ENV
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment