add an e2e build target for ios

Squashe commits:
- add an e2e build target for ios
- add correct sdk and destination for simulator
- fixup! add correct sdk and destination for simulator
- drop xcarchive_path since we are not using it
- temporarily bind ios build to macos-03
- Detect installed simulator SDK and use it.
- Signed-off-by: Jakub Sokołowski <jakub@status.im>

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2018-11-19 18:37:04 +01:00
parent 9e75b89d58
commit a91a799eb5
No known key found for this signature in database
GPG Key ID: 4EF064D0E6D63020
8 changed files with 151 additions and 47 deletions

1
.gitignore vendored
View File

@ -91,6 +91,7 @@ status-go-*.zip
ios/Pods ios/Pods
ios/StatusIm.xcworkspace ios/StatusIm.xcworkspace
.ruby-version .ruby-version
status-e2e/
#python #python
*.pyc *.pyc

View File

@ -26,45 +26,44 @@ pipeline {
} }
stage('Build') { stage('Build') {
parallel { parallel {
stage('MacOS') { stage('MacOS') { when { expression { btype != 'release' } }
when { expression { btype != 'release' } }
steps { script { steps { script {
osx = cmn.buildBranch('status-react/combined/desktop-macos') osx = cmn.buildBranch('status-react/combined/desktop-macos')
} } } } } }
stage('Linux') { stage('Linux') { when { expression { btype != 'release' } }
when { expression { btype != 'release' } }
steps { script { steps { script {
nix = cmn.buildBranch('status-react/combined/desktop-linux') nix = cmn.buildBranch('status-react/combined/desktop-linux')
} } } } } }
stage('Windows') { stage('Windows') { when { expression { btype != 'release' } }
when { expression { btype != 'release' } }
steps { script { steps { script {
win = cmn.buildBranch('status-react/combined/desktop-windows') win = cmn.buildBranch('status-react/combined/desktop-windows')
} } } } } }
stage('iOS') { steps { script { stage('iOS') { steps { script {
ios = cmn.buildBranch('status-react/combined/mobile-ios') ios = cmn.buildBranch('status-react/combined/mobile-ios')
} } } } } }
stage('iOS e2e') { steps { script {
iose2e = cmn.buildBranch('status-react/combined/mobile-ios', 'e2e')
} } }
stage('Android') { steps { script { stage('Android') { steps { script {
dro = cmn.buildBranch('status-react/combined/mobile-android') apk = cmn.buildBranch('status-react/combined/mobile-android')
} } } } } }
stage('Android e2e') { steps { script { stage('Android e2e') { steps { script {
e2e = cmn.buildBranch('status-react/combined/mobile-android', 'e2e') apke2e = cmn.buildBranch('status-react/combined/mobile-android', 'e2e')
} } } } } }
} }
} }
stage('Archive') { stage('Archive') {
steps { script { steps { script {
sh('rm -f pkg/*') sh('rm -f pkg/*')
if (btype != 'release') { if (btype != 'release') {
cmn.copyArts('status-react/combined/desktop-macos', osx.number) cmn.copyArts('status-react/combined/desktop-macos', osx.number)
cmn.copyArts('status-react/combined/desktop-linux', nix.number) cmn.copyArts('status-react/combined/desktop-linux', nix.number)
cmn.copyArts('status-react/combined/desktop-windows', win.number) cmn.copyArts('status-react/combined/desktop-windows', win.number)
} }
cmn.copyArts('status-react/combined/mobile-ios', ios.number) cmn.copyArts('status-react/combined/mobile-ios', ios.number)
cmn.copyArts('status-react/combined/mobile-android', dro.number) cmn.copyArts('status-react/combined/mobile-ios', iose2e.number)
cmn.copyArts('status-react/combined/mobile-android', e2e.number) cmn.copyArts('status-react/combined/mobile-android', apk.number)
cmn.copyArts('status-react/combined/mobile-ios', ios.number) cmn.copyArts('status-react/combined/mobile-android', apke2e.number)
dir('pkg') { dir('pkg') {
/* generate sha256 checksums for upload */ /* generate sha256 checksums for upload */
sh "sha256sum * | tee ${cmn.pkgFilename(btype, 'sha256')}" sh "sha256sum * | tee ${cmn.pkgFilename(btype, 'sha256')}"
@ -74,7 +73,7 @@ pipeline {
} }
stage('Upload') { stage('Upload') {
steps { script { steps { script {
e2eUrl = cmn.uploadArtifact(cmn.pkgFind('e2e.apk')) apke2eUrl = cmn.uploadArtifact(cmn.pkgFind('e2e.apk'))
apkUrl = cmn.uploadArtifact(cmn.pkgFind("${btype}.apk")) apkUrl = cmn.uploadArtifact(cmn.pkgFind("${btype}.apk"))
if (btype != 'release') { if (btype != 'release') {
dmgUrl = cmn.uploadArtifact(cmn.pkgFind('dmg')) dmgUrl = cmn.uploadArtifact(cmn.pkgFind('dmg'))
@ -85,18 +84,23 @@ pipeline {
appUrl = null appUrl = null
exeUrl = null exeUrl = null
} }
iose2eUrl = cmn.uploadArtifact(cmn.pkgFind("e2e.app.zip"))
/* special case for iOS Diawi links */ /* special case for iOS Diawi links */
ipaUrl = ios.getBuildVariables().get('DIAWI_URL') ipaUrl = ios.getBuildVariables().get('DIAWI_URL')
/* upload the sha256 checksums file too */ /* upload the sha256 checksums file too */
shaUrl = cmn.uploadArtifact(cmn.pkgFind('sha256')) shaUrl = cmn.uploadArtifact(cmn.pkgFind('sha256'))
/* add URLs to the build description */ /* add URLs to the build description */
cmn.setBuildDesc( cmn.setBuildDesc(
Apk: apkUrl, e2e: e2eUrl, iOS: ipaUrl, App: appUrl, Mac: dmgUrl, Win: exeUrl, Apk: apkUrl, Apke2e: apke2eUrl,
iOS: ipaUrl, iOSe2e: iose2eUrl,
App: appUrl, Mac: dmgUrl, Win: exeUrl,
) )
/* Create latest.json with newest nightly URLs */ /* Create latest.json with newest nightly URLs */
if (btype == 'nightly') { if (btype == 'nightly') {
cmn.updateLatestNightlies( cmn.updateLatestNightlies(
APK: apkUrl, IOS: ipaUrl, APP: appUrl, MAC: dmgUrl, WIN: exeUrl, SHA: shaUrl APK: apkUrl, IOS: ipaUrl,
APP: appUrl, MAC: dmgUrl,
WIN: exeUrl, SHA: shaUrl
) )
} }
} } } }
@ -105,7 +109,9 @@ pipeline {
steps { script { steps { script {
if (env.CHANGE_ID != null) { if (env.CHANGE_ID != null) {
cmn.githubNotify( cmn.githubNotify(
apk: apkUrl, e2e: e2eUrl, ipa: ipaUrl, app: appUrl, dmg: dmgUrl, win: exeUrl, apk: apkUrl, apke2e: apke2eUrl,
ipa: ipaUrl, iose2e: iose2eUrl,
app: appUrl, dmg: dmgUrl, win: exeUrl,
) )
} }
} } } }
@ -130,7 +136,7 @@ pipeline {
stage('Run e2e') { stage('Run e2e') {
when { expression { btype == 'nightly' } } when { expression { btype == 'nightly' } }
steps { script { steps { script {
e2eApk = e2e.getBuildVariables().get('SAUCE_URL') e2eApk = apke2e.getBuildVariables().get('SAUCE_URL')
build( build(
job: 'end-to-end-tests/status-app-nightly', wait: false, job: 'end-to-end-tests/status-app-nightly', wait: false,
parameters: [string(name: 'apk', value: "--apk=${e2eApk}")] parameters: [string(name: 'apk', value: "--apk=${e2eApk}")]

View File

@ -1,5 +1,5 @@
pipeline { pipeline {
agent { label 'fastlane' } agent { label 'macos' }
options { options {
timestamps() timestamps()
@ -7,8 +7,8 @@ pipeline {
timeout(time: 35, unit: 'MINUTES') timeout(time: 35, unit: 'MINUTES')
/* Limit builds retained */ /* Limit builds retained */
buildDiscarder(logRotator( buildDiscarder(logRotator(
numToKeepStr: '60', numToKeepStr: '90',
daysToKeepStr: '30', daysToKeepStr: '60',
artifactNumToKeepStr: '60', artifactNumToKeepStr: '60',
)) ))
} }
@ -66,7 +66,14 @@ pipeline {
} }
stage('Upload') { stage('Upload') {
steps { steps {
script { env.DIAWI_URL = mobile.ios.uploadToDiawi() } script {
switch (cmn.getBuildType()) {
case 'nightly':
env.DIAWI_URL = mobile.ios.uploadToDiawi(); break;
case 'e2e':
env.SAUCE_URL = mobile.ios.uploadToSauceLabs(); break;
}
}
} }
} }
} }

View File

@ -26,9 +26,9 @@ def uploadToPlayStore(type = 'nightly') {
def uploadToSauceLabs() { def uploadToSauceLabs() {
def changeId = common.getParentRunEnv('CHANGE_ID') def changeId = common.getParentRunEnv('CHANGE_ID')
if (changeId != null) { if (changeId != null) {
env.SAUCE_LABS_APK = "${changeId}.apk" env.SAUCE_LABS_NAME = "${changeId}.apk"
} else { } else {
env.SAUCE_LABS_APK = "im.status.ethereum-e2e-${GIT_COMMIT.take(6)}.apk" env.SAUCE_LABS_NAME = "im.status.ethereum-e2e-${GIT_COMMIT.take(6)}.apk"
} }
withCredentials([ withCredentials([
string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'),
@ -36,11 +36,11 @@ def uploadToSauceLabs() {
]) { ]) {
sh 'bundle exec fastlane android saucelabs' sh 'bundle exec fastlane android saucelabs'
} }
return env.SAUCE_LABS_APK return env.SAUCE_LABS_NAME
} }
def uploadToDiawi() { def uploadToDiawi() {
env.SAUCE_LABS_APK = "im.status.ethereum-e2e-${GIT_COMMIT.take(6)}.apk" env.SAUCE_LABS_NAME = "im.status.ethereum-e2e-${GIT_COMMIT.take(6)}.apk"
withCredentials([ withCredentials([
string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'), string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'),
]) { ]) {

View File

@ -144,7 +144,7 @@ def githubNotify(Map urls) {
message += "CI BUILD SUCCESSFUL in ${currentBuild.durationString} (${GIT_COMMIT})\n" message += "CI BUILD SUCCESSFUL in ${currentBuild.durationString} (${GIT_COMMIT})\n"
message += '| | | | | |\n' message += '| | | | | |\n'
message += '|-|-|-|-|-|\n' message += '|-|-|-|-|-|\n'
message += "| [Android](${urls.apk})([e2e](${urls.e2e})) | [iOS](${urls.ipa}) |" message += "| [Android](${urls.apk}) ([e2e](${urls.apke2e})) | [iOS](${urls.ipa}) ([e2e](${urls.iose2e})) |"
if (dmgUrl != null) { if (dmgUrl != null) {
message += " [MacOS](${urls.dmg}) | [AppImage](${urls.app}) | [Windows](${urls.win}) |" message += " [MacOS](${urls.dmg}) | [AppImage](${urls.app}) | [Windows](${urls.win}) |"
} else { } else {
@ -163,7 +163,13 @@ def githubNotify(Map urls) {
} }
def pkgFind(glob) { def pkgFind(glob) {
return findFiles(glob: "pkg/*${glob}")[0].path def fullGlob = "pkg/*${glob}"
def found = findFiles(glob: fullGlob)
if (found.size() == 0) {
sh 'ls -l pkg/'
error("File not found via glob: ${fullGlob} ${found.size()}")
}
return found[0].path
} }
def setBuildDesc(Map links) { def setBuildDesc(Map links) {

View File

@ -1,24 +1,23 @@
common = load('ci/common.groovy') cmn = load('ci/common.groovy')
def plutil(name, value) { def plutil(name, value) {
sh "plutil -replace ${name} -string ${value} ios/StatusIm/Info.plist" sh "plutil -replace ${name} -string ${value} ios/StatusIm/Info.plist"
} }
def compile(type = 'nightly') { def compile(type = 'nightly') {
def target = 'nightly' def target
switch (type) {
if (type == 'release') { case 'release': target = 'adhoc'; break;
target = 'adhoc' case 'testflight': target = 'release'; break;
case 'e2e': target = 'e2e'; break;
default: target = 'nightly';
} }
if (type == 'testflight') {
target = 'release'
}
/* configure build metadata */ /* configure build metadata */
plutil('CFBundleShortVersionString', common.version()) plutil('CFBundleShortVersionString', common.version())
plutil('CFBundleVersion', common.buildNumber()) plutil('CFBundleVersion', common.buildNumber())
plutil('CFBundleBuildUrl', currentBuild.absoluteUrl) plutil('CFBundleBuildUrl', currentBuild.absoluteUrl)
/* the dir might not exist */
sh 'mkdir -p status-e2e'
/* build the actual app */ /* build the actual app */
withCredentials([ withCredentials([
string(credentialsId: 'SLACK_URL', variable: 'SLACK_URL'), string(credentialsId: 'SLACK_URL', variable: 'SLACK_URL'),
@ -29,12 +28,16 @@ def compile(type = 'nightly') {
]) { ]) {
sh "bundle exec fastlane ios ${target}" sh "bundle exec fastlane ios ${target}"
} }
if (type != 'testflight') { /* rename built file for uploads and archivization */
def pkg = common.pkgFilename(type, 'ipa') def pkg = ''
sh "cp status-adhoc/StatusIm.ipa ${pkg}" if (type == 'e2e') {
return pkg pkg = cmn.pkgFilename('e2e', 'app.zip')
sh "cp status-e2e/StatusIm.app.zip ${pkg}"
} else if (type != 'testflight') {
pkg = cmn.pkgFilename(type, 'ipa')
sh "cp status-adhoc/StatusIm.ipa ${pkg}"
} }
return '' return pkg
} }
def uploadToDiawi() { def uploadToDiawi() {
@ -47,4 +50,20 @@ def uploadToDiawi() {
return diawiUrl return diawiUrl
} }
def uploadToSauceLabs() {
def changeId = cmn.getParentRunEnv('CHANGE_ID')
if (changeId != null) {
env.SAUCE_LABS_NAME = "${changeId}.app.zip"
} else {
env.SAUCE_LABS_NAME = "im.status.ethereum-e2e-${cmn.gitCommit()}.app.zip"
}
withCredentials([
string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'),
string(credentialsId: 'SAUCE_USERNAME', variable: 'SAUCE_USERNAME'),
]) {
sh 'bundle exec fastlane ios saucelabs'
}
return env.SAUCE_LABS_NAME
}
return this return this

View File

@ -27,7 +27,7 @@ def podUpdate() {
try { try {
wait(lockFile) wait(lockFile)
sh "touch ${lockFile}" sh "touch ${lockFile}"
sh 'pod update --silent' sh 'pod update --silent --no-ansi'
} finally { } finally {
sh "rm ${lockFile}" sh "rm ${lockFile}"
} }

View File

@ -27,9 +27,9 @@ end
# uploads `file` to sauce labs (overwrites if there is anoter file from the # uploads `file` to sauce labs (overwrites if there is anoter file from the
# same commit) # same commit)
def upload_to_saucelabs(file) def upload_to_saucelabs(file)
username = ENV["SAUCE_USERNAME"]
key = ENV["SAUCE_ACCESS_KEY"] key = ENV["SAUCE_ACCESS_KEY"]
unique_name = ENV["SAUCE_LABS_APK"] username = ENV["SAUCE_USERNAME"]
unique_name = ENV["SAUCE_LABS_NAME"]
url = "https://saucelabs.com/rest/v1/storage/" + username + '/' + unique_name + "?overwrite=true" url = "https://saucelabs.com/rest/v1/storage/" + username + '/' + unique_name + "?overwrite=true"
@ -71,6 +71,50 @@ def build_ios_adhoc
) )
end end
# builds an ios app with e2e configuration and put it
# to "status-e2e" output folder
def build_ios_e2e
# determine a simulator SDK installed
showsdks_output = sh('xcodebuild', '-showsdks')
simulator_sdk = showsdks_output.scan(/iphonesimulator\d\d?\.\d\d?/).first
match(
type: "adhoc",
force_for_new_devices: true,
readonly: true,
keychain_name: "login.keychain"
)
build_ios_app(
# Creating a build for the iOS Simulator
# 1. https://medium.com/rocket-fuel/fastlane-to-the-simulator-87549b2601b9
sdk: simulator_sdk,
destination: "generic/platform=iOS Simulator",
# 2. fixing compilations issues as stated in https://stackoverflow.com/a/20505258
# it looks like i386 isn't supported by React Native
xcargs: "ARCHS=\"x86_64\" ONLY_ACTIVE_ARCH=NO",
# 3. directory where to up StatusIm.app
derived_data_path: "status-e2e",
output_name: "StatusIm.app",
# -------------------------------------
# Normal stuff
scheme: "StatusIm",
workspace: "ios/StatusIm.xcworkspace",
configuration: "Release",
# Simulator apps can't be archived...
skip_archive: true,
# ...and we don't need an .ipa file for them, because we use .app directly
skip_package_ipa: true
)
zip(
path: "status-e2e/Build/Products/Release-iphonesimulator/StatusIm.app",
output_path: "status-e2e/StatusIm.app.zip",
verbose: false,
)
end
def upload_to_diawi(source) def upload_to_diawi(source)
diawi( diawi(
@ -84,7 +128,7 @@ end
platform :ios do platform :ios do
desc "`fastlane ios adhoc` - ad-hoc lane for iOS." desc "`fastlane ios adhoc` - ad-hoc lane for iOS."
desc "This lane is used PRs, Releases, etc." desc "This lane is used for PRs, Releases, etc."
desc "It creates an .ipa that can be used by a list of devices, registeded in the App Store Connect." desc "It creates an .ipa that can be used by a list of devices, registeded in the App Store Connect."
desc "This .ipa is ready to be distibuted through diawi.com" desc "This .ipa is ready to be distibuted through diawi.com"
lane :adhoc do lane :adhoc do
@ -92,6 +136,14 @@ platform :ios do
build_ios_adhoc build_ios_adhoc
end end
desc "`fastlane ios e2e` - e2e lane for iOS."
desc "This lane is used for SauceLabs end-to-end testing."
desc "It creates an .app that can be used inside of a iPhone simulator."
lane :e2e do
unlock_keychain_if_needed
build_ios_e2e
end
desc "`fastlane ios pr` - makes a new pr build" desc "`fastlane ios pr` - makes a new pr build"
desc "This lane builds a new adhoc build and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)" desc "This lane builds a new adhoc build and leaves an .ipa that is ad-hoc signed (can be uploaded to diawi)"
lane :pr do lane :pr do
@ -149,6 +201,19 @@ platform :ios do
upload_to_diawi("status-adhoc/StatusIm.ipa") upload_to_diawi("status-adhoc/StatusIm.ipa")
end end
desc "`fastlane ios saucelabs` - upload .app to sauce labs"
desc "also notifies in a GitHub comments"
desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`"
desc "expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable"
desc "expects to have a saucelabs username token as SAUCE_USERNAME env variable"
desc "expects to have a saucelabs destination name as SAUCE_LABS_NAME env variable"
desc "will fails if file isn't there"
lane :saucelabs do
upload_to_saucelabs(
"status-e2e/StatusIm.app.zip"
)
end
desc "This fastlane step cleans up XCode DerivedData folder" desc "This fastlane step cleans up XCode DerivedData folder"
lane :cleanup do lane :cleanup do
clear_derived_data clear_derived_data
@ -204,7 +269,7 @@ platform :android do
desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`" desc "expects to have an .apk prepared: `android/app/build/outputs/apk/release/app-release.apk`"
desc "expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable" desc "expects to have a saucelabs access key as SAUCE_ACCESS_KEY env variable"
desc "expects to have a saucelabs username token as SAUCE_USERNAME env variable" desc "expects to have a saucelabs username token as SAUCE_USERNAME env variable"
desc "expects to have a saucelabs destination name as SAUCE_LABS_APK env variable" desc "expects to have a saucelabs destination name as SAUCE_LABS_NAME env variable"
desc "will fails if file isn't there" desc "will fails if file isn't there"
lane :saucelabs do lane :saucelabs do
upload_to_saucelabs( upload_to_saucelabs(