From d38f74fdfdfc2b31ebc5724dae3f32b1fef45984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Soko=C5=82owski?= Date: Tue, 14 Aug 2018 14:09:52 -0400 Subject: [PATCH] create Jenkinsfile.combined and extract build teps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jakub SokoĊ‚owski --- .gitignore | 3 + ci/Jenkinsfile.android | 72 +++++++++++++ ci/Jenkinsfile.combined | 115 ++++++++++++++++++++ ci/Jenkinsfile.ios | 65 ++++++++++++ ci/Jenkinsfile.linux | 47 ++++++++ ci/Jenkinsfile.macos | 47 ++++++++ ci/Jenkinsfile.nightly_fastlane | 5 +- ci/Jenkinsfile.release | 14 +-- ci/common.groovy | 60 +++++++++++ ci/desktop.groovy | 183 ++++++++++++++++++++++++++++++++ ci/mobile.groovy | 102 ++++++++++++++++++ fastlane/Fastfile | 8 +- scripts/build_no.sh | 52 +++++---- 13 files changed, 743 insertions(+), 30 deletions(-) create mode 100644 ci/Jenkinsfile.android create mode 100644 ci/Jenkinsfile.combined create mode 100644 ci/Jenkinsfile.ios create mode 100644 ci/Jenkinsfile.linux create mode 100644 ci/Jenkinsfile.macos create mode 100644 ci/common.groovy create mode 100644 ci/desktop.groovy create mode 100644 ci/mobile.groovy diff --git a/.gitignore b/.gitignore index 3c633dc108..7e40b27b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,6 @@ fastlane/README.md /package-lock.json /package.json /.re-natal + +# Jenkins +pkg diff --git a/ci/Jenkinsfile.android b/ci/Jenkinsfile.android new file mode 100644 index 0000000000..3d6a716603 --- /dev/null +++ b/ci/Jenkinsfile.android @@ -0,0 +1,72 @@ +pipeline { + agent { label 'macos' } + + options { + buildDiscarder(logRotator( + numToKeepStr: '10', + daysToKeepStr: '30', + artifactNumToKeepStr: '4', + )) + } + + environment { + LANG = 'en_US.UTF-8' + LANGUAGE = 'en_US.UTF-8' + LC_ALL = 'en_US.UTF-8' + FASTLANE_DISABLE_COLORS = 1 + REALM_DISABLE_ANALYTICS = 1 + ANDROID_HOME = '/usr/local/share/android-sdk' + ANDROID_SDK_ROOT = '/usr/local/share/android-sdk' + ANDROID_NDK = '/Users/jenkins/android-ndk-r10e' + ANDROID_NDK_HOME = '/Users/jenkins/android-ndk-r10e' + } + + stages { + stage('Prep') { + steps { + script { + /* Necessary to load methods */ + mobile = load 'ci/mobile.groovy' + mobile.prepDeps() + } + } + } + stage('Tests') { + steps { + script { mobile.runTests() } + } + } + stage('Build') { + steps { + script { mobile.leinBuild() } + } + } + stage('Compile') { + steps { + script { apk = mobile.compileAndroid(e2e: params.RUN_E2E) } + } + } + stage('Bundle') { + when { expression { !params.RUN_E2E } } + steps { + script { mobile.bundleAndroid() } + } + } + stage('Archive') { + when { expression { !params.RUN_E2E } } + steps { + script { archiveArtifacts apk } + } + } + stage('Run e2e') { + when { expression { params.RUN_E2E } } + steps { script { + apk = mobile.uploadSauceLabs() + build( + job: 'end-to-end-tests/status-app-nightly', wait: false, + parameters: [string(name: 'apk', value: "--apk=${apk}")] + ) + } } + } + } +} diff --git a/ci/Jenkinsfile.combined b/ci/Jenkinsfile.combined new file mode 100644 index 0000000000..f238f284d7 --- /dev/null +++ b/ci/Jenkinsfile.combined @@ -0,0 +1,115 @@ +pipeline { + agent { label 'master' } + + options { + disableConcurrentBuilds() + buildDiscarder(logRotator( + numToKeepStr: '10', + daysToKeepStr: '30', + artifactNumToKeepStr: '10', + )) + } + + stages { + stage('Tag') { + steps { script { + common = load('ci/common.groovy') + /* to avoid race conditions in parallel builds */ + print "Build Number: ${common.tagBuild()}" + } } + } + stage('Build') { + parallel { + stage('MacOS') { + steps { script { + osx = build('status-react/combined/desktop-macos') + } } + } + stage('Linux') { + steps { script { + nix = build('status-react/combined/desktop-linux') + } } + } + stage('iOS') { + steps { script { + ios = build('status-react/combined/mobile-ios') + } } + } + stage('Android') { + steps { script { + dro = build('status-react/combined/mobile-android') + } } + } + stage('Android e2e') { + steps { + build('status-react/combined/mobile-android-e2e') + } + } + } + } + stage('Archive') { + steps { + sh('rm -f pkg/*') + copyArtifacts( + projectName: 'status-react/combined/mobile-ios', target: 'pkg', + flatten: true, selector: specific("${ios.number}") + ) + copyArtifacts( + projectName: 'status-react/combined/mobile-android', target: 'pkg', + flatten: true, selector: specific("${dro.number}") + ) + copyArtifacts( + projectName: 'status-react/combined/desktop-macos', target: 'pkg', + flatten: true, selector: specific("${osx.number}") + ) + copyArtifacts( + projectName: 'status-react/combined/desktop-linux', target: 'pkg', + flatten: true, selector: specific("${nix.number}") + ) + archiveArtifacts('pkg/*') + } + } + stage('Upload') { + when { expression { params.PUBLISH } } + steps { script { + def pkg = "pkg/StatusIm-${GIT_COMMIT.take(6)}" + apkUrl = common.uploadArtifact("${pkg}.apk") + ipaUrl = common.uploadArtifact("${pkg}.ipa") + dmgUrl = common.uploadArtifact("${pkg}.dmg") + appUrl = common.uploadArtifact("${pkg}.AppImage") + } } + } + stage('Notify') { + steps { + slackSend( + message: ( + "Build success! "+ + "<${currentBuild.absoluteUrl}|${currentBuild.displayName}> "+ + "(${currentBuild.durationString})\n"+ + (params.PUBLISH ? + "APK: ${apkUrl}\n"+ + "IPA: ${ipaUrl}\n"+ + "DMG: ${dmgUrl}\n"+ + "APP: ${appUrl}\n" + : '') + ), + color: 'good' + ) + } + } + stage('Publish') { + when { expression { params.PUBLISH } } + steps { + build( + job: 'misc/status-im.github.io-update_env', + parameters: [ + [name: 'APK_URL', value: apkUrl, $class: 'StringParameterValue'], + [name: 'IOS_URL', value: ipaUrl, $class: 'StringParameterValue'], + [name: 'DMG_URL', value: dmgUrl, $class: 'StringParameterValue'], + [name: 'NIX_URL', value: appUrl, $class: 'StringParameterValue'] + ] + ) + } + } + } +} diff --git a/ci/Jenkinsfile.ios b/ci/Jenkinsfile.ios new file mode 100644 index 0000000000..5f3c91caa5 --- /dev/null +++ b/ci/Jenkinsfile.ios @@ -0,0 +1,65 @@ +pipeline { + agent { label 'fastlane' } + + parameters { + booleanParam( + name: 'RUN_E2E', + defaultValue: false, + description: 'If true triggers end-to-end tests.' + ) + } + + options { + buildDiscarder(logRotator( + numToKeepStr: '10', + daysToKeepStr: '30', + artifactNumToKeepStr: '1', + )) + } + + environment { + LANG = 'en_US.UTF-8' + LANGUAGE = 'en_US.UTF-8' + LC_ALL = 'en_US.UTF-8' + FASTLANE_DISABLE_COLORS=1 + REALM_DISABLE_ANALYTICS=1 + } + + stages { + stage('Prep') { + steps { + script { + /* Necessary to load methods */ + mobile = load 'ci/mobile.groovy' + mobile.prepDeps() + } + } + } + stage('Tests') { + steps { + script { mobile.runTests() } + } + } + stage('Build') { + steps { + script { mobile.leinBuild() } + } + } + stage('Compile') { + steps { + script { api = mobile.compileiOS() } + } + } + stage('Archive') { + steps { + script { archiveArtifacts api } + } + } + stage('Upload') { + when { expression { params.RUN_E2E } } + steps { + script { env.DIAWI_URL = uploadiOS() } + } + } + } +} diff --git a/ci/Jenkinsfile.linux b/ci/Jenkinsfile.linux new file mode 100644 index 0000000000..1747d94be4 --- /dev/null +++ b/ci/Jenkinsfile.linux @@ -0,0 +1,47 @@ +pipeline { + agent { label 'linux-new' } + + options { + buildDiscarder(logRotator( + numToKeepStr: '10', + daysToKeepStr: '30', + artifactNumToKeepStr: '1', + )) + } + + environment { + LANG = 'en_US.UTF-8' + LANGUAGE = 'en_US.UTF-8' + LC_ALL = 'en_US.UTF-8' + } + + stages { + stage('Prep') { + steps { + script { + /* Necessary to load methods */ + desktop = load 'ci/desktop.groovy' + desktop.prepDeps() + } + } + } + stage('Build') { + steps { + script { desktop.buildClojureScript() } + } + } + stage('Compile') { + steps { + script { desktop.compileLinux() } + } + } + stage('Bundle') { + steps { + script { app = desktop.bundleLinux() } + } + } + stage('Archive') { + steps { archiveArtifacts app } + } + } +} diff --git a/ci/Jenkinsfile.macos b/ci/Jenkinsfile.macos new file mode 100644 index 0000000000..b19f194228 --- /dev/null +++ b/ci/Jenkinsfile.macos @@ -0,0 +1,47 @@ +pipeline { + agent { label 'macos' } + + options { + buildDiscarder(logRotator( + numToKeepStr: '10', + daysToKeepStr: '30', + artifactNumToKeepStr: '1', + )) + } + + environment { + LANG = 'en_US.UTF-8' + LANGUAGE = 'en_US.UTF-8' + LC_ALL = 'en_US.UTF-8' + } + + stages { + stage('Prep') { + steps { + script { + /* Necessary to load methods */ + desktop = load 'ci/desktop.groovy' + desktop.prepDeps() + } + } + } + stage('Build') { + steps { + script { desktop.buildClojureScript() } + } + } + stage('Compile') { + steps { + script { desktop.compileMacOS() } + } + } + stage('Bundle') { + steps { + script { dmg = desktop.bundleMacOS() } + } + } + stage('Archive') { + steps { archiveArtifacts dmg } + } + } +} diff --git a/ci/Jenkinsfile.nightly_fastlane b/ci/Jenkinsfile.nightly_fastlane index 0abe47725a..d60accd860 100644 --- a/ci/Jenkinsfile.nightly_fastlane +++ b/ci/Jenkinsfile.nightly_fastlane @@ -150,7 +150,10 @@ timeout(90) { } stage('Build (Android) for e2e tests') { - sh 'cd android && mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/app-release.original.apk && ENVFILE=.env.e2e ./gradlew assembleRelease' + sh 'cd android && \ + mv app/build/outputs/apk/release/app-release.apk \ + app/build/outputs/apk/release/app-release.original.apk \ + && ENVFILE=.env.e2e ./gradlew assembleRelease' } stage('Upload apk for e2e tests') { diff --git a/ci/Jenkinsfile.release b/ci/Jenkinsfile.release index e3b1f5797b..1467e982eb 100644 --- a/ci/Jenkinsfile.release +++ b/ci/Jenkinsfile.release @@ -134,17 +134,19 @@ timeout(90) { } stage('Upload apk for e2e tests') { - env.SAUCE_LABS_APK = 'im.status.ethereum-e2e-' + shortCommit + '.apk' + if (env.CHANGE_ID != null) { + env.SAUCE_LABS_APK = 'im.status.ethereum-e2e-' + env.CHANGE_ID + '.apk' withCredentials([ - string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), - string(credentialsId: 'SAUCE_USERNAME', variable: 'SAUCE_USERNAME'), - string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'), - string(credentialsId: 'GIT_HUB_TOKEN', variable: 'GITHUB_TOKEN'), - string(credentialsId: 'SLACK_JENKINS_WEBHOOK', variable: 'SLACK_URL') + string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), + string(credentialsId: 'SAUCE_USERNAME', variable: 'SAUCE_USERNAME'), + string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'), + string(credentialsId: 'GIT_HUB_TOKEN', variable: 'GITHUB_TOKEN'), + string(credentialsId: 'SLACK_JENKINS_WEBHOOK', variable: 'SLACK_URL') ]) { sh 'fastlane android saucelabs' sh 'fastlane android upload_diawi' } + } } stage('Run extended e2e tests') { diff --git a/ci/common.groovy b/ci/common.groovy new file mode 100644 index 0000000000..25e2091d18 --- /dev/null +++ b/ci/common.groovy @@ -0,0 +1,60 @@ +def installJSDeps(platform) { + def attempt = 1 + def maxAttempts = 10 + def installed = false + /* prepare environment for specific platform build */ + sh "scripts/prepare-for-platform.sh ${platform}" + while (!installed && attempt <= maxAttempts) { + println "#${attempt} attempt to install npm deps" + sh 'npm install' + installed = fileExists('node_modules/web3/index.js') + attemp = attempt + 1 + } +} + +def doGitRebase() { + try { + sh 'git rebase origin/develop' + } catch (e) { + sh 'git rebase --abort' + throw e + } +} + +def tagBuild() { + withCredentials([[ + $class: 'UsernamePasswordMultiBinding', + credentialsId: 'status-im-auto', + usernameVariable: 'GIT_USER', + passwordVariable: 'GIT_PASS' + ]]) { + return sh( + returnStdout: true, + script: './scripts/build_no.sh --increment' + ).trim() + } +} + +def uploadArtifact(filename) { + def domain = 'ams3.digitaloceanspaces.com' + def bucket = 'status-im-desktop' + withCredentials([usernamePassword( + credentialsId: 'digital-ocean-access-keys', + usernameVariable: 'DO_ACCESS_KEY', + passwordVariable: 'DO_SECRET_KEY' + )]) { + sh """ + s3cmd \\ + --acl-public \\ + --host='${domain}' \\ + --host-bucket='%(bucket)s.${domain}' \\ + --access_key=${DO_ACCESS_KEY} \\ + --secret_key=${DO_SECRET_KEY} \\ + put ${filename} s3://${bucket}/ + """ + } + def url = "https://${bucket}.${domain}/${filename}" + return url +} + +return this diff --git a/ci/desktop.groovy b/ci/desktop.groovy new file mode 100644 index 0000000000..cb7edb063e --- /dev/null +++ b/ci/desktop.groovy @@ -0,0 +1,183 @@ +common = load 'ci/common.groovy' + +qtBin = '/opt/qt59/bin' +packageFolder = './StatusImPackage' +external_modules_dir = [ + 'node_modules/react-native-i18n/desktop', + 'node_modules/react-native-config/desktop', + 'node_modules/react-native-fs/desktop', + 'node_modules/react-native-http-bridge/desktop', + 'node_modules/react-native-webview-bridge/desktop', + 'node_modules/react-native-keychain/desktop', + 'node_modules/react-native-securerandom/desktop', + 'modules/react-native-status/desktop', +] + +def cleanupBuild() { + sh """ + rm -rf \\ + node_modules ${packageFolder} \\ + desktop/modules desktop/node_modules + """ +} + +def cleanupAndDeps() { + cleanupBuild() + sh 'cp .env.jenkins .env' + sh 'lein deps' + common.installJSDeps('desktop') +} + +def slackNotify(message, color = 'good') { + slackSend( + color: color, + channel: '#jenkins-desktop', + message: "develop (${env.CHANGE_BRANCH}) ${message} ${env.BUILD_URL}" + ) +} + +def buildClojureScript() { + sh 'rm -f index.desktop.js' + sh 'lein prod-build-desktop' + sh "mkdir ${packageFolder}" + sh """ + react-native bundle \\ + --entry-file index.desktop.js \\ + --dev false --platform desktop \\ + --bundle-output ${packageFolder}/StatusIm.jsbundle \\ + --assets-dest ${packageFolder}/assets + """ +} + +def uploadArtifact(filename) { + def domain = 'ams3.digitaloceanspaces.com' + def bucket = 'status-im-desktop' + withCredentials([usernamePassword( + credentialsId: 'digital-ocean-access-keys', + usernameVariable: 'DO_ACCESS_KEY', + passwordVariable: 'DO_SECRET_KEY' + )]) { + sh """ + s3cmd \\ + --acl-public \\ + --host='${domain}' \\ + --host-bucket='%(bucket)s.${domain}' \\ + --access_key=${DO_ACCESS_KEY} \\ + --secret_key=${DO_SECRET_KEY} \\ + put ${filename} s3://${bucket}/ + """ + + } + def url = "https://${bucket}.${domain}/${filename}" + return url +} + +/* MAIN --------------------------------------------------*/ + +def prepDeps() { + common.doGitRebase() + cleanupAndDeps() +} + +def compileLinux() { + /* add path for QT installation binaries */ + env.PATH = "${qtBin}:${env.PATH}" + dir('desktop') { + sh 'rm -rf CMakeFiles CMakeCache.txt cmake_install.cmake Makefile' + sh """ + cmake -Wno-dev \\ + -DCMAKE_BUILD_TYPE=Release \\ + -DEXTERNAL_MODULES_DIR='${external_modules_dir.join(";")}' \\ + -DJS_BUNDLE_PATH='${workspace}/${packageFolder}/StatusIm.jsbundle' \\ + -DCMAKE_CXX_FLAGS:='-DBUILD_FOR_BUNDLE=1' + """ + sh 'make' + } +} + +def bundleLinux() { + def appFile + + dir(packageFolder) { + sh 'rm -rf StatusImAppImage' + /* TODO this needs to be fixed: status-react/issues/5378 */ + sh 'cp /opt/StatusImAppImage.zip ./' + sh 'unzip ./StatusImAppImage.zip' + sh 'rm -rf AppDir' + sh 'mkdir AppDir' + } + sh "cp -r ./deployment/linux/usr ${packageFolder}/AppDir" + sh "cp ./deployment/linux/.env ${packageFolder}/AppDir" + sh "cp ./desktop/bin/StatusIm ${packageFolder}/AppDir/usr/bin" + sh 'wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage' + sh 'chmod a+x ./linuxdeployqt-continuous-x86_64.AppImage' + + sh 'rm -f Application-x86_64.AppImage' + sh 'rm -f StatusIm-x86_64.AppImage' + + sh "ldd ${packageFolder}/AppDir/usr/bin/StatusIm" + sh """ + ./linuxdeployqt-continuous-x86_64.AppImage \\ + ${packageFolder}/AppDir/usr/share/applications/StatusIm.desktop \\ + -verbose=3 -always-overwrite -no-strip \\ + -no-translations -bundle-non-qt-libs \\ + -qmake=${qtBin}/qmake \\ + -extra-plugins=imageformats/libqsvg.so \\ + -qmldir='${workspace}/node_modules/react-native' + """ + dir(packageFolder) { + sh 'ldd AppDir/usr/bin/StatusIm' + sh 'cp -r assets/share/assets AppDir/usr/bin' + sh 'cp -rf StatusImAppImage/* AppDir/usr/bin' + sh 'rm -f AppDir/usr/bin/StatusIm.AppImage' + } + sh """ + ./linuxdeployqt-continuous-x86_64.AppImage \\ + ${packageFolder}/AppDir/usr/share/applications/StatusIm.desktop \\ + -verbose=3 -appimage -qmake=${qtBin}/qmake + """ + dir(packageFolder) { + sh 'ldd AppDir/usr/bin/StatusIm' + sh 'rm -rf StatusIm.AppImage' + appFile = "StatusIm-${GIT_COMMIT.take(6)}.AppImage" + sh "mv ../StatusIm-x86_64.AppImage ${appFile}" + } + return "${packageFolder}/${appFile}".drop(2) +} + +def compileMacOS() { + /* add path for QT installation binaries */ + env.PATH = "/Users/administrator/qt/5.9.1/clang_64/bin:${env.PATH}" + dir('desktop') { + sh 'rm -rf CMakeFiles CMakeCache.txt cmake_install.cmake Makefile' + sh """ + cmake -Wno-dev \\ + -DCMAKE_BUILD_TYPE=Release \\ + -DEXTERNAL_MODULES_DIR='${external_modules_dir.join(";")}' \\ + -DJS_BUNDLE_PATH='${workspace}/${packageFolder}/StatusIm.jsbundle' \\ + -DCMAKE_CXX_FLAGS:='-DBUILD_FOR_BUNDLE=1' + """ + sh 'make' + } +} + +def bundleMacOS() { + def dmgFile + dir(packageFolder) { + sh 'git clone https://github.com/vkjr/StatusAppFiles.git' + sh 'unzip StatusAppFiles/StatusIm.app.zip' + sh 'cp -r assets/share/assets StatusIm.app/Contents/MacOs' + sh 'chmod +x StatusIm.app/Contents/MacOs/ubuntu-server' + sh 'cp ../desktop/bin/StatusIm StatusIm.app/Contents/MacOs' + sh """ + macdeployqt StatusIm.app -verbose=1 -dmg \\ + -qmldir='${workspace}/node_modules/react-native/ReactQt/runtime/src/qml/' + """ + sh 'rm -fr StatusAppFiles' + dmgFile = "StatusIm-${GIT_COMMIT.take(6)}.dmg" + sh "mv StatusIm.dmg ${dmgFile}" + } + return "${packageFolder}/${dmgFile}".drop(2) +} + +return this diff --git a/ci/mobile.groovy b/ci/mobile.groovy new file mode 100644 index 0000000000..37d893cced --- /dev/null +++ b/ci/mobile.groovy @@ -0,0 +1,102 @@ +common = load 'ci/common.groovy' + +def uploadArtifact() { + def artifact_dir = pwd() + '/android/app/build/outputs/apk/release/' + println (artifact_dir + 'app-release.apk') + def artifact = (artifact_dir + 'app-release.apk') + def server = Artifactory.server('artifacts') + def filename = "im.status.ethereum-${GIT_COMMIT.take(6)}-n-fl.apk" + def newArtifact = (artifact_dir + filename) + sh "cp ${artifact} ${newArtifact}" + def uploadSpec = '{ "files": [ { "pattern": "*apk/release/' + filename + '", "target": "nightlies-local" }]}' + def buildInfo = server.upload(uploadSpec) + return 'http://artifacts.status.im:8081/artifactory/nightlies-local/' + filename +} + +def prepDeps() { + sh 'rm -rf node_modules' + sh 'cp .env.nightly .env' + common.installJSDeps('mobile') + sh 'mvn -f modules/react-native-status/ios/RCTStatus dependency:unpack' + /* generate ios/StatusIm.xcworkspace */ + dir('ios') { + sh 'pod install' + } +} + +def runTests() { + sh 'lein test-cljs' +} + +def leinBuild() { + sh 'lein prod-build' +} + +def compileAndroid(e2e = false) { + build_no = common.tagBuild() + if (e2e) { + env.ENVFILE=".env.e2e" + } + dir('android') { + sh './gradlew react-native-android:installArchives' + sh './gradlew assembleRelease' + } + def pkg = "StatusIm-${GIT_COMMIT.take(6)}.apk" + sh "cp android/app/build/outputs/apk/release/app-release.apk ${pkg}" + return pkg +} + +def bundleAndroid() { + withCredentials([ + string(credentialsId: "SUPPLY_JSON_KEY_DATA", variable: 'GOOGLE_PLAY_JSON_KEY'), + string(credentialsId: "SLACK_URL", variable: 'SLACK_URL') + ]) { + sh 'bundle exec fastlane android nightly' + } +} + +def uploadSauceLabs() { + env.SAUCE_LABS_APK = "im.status.ethereum-e2e-${GIT_COMMIT.take(6)}.apk" + withCredentials([ + string(credentialsId: 'SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), + string(credentialsId: 'SAUCE_USERNAME', variable: 'SAUCE_USERNAME'), + string(credentialsId: 'GIT_HUB_TOKEN', variable: 'GITHUB_TOKEN'), + string(credentialsId: 'SLACK_JENKINS_WEBHOOK', variable: 'SLACK_URL') + ]) { + sh 'fastlane android saucelabs' + } + return env.SAUCE_LABS_APK +} + +def compileiOS() { + version = readFile("${env.WORKSPACE}/VERSION").trim() + build_no = common.tagBuild() + withCredentials([ + string(credentialsId: 'SLACK_URL', variable: 'SLACK_URL'), + string(credentialsId: "slave-pass-${env.NODE_NAME}", variable: 'KEYCHAIN_PASSWORD'), + string(credentialsId: 'FASTLANE_PASSWORD', variable: 'FASTLANE_PASSWORD'), + string(credentialsId: 'APPLE_ID', variable: 'APPLE_ID'), + string(credentialsId: 'fastlane-match-password', variable:'MATCH_PASSWORD') + ]) { + sh "plutil -replace CFBundleShortVersionString -string ${version} ios/StatusIm/Info.plist" + sh "plutil -replace CFBundleVersion -string ${build_no} ios/StatusIm/Info.plist" + sh 'fastlane ios nightly' + } + def pkg = "StatusIm-${GIT_COMMIT.take(6)}.ipa" + sh "cp status-adhoc/StatusIm.ipa ${pkg}" + return pkg +} + +def uploadiOS() { + withCredentials([ + string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'), + string(credentialsId: 'GIT_HUB_TOKEN', variable: 'GITHUB_TOKEN'), + string(credentialsId: 'SLACK_JENKINS_WEBHOOK', variable: 'SLACK_URL') + ]) { + sh 'fastlane android upload_diawi' + } + diawiUrl = readFile "${env.WORKSPACE}/fastlane/diawi.out" + return diawiUrl +} + +return this diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 045d110873..2e1f125634 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -156,9 +156,6 @@ platform :ios do export_method: "app-store", output_directory: "status_appstore" ) - upload_to_testflight( - ipa: "status_appstore/StatusIm.ipa" - ) slack( message: "New nightly build uploaded to TestFlight", @@ -227,7 +224,6 @@ platform :ios do ) end - desc "This fastlane step is a workaround!" desc "every now and then Realm fails on iOS on the 'Download Core` step" desc "the issue is because multiple downloads use the same temp dir" @@ -241,6 +237,10 @@ platform :ios do ) end + desc "This fastlane step cleans up XCode DerivedData folder" + lane :cleanup do + clear_derived_data + end end diff --git a/scripts/build_no.sh b/scripts/build_no.sh index f5e7cbcb8a..b94c98b8d0 100755 --- a/scripts/build_no.sh +++ b/scripts/build_no.sh @@ -1,7 +1,4 @@ #!/usr/bin/env sh - -set -e - # # This script manages app build numbers. # It returns the next build number to be used. @@ -21,7 +18,7 @@ set -e # getNumber () { - echo "$BUILD" | sed 's/[^0-9]*//g' + echo "$1" | sed 's/[^0-9]*//g' } REGEX='^build-[0-9]\+$' @@ -29,28 +26,45 @@ REGEX='^build-[0-9]\+$' # make sure we have all the tags git fetch --tags --quiet >/dev/null || >&2 echo "Could not fetch tags from remote" -# even if the current commit has a tag already, it is normal that the same commit -# is built multiple times (with different build configurations, for instance), -# so we increment the build number every time. +# check if current commit has a build tag +# since we are building in separate jobs we have to check for a tag +BUILD_TAG=$(git tag --points-at HEAD | grep -e "$REGEX") + +# chech for multiple lines +if [ 1 -lt $(echo "$BUILD_TAG" | grep -c -) ]; then + echo "Commit marked with more than one build tag!" >&2 + echo "$BUILD_TAG" >&2 + exit 1 +fi + +# use already existing build number if applicable +if [ -n "$BUILD_TAG" ]; then + echo "Current commit already tagged: $BUILD_TAG" >&2 + getNumber $BUILD_TAG + exit 0 +fi + +# if no tag was found and --increment was not given stop +if [ "$1" != "--increment" ]; then + exit 0 +fi # find the last used build number BUILD=$(git tag -l --sort=-v:refname | grep -e "$REGEX" | head -n 1) # extract the number BUILD_NO=$(getNumber "$BUILD") -if [ "$1" = "--increment" ]; then - # These need to be provided by Jenkins - if [ -z "${GIT_USER}" ] || [ -z "${GIT_PASS}" ]; then - echo "Git credentials not specified! (GIT_USER, GIT_PASS)" >&2 - exit 1 - fi - # increment - BUILD_NO="$((BUILD_NO+1))" - - echo "Tagging HEAD: build-$BUILD_NO" >&2 - git tag "build-$BUILD_NO" HEAD - git push --tags https://${GIT_USER}:${GIT_PASS}@github.com/status-im/status-react +# These need to be provided by Jenkins +if [ -z "${GIT_USER}" ] || [ -z "${GIT_PASS}" ]; then + echo "Git credentials not specified! (GIT_USER, GIT_PASS)" >&2 + exit 1 fi +# increment +BUILD_NO="$((BUILD_NO+1))" + +echo "Tagging HEAD: build-$BUILD_NO" >&2 +git tag "build-$BUILD_NO" HEAD +git push --tags https://${GIT_USER}:${GIT_PASS}@github.com/status-im/status-react # finally print build number echo "$BUILD_NO"