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.
- ruby
- fastlane
- fastlane/match
- xcodeproj
- npm
- gulp
- run-sequence
- gulp-shell
-
Install fastlane deps on the Mac Jenkins slave
gem install fastlane gem install match gem install xcodeproj
-
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.
-
Setup cert handling with fastlane match
-
Create the dist cert for your app
MATCH_FORCE_ENTERPRISE="1" match enterprise -a "com.company.app" -r "ssh://[email protected]/project/certs.git" -u "[email protected]"
This will create a cert and a provisiong profile for you in the apple portal and save it in the cretaed cert repo.
-
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 calledapp.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.
-
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
-
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 [email protected] ], { 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); });
-
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
-
Add
fastlane/Appfile
team_id "[YOU_APPLE_TEAM_ID]" # Developer Portal Team ID
-
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
-
Create a Jenkins user on the Mac Jenkins slave
-
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.
-
...
-
Restrict where this project can be run
Set to your Mac slave -
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.
-
Source Code Management
Set to your git repo and branch -
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://[email protected]/project/certs.git [email protected] HA_API_TOKEN=[your Hockey App API token]
- Set
-
Execute shell
./build.sh $ENV