use status-react-jenkins as CI library

This PR extracts all the ci/*.groovy scripts into a separate private repo located at:
https://github.com/status-im/status-react-jenkins

The main reasons for a separate repo are:

* Hiding the internal details of our CI setup
* Hiding names of Jenkins credentials available in CI jobs
* Lowering attack surface for malicious external contributors
* Increasing focus on PRs related to CI setup

You can read more about how Jenkins pipeline shared libraries work here:
https://jenkins.io/doc/book/pipeline/shared-libraries/

In simple terms I've added the repo to the main Jenkins configuration in "Global Pipeline Libraries" section and load it using:

  library 'status-react-jenkins@master'

Which makes globally available all of the libraries defined in the `vars` directory of that repo.
This also eliminates the need for statements like `android = load 'ci/android.groovy'`.

Signed-off-by: Jakub Sokołowski <jakub@status.im>
This commit is contained in:
Jakub Sokołowski 2020-03-06 16:43:04 +01:00
parent 826b7df982
commit 4e567cf782
No known key found for this signature in database
GPG Key ID: 4EF064D0E6D63020
19 changed files with 95 additions and 1261 deletions

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'linux' }
@ -40,14 +42,11 @@ pipeline {
stage('Prep') {
steps {
script {
/* Necessary to load methods */
android = load 'ci/android.groovy'
cmn = load 'ci/common.groovy'
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
print "Running ${btype} build!"
cmn.ci.abortPreviousRunningBuilds()
jenkins.abortPreviousRunningBuilds()
/* Cleanup and Prep */
cmn.prep(btype)
commonPrep(btype)
}
}
}
@ -56,7 +55,7 @@ pipeline {
/* Build implicit dependencies if needed (we run `lein deps :tree` but it's not really required, for this purpose)
Implicit dependencies include building a patched node_modules, fetching maven dependencies, and anything else required.
We do this before the parallel steps so we have a known starting situation. */
script { cmn.nix.shell('lein deps :tree', attr: 'shells.lein') }
script { nix.shell('lein deps :tree', attr: 'shells.lein') }
}
}
stage('Parallel Assemble') {
@ -64,12 +63,12 @@ pipeline {
stage('Checks') { stages {
stage('Lint') {
steps {
script { cmn.nix.shell('lein cljfmt check', attr: 'shells.lein') }
script { nix.shell('lein cljfmt check', attr: 'shells.lein') }
}
}
stage('Tests') {
steps {
script { cmn.nix.shell('lein test-cljs', attr: 'shells.lein') }
script { nix.shell('lein test-cljs', attr: 'shells.lein') }
}
}
} }
@ -77,7 +76,7 @@ pipeline {
stage('JSBundle') {
steps {
script {
cmn.nix.shell('make jsbundle-android', pure: false)
nix.shell('make jsbundle-android', pure: false)
}
}
}
@ -99,7 +98,7 @@ pipeline {
stage('Upload') {
steps {
script {
def urls = apks.collect { cmn.uploadArtifact(it) }
def urls = apks.collect { s3.uploadArtifact(it) }
/* return only the universal APK */
if (urls.size() > 1) {
env.PKG_URL = urls.find { it.contains('universal') }
@ -125,8 +124,8 @@ pipeline {
}
}
post {
success { script { load('ci/github.groovy').notifyPR(true) } }
failure { script { load('ci/github.groovy').notifyPR(false) } }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
always { sh 'make _fix-node-perms' }
}
}

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'linux' }
@ -18,36 +20,33 @@ pipeline {
stage('Prep') {
steps { script {
println "Current JOB: ${env.JOB_NAME}"
/* load common lib */
cmn = load('ci/common.groovy')
gh = load('ci/github.groovy')
/* just for a shorter access */
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
} }
}
stage('Build') {
parallel {
stage('iOS') { steps { script {
ios = cmn.ci.Build('status-react/combined/mobile-ios')
ios = jenkins.Build('status-react/combined/mobile-ios')
} } }
stage('Android') { steps { script {
apk = cmn.ci.Build('status-react/combined/mobile-android')
apk = jenkins.Build('status-react/combined/mobile-android')
} } }
stage('Android e2e') { steps { script {
apke2e = cmn.ci.Build('status-react/combined/mobile-android-e2e')
apke2e = jenkins.Build('status-react/combined/mobile-android-e2e')
} } }
}
}
stage('Archive') {
steps { script {
sh('rm -f pkg/*')
cmn.ci.copyArts(ios)
//cmn.ci.copyArts(iose2e)
cmn.ci.copyArts(apk)
cmn.ci.copyArts(apke2e)
jenkins.copyArts(ios)
//jenkins.copyArts(iose2e)
jenkins.copyArts(apk)
jenkins.copyArts(apke2e)
dir('pkg') {
/* generate sha256 checksums for upload */
sh "sha256sum * | tee ${cmn.utils.pkgFilename(btype, 'sha256')}"
sh "sha256sum * | tee ${utils.pkgFilename(btype, 'sha256')}"
archiveArtifacts('*')
}
} }
@ -57,18 +56,18 @@ pipeline {
/* object for easier URLs handling */
urls = [
/* mobile */
Apk: cmn.pkgUrl(apk), Apke2e: cmn.pkgUrl(apke2e),
iOS: cmn.pkgUrl(ios), /*iOSe2e: cmn.pkgUrl(iose2e),*/
Diawi: cmn.utils.getEnv(ios, 'DIAWI_URL'),
Apk: utils.pkgUrl(apk), Apke2e: utils.pkgUrl(apke2e),
iOS: utils.pkgUrl(ios), /*iOSe2e: utils.pkgUrl(iose2e),*/
Diawi: utils.utils.getEnv(ios, 'DIAWI_URL'),
/* upload the sha256 checksums file too */
SHA: cmn.uploadArtifact(cmn.utils.pkgFind('sha256')),
SHA: s3.uploadArtifact(utils.pkgFind('sha256')),
]
/* add URLs to the build description */
cmn.ci.setBuildDesc(urls)
jenkins.setBuildDesc(urls)
/* Create JSON file with newest build URLs */
switch (btype) {
/* legacy naming, should have named it nightly.json */
case 'nightly': cmn.updateBucketJSON(urls, 'latest.json'); break
case 'nightly': s3.updateBucketJSON(urls, 'latest.json'); break
}
} }
}
@ -83,10 +82,11 @@ pipeline {
stage('Run e2e') {
when { expression { btype == 'nightly' } }
steps { script {
e2eApk = cmn.utils.getEnv(apke2e, 'SAUCE_URL')
e2eApk = utils.getEnv(apke2e, 'SAUCE_URL')
build(
job: 'end-to-end-tests/status-app-nightly', wait: false,
parameters: [string(name: 'APK_NAME', value: e2eApk)]
job: 'end-to-end-tests/status-app-nightly',
parameters: [string(name: 'APK_NAME', value: e2eApk)],
wait: false
)
} }
}

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'macos' }
@ -26,7 +28,6 @@ pipeline {
stages {
stage('Prep') {
steps { script {
nix = load('ci/nix.groovy')
nix.shell(
'bundle install --gemfile=fastlane/Gemfile',
attr: 'shells.fastlane',

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'macos-xcode-11.3.1' }
@ -39,14 +41,11 @@ pipeline {
stage('Prep') {
steps {
script {
/* Necessary to load methods */
ios = load 'ci/ios.groovy'
cmn = load 'ci/common.groovy'
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
print "Running ${btype} build!"
cmn.ci.abortPreviousRunningBuilds()
jenkins.abortPreviousRunningBuilds()
/* Cleanup and Prep */
cmn.prep(btype)
commonPrep(btype)
}
}
}
@ -55,19 +54,19 @@ pipeline {
stage('Checks') { stages {
stage('Lint') {
steps {
script { cmn.nix.shell('lein cljfmt check', attr: 'shells.lein') }
script { nix.shell('lein cljfmt check', attr: 'shells.lein') }
}
}
stage('Tests') {
steps {
script { cmn.nix.shell('lein test-cljs', attr: 'shells.lein') }
script { nix.shell('lein test-cljs', attr: 'shells.lein') }
}
}
} }
stage('Build') { stages {
stage('JSBundle') {
steps {
script { cmn.nix.shell('make jsbundle-ios') }
script { nix.shell('make jsbundle-ios') }
}
}
stage('Bundle') {
@ -88,7 +87,7 @@ pipeline {
stage('Upload') {
steps {
script {
env.PKG_URL = cmn.uploadArtifact(api)
env.PKG_URL = s3.uploadArtifact(api)
/* e2e builds get tested in SauceLabs */
if (btype == 'e2e') {
env.SAUCE_URL = ios.uploadToSauceLabs()
@ -108,8 +107,8 @@ pipeline {
}
}
post {
success { script { load('ci/github.groovy').notifyPR(true) } }
failure { script { load('ci/github.groovy').notifyPR(false) } }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
always { sh 'make _fix-node-perms' }
}
}

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'linux' }
@ -42,14 +44,11 @@ pipeline {
stage('Prep') {
steps {
script {
/* Necessary to load methods */
desktop = load 'ci/desktop.groovy'
cmn = load 'ci/common.groovy'
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
print "Running ${btype} build!"
cmn.ci.abortPreviousRunningBuilds()
jenkins.abortPreviousRunningBuilds()
/* Cleanup and Prep */
cmn.prep(btype)
commonPrep(btype)
}
}
}
@ -58,12 +57,12 @@ pipeline {
stage('Checks') { stages {
stage('Lint') {
steps {
script { cmn.nix.shell('lein cljfmt check', attr: 'shells.lein') }
script { nix.shell('lein cljfmt check', attr: 'shells.lein') }
}
}
stage('Tests') {
steps {
script { cmn.nix.shell('lein test-cljs', attr: 'shells.lein') }
script { nix.shell('lein test-cljs', attr: 'shells.lein') }
}
}
} }
@ -95,7 +94,7 @@ pipeline {
}
stage('Upload') {
steps {
script { env.PKG_URL = cmn.uploadArtifact(app) }
script { env.PKG_URL = s3.uploadArtifact(app) }
}
}
}
@ -108,7 +107,7 @@ pipeline {
}
}
post {
success { script { load('ci/github.groovy').notifyPR(true) } }
failure { script { load('ci/github.groovy').notifyPR(false) } }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
}
}

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'macos-xcode-11.3.1' }
@ -40,14 +42,11 @@ pipeline {
stage('Prep') {
steps {
script {
/* Necessary to load methods */
desktop = load 'ci/desktop.groovy'
cmn = load 'ci/common.groovy'
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
print "Running ${btype} build!"
cmn.ci.abortPreviousRunningBuilds()
jenkins.abortPreviousRunningBuilds()
/* Cleanup and Prep */
cmn.prep(btype)
commonPrep(btype)
}
}
}
@ -56,12 +55,12 @@ pipeline {
stage('Checks') { stages {
stage('Lint') {
steps {
script { cmn.nix.shell('lein cljfmt check', attr: 'shells.lein') }
script { nix.shell('lein cljfmt check', attr: 'shells.lein') }
}
}
stage('Tests') {
steps {
script { cmn.nix.shell('lein test-cljs', attr: 'shells.lein') }
script { nix.shell('lein test-cljs', attr: 'shells.lein') }
}
}
} }
@ -93,7 +92,7 @@ pipeline {
}
stage('Upload') {
steps {
script { env.PKG_URL = cmn.uploadArtifact(dmg) }
script { env.PKG_URL = s3.uploadArtifact(dmg) }
}
}
}
@ -106,7 +105,7 @@ pipeline {
}
}
post {
success { script { load('ci/github.groovy').notifyPR(true) } }
failure { script { load('ci/github.groovy').notifyPR(false) } }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
}
}

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label params.AGENT_LABEL }
@ -25,11 +27,6 @@ pipeline {
}
stages {
stage('Prep') {
steps { script {
nix = load('ci/nix.groovy')
} }
}
stage('Setup') {
steps { script {
nix.shell('nix-env -i openssh', pure: false)

View File

@ -1,3 +1,5 @@
library 'status-react-jenkins@master'
pipeline {
agent { label 'linux' }
@ -45,14 +47,11 @@ pipeline {
stage('Prep') {
steps {
script {
/* Necessary to load methods */
desktop = load 'ci/desktop.groovy'
cmn = load 'ci/common.groovy'
btype = cmn.utils.getBuildType()
btype = utils.getBuildType()
print "Running ${btype} build!"
cmn.ci.abortPreviousRunningBuilds()
jenkins.abortPreviousRunningBuilds()
/* Cleanup and Prep */
cmn.prep(btype)
commonPrep(btype)
}
}
}
@ -61,12 +60,12 @@ pipeline {
stage('Checks') { stages {
stage('Lint') {
steps {
script { cmn.nix.shell('lein cljfmt check', attr: 'shells.lein') }
script { nix.shell('lein cljfmt check', attr: 'shells.lein') }
}
}
stage('Tests') {
steps {
script { cmn.nix.shell('lein test-cljs', attr: 'shells.lein') }
script { nix.shell('lein test-cljs', attr: 'shells.lein') }
}
}
} }
@ -98,7 +97,7 @@ pipeline {
}
stage('Upload') {
steps {
script { env.PKG_URL = cmn.uploadArtifact(app) }
script { env.PKG_URL = s3.uploadArtifact(app) }
}
}
}
@ -111,7 +110,7 @@ pipeline {
}
}
post {
success { script { load('ci/github.groovy').notifyPR(true) } }
failure { script { load('ci/github.groovy').notifyPR(false) } }
success { script { github.notifyPR(true) } }
failure { script { github.notifyPR(false) } }
}
}

16
ci/README.md Normal file
View File

@ -0,0 +1,16 @@
# Description
This folder contains files defininf [Jenkins pipelines](https://jenkins.io/doc/book/pipeline/) that run on https://ci.status.im/.
# Libraries
All `Jenkinsfile`s contain the following line:
```groovy
library 'status-react-jenkins@master'
```
Which loads the used methods - like `nix.shell()` - from a separate private repo:
https://github.com/status-im/status-react-jenkins
This is done to improve security of our CI setup.

View File

@ -1,178 +0,0 @@
nix = load 'ci/nix.groovy'
utils = load 'ci/utils.groovy'
def bundle() {
/* we use the method because parameter build type does not take e2e into account */
def btype = utils.getBuildType()
/* Disable Gradle Daemon https://stackoverflow.com/questions/38710327/jenkins-builds-fail-using-the-gradle-daemon */
def gradleOpt = "-PbuildUrl='${currentBuild.absoluteUrl}' --console plain "
/* Can't take more digits than unsigned int */
def buildNumber = utils.readBuildNumber().substring(0, 10)
/* we don't need x86 for any builds except e2e */
env.ANDROID_ABI_INCLUDE="armeabi-v7a;arm64-v8a"
env.ANDROID_ABI_SPLIT="false"
/* some builds tyes require different architectures */
switch (btype) {
case 'e2e':
env.ANDROID_ABI_INCLUDE="x86" /* e2e builds are used with simulators */
break
case 'release':
env.ANDROID_ABI_SPLIT="true"
gradleOpt += "-PreleaseVersion='${utils.getVersion()}'"
break
}
/* credentials necessary to open the keystore and sign the APK */
withCredentials([
file(
credentialsId: 'status-im.keystore',
variable: 'KEYSTORE_PATH'
),
string(
credentialsId: 'android-keystore-pass',
variable: 'KEYSTORE_PASSWORD'
),
usernamePassword(
credentialsId: 'android-keystore-key-pass',
usernameVariable: 'KEYSTORE_ALIAS',
passwordVariable: 'KEYSTORE_KEY_PASSWORD'
)
]) {
/* Nix target which produces the final APKs */
nix.build(
attr: 'targets.mobile.android.release',
conf: [
'status-im.ci': '1',
'status-im.build-type': btype,
'status-im.status-react.gradle-opts': gradleOpt,
'status-im.status-react.build-number': buildNumber,
],
safeEnv: [
'KEYSTORE_ALIAS',
'KEYSTORE_PASSWORD',
'KEYSTORE_KEY_PASSWORD',
],
keepEnv: [
'ANDROID_ABI_SPLIT',
'ANDROID_ABI_INCLUDE',
'KEYSTORE_PATH',
],
sandboxPaths: [
env.KEYSTORE_PATH,
],
link: false
)
}
/* necessary for Fastlane */
def apks = renameAPKs()
/* for use with Fastlane */
env.APK_PATHS = apks.join(";")
return apks
}
def extractArchFromAPK(name) {
def pattern = /app-(.+)-[^-]+.apk/
/* extract architecture from filename */
def matches = (name =~ pattern)
if (matches.size() > 0) {
return matches[0][1]
}
if (utils.isE2EBuild()) {
return 'x86'
}
/* non-release builds make universal APKs */
return 'universal'
}
/**
* We need more informative filenames for all builds.
* For more details on the format see utils.pkgFilename().
**/
def renameAPKs() {
/* find all APK files */
def apkGlob = 'result/*.apk'
def found = findFiles(glob: apkGlob)
if (found.size() == 0) {
throw "APKs not found via glob: ${apkGlob}"
}
def renamed = []
/* rename each for upload & archiving */
for (apk in found) {
def arch = extractArchFromAPK(apk)
def pkg = utils.pkgFilename(env.BUILD_TYPE, 'apk', arch)
def newApk = "result/${pkg}"
renamed += newApk
sh "cp ${apk.path} ${newApk}"
}
return renamed
}
def uploadToPlayStore(type = 'nightly') {
withCredentials([
string(credentialsId: "SUPPLY_JSON_KEY_DATA", variable: 'GOOGLE_PLAY_JSON_KEY'),
]) {
nix.shell(
"fastlane android ${type}",
keepEnv: ['FASTLANE_DISABLE_COLORS', 'APK_PATHS', 'GOOGLE_PLAY_JSON_KEY'],
attr: 'shells.fastlane',
pure: false
)
}
}
def uploadToSauceLabs() {
def changeId = utils.changeId()
if (changeId != null) {
env.SAUCE_LABS_NAME = "${changeId}.apk"
} else {
def pkg = utils.pkgFilename(env.BUILD_TYPE, 'apk', 'x86')
env.SAUCE_LABS_NAME = "${pkg}"
}
withCredentials([
usernamePassword(
credentialsId: 'sauce-labs-api',
usernameVariable: 'SAUCE_USERNAME',
passwordVariable: 'SAUCE_ACCESS_KEY'
),
]) {
nix.shell(
'fastlane android saucelabs',
keepEnv: [
'FASTLANE_DISABLE_COLORS', 'APK_PATHS',
'SAUCE_ACCESS_KEY', 'SAUCE_USERNAME', 'SAUCE_LABS_NAME'
],
attr: 'shells.fastlane',
pure: false
)
}
return env.SAUCE_LABS_NAME
}
def uploadToDiawi() {
withCredentials([
string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'),
]) {
nix.shell(
'fastlane android upload_diawi',
keepEnv: ['FASTLANE_DISABLE_COLORS', 'APK_PATHS', 'DIAWI_TOKEN'],
attr: 'shells.fastlane',
pure: false
)
}
diawiUrl = readFile "${env.WORKSPACE}/fastlane/diawi.out"
return diawiUrl
}
def coverage() {
withCredentials([
string(credentialsId: 'coveralls-status-react-token', variable: 'COVERALLS_REPO_TOKEN'),
]) {
nix.shell(
'make coverage',
keepEnv: ['COVERALLS_REPO_TOKEN', 'COVERALLS_SERVICE_NAME', 'COVERALLS_SERVICE_JOB_ID']
)
}
}
return this

View File

@ -1,106 +0,0 @@
import groovy.json.JsonBuilder
/* Libraries -----------------------------------------------------------------*/
ci = load 'ci/jenkins.groovy'
nix = load 'ci/nix.groovy'
utils = load 'ci/utils.groovy'
/* Small Helpers -------------------------------------------------------------*/
def pkgUrl(build) {
return utils.getEnv(build, 'PKG_URL')
}
def updateBucketJSON(urls, fileName) {
/* latest.json has slightly different key names */
def content = [
DIAWI: urls.Diawi,
APK: urls.Apk, IOS: urls.iOS,
APP: urls.App, MAC: urls.Mac,
WIN: urls.Win, SHA: urls.SHA
]
def filePath = "${pwd()}/pkg/${fileName}"
/* it might not exist */
sh "mkdir -p ${pwd()}/pkg"
def contentJson = new JsonBuilder(content).toPrettyString()
println "${filePath}:\n${contentJson}"
writeFile(file: filePath, text: contentJson)
return uploadArtifact(filePath)
}
def prep(type = 'nightly') {
/* build/downloads all nix deps in advance */
nix.prepEnv()
/* rebase unless this is a release build */
utils.doGitRebase()
/* ensure that we start from a known state */
sh 'make clean'
/* Disable git hooks in CI, it's not useful, takes time and creates weird errors at times
(e.g. bin/sh: 2: /etc/ssl/certs/ca-certificates.crt: Permission denied) */
sh 'make disable-githooks'
/* pick right .env and update from params */
utils.updateEnv(type)
if (['android', 'ios'].contains(env.TARGET)) {
/* Run at start to void mismatched numbers */
utils.genBuildNumber()
}
nix.shell('watchman watch-del-all', attr: 'shells.watchman')
if (env.TARGET == 'ios') {
/* install ruby dependencies */
nix.shell(
'bundle install --gemfile=fastlane/Gemfile --quiet',
attr: 'shells.fastlane',
sandbox: false
)
}
if (['macos', 'linux', 'windows'].contains(env.TARGET)) {
/* node deps, pods, and status-go download */
nix.shell('scripts/prepare-for-desktop-platform.sh', pure: false)
}
/* run script in the nix shell so that node_modules gets instantiated before attempting the copies */
nix.shell(
'scripts/copy-translations.sh chmod',
attr: "shells.${env.TARGET}",
sandbox: false
)
}
def uploadArtifact(path) {
/* defaults for upload */
def domain = 'ams3.digitaloceanspaces.com'
def bucket = 'status-im'
/* There's so many PR builds we need a separate bucket */
if (utils.isPRBuild()) {
bucket = 'status-im-prs'
}
/* WARNING: s3cmd can't guess APK MIME content-type */
def customOpts = ''
if (path.endsWith('apk')) {
customOpts += "--mime-type='application/vnd.android.package-archive'"
}
/* We also need credentials for the upload */
withCredentials([usernamePassword(
credentialsId: 'digital-ocean-access-keys',
usernameVariable: 'DO_ACCESS_KEY',
passwordVariable: 'DO_SECRET_KEY'
)]) {
sh("""
s3cmd ${customOpts} \\
--acl-public \\
--host="${domain}" \\
--host-bucket="%(bucket)s.${domain}" \\
--access_key=${DO_ACCESS_KEY} \\
--secret_key=${DO_SECRET_KEY} \\
put ${path} s3://${bucket}/
""")
}
return "https://${bucket}.${domain}/${utils.getFilename(path)}"
}
return this

View File

@ -1,109 +0,0 @@
nix = load 'ci/nix.groovy'
utils = load 'ci/utils.groovy'
packageFolder = './StatusImPackage'
def buildJSBundle() {
nix.shell(
'''
make jsbundle-desktop && \
./scripts/build-desktop.sh buildJSBundle
''',
keepEnv: ['VERBOSE_LEVEL']
)
}
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 compile() {
/* disable logs for desktop builds when releasing */
if (params.BUILD_TYPE == 'release') {
env.STATUS_NO_LOGGING = 1
}
/* since QT is in a custom path we need to add it to PATH */
if (env.QT_PATH) {
env.PATH = "${env.QT_PATH}:${env.PATH}"
}
nix.shell(
'./scripts/build-desktop.sh compile',
keepEnv: ['VERBOSE_LEVEL']
)
}
def bundleWindows(type = 'nightly') {
def pkg
nix.shell(
'./scripts/build-desktop.sh bundle',
keepEnv: ['VERBOSE_LEVEL']
)
dir(packageFolder) {
pkg = utils.pkgFilename(type, 'exe')
sh "mv ../Status-x86_64-setup.exe ${pkg}"
}
return "${packageFolder}/${pkg}".drop(2)
}
def bundleLinux(type = 'nightly') {
def pkg
nix.shell(
'./scripts/build-desktop.sh bundle',
keepEnv: ['VERBOSE_LEVEL']
)
dir(packageFolder) {
pkg = utils.pkgFilename(type, 'AppImage')
sh "mv ../Status-x86_64.AppImage ${pkg}"
}
return "${packageFolder}/${pkg}".drop(2)
}
def bundleMacOS(type = 'nightly') {
def pkg = utils.pkgFilename(type, 'dmg')
nix.shell(
'./scripts/build-desktop.sh bundle',
keepEnv: ['VERBOSE_LEVEL']
)
dir(packageFolder) {
withCredentials([
string(credentialsId: 'desktop-gpg-outer-pass', variable: 'GPG_PASS_OUTER'),
string(credentialsId: 'desktop-gpg-inner-pass', variable: 'GPG_PASS_INNER'),
string(credentialsId: 'desktop-keychain-pass', variable: 'KEYCHAIN_PASS')
]) {
nix.shell(
"""
../scripts/sign-macos-pkg.sh Status.app ../deployment/macos/macos-developer-id.keychain-db.gpg && \
../node_modules/appdmg/bin/appdmg.js ../deployment/macos/status-dmg.json ${pkg} && \
../scripts/sign-macos-pkg.sh ${pkg} ../deployment/macos/macos-developer-id.keychain-db.gpg
""",
pure: false,
keepEnv: ['GPG_PASS_OUTER', 'GPG_PASS_INNER', 'KEYCHAIN_PASS']
)
}
}
return "${packageFolder}/${pkg}".drop(2)
}
return this

View File

@ -1,62 +0,0 @@
import groovy.json.JsonBuilder
utils = load 'ci/utils.groovy'
/**
* Methods for interacting with ghcmgr API.
* For more details see:
* https://github.com/status-im/github-comment-manager
**/
def buildObj(success) {
def pkg_url = env.PKG_URL
/* a bare ipa file is not installable on iOS */
if (env.TARGET == 'ios' && env.DIAWI_URL != "") {
pkg_url = env.DIAWI_URL
}
/* assemble build object valid for ghcmgr */
return [
id: env.BUILD_DISPLAY_NAME,
commit: GIT_COMMIT.take(8),
success: success != null ? success : true,
platform: env.TARGET + (utils.isE2EBuild() ? '-e2e' : ''),
duration: utils.buildDuration(),
url: currentBuild.absoluteUrl,
pkg_url: pkg_url,
]
}
def postBuild(success) {
/**
* This is our own service for avoiding comment spam.
* https://github.com/status-im/github-comment-manager
**/
def ghcmgrurl = 'https://ghcmgr.status.im'
def body = buildObj(success)
def json = new JsonBuilder(body).toPrettyString()
def stdout = null
withCredentials([usernamePassword(
credentialsId: 'ghcmgr-auth',
usernameVariable: 'GHCMGR_USER',
passwordVariable: 'GHCMGR_PASS'
)]) {
stdout = sh(
returnStdout: true,
script: """
curl --silent \
-XPOST --data '${json}' \
-u '${GHCMGR_USER}:${GHCMGR_PASS}' \
-w '\nHTTP_CODE:%{http_code}' \
-H "content-type: application/json" \
'${ghcmgrurl}/builds/status-react/${utils.changeId()}'
"""
)
}
/* We're not using --fail because it suppresses server response */
if (!stdout.contains('HTTP_CODE:201')) {
println("STDOUT:\n${stdout}")
error("Notifying GHCMGR failed")
}
}
return this

View File

@ -1,205 +0,0 @@
import groovy.json.JsonBuilder
/* I'm using utils from ghcmgr to avoid another load */
ghcmgr = load 'ci/ghcmgr.groovy'
/**
* Methods for interacting with GitHub API and related tools.
**/
/* Comments -------------------------------------------------------*/
def notify(message) {
def githubIssuesUrl = 'https://api.github.com/repos/status-im/status-react/issues'
def changeId = ghcmgr.utils.changeId()
if (changeId == null) { return }
def msgObj = [body: message]
def msgJson = new JsonBuilder(msgObj).toPrettyString()
withCredentials([usernamePassword(
credentialsId: 'status-im-auto',
usernameVariable: 'GH_USER',
passwordVariable: 'GH_PASS'
)]) {
sh """
curl --silent \
-u '${GH_USER}:${GH_PASS}' \
--data '${msgJson}' \
-H "Content-Type: application/json" \
"${githubIssuesUrl}/${changeId}/comments"
""".trim()
}
}
def notifyFull(urls) {
def msg = "#### :white_check_mark: "
msg += "[${env.JOB_NAME}${currentBuild.displayName}](${currentBuild.absoluteUrl}) "
msg += "CI BUILD SUCCESSFUL in ${ghcmgr.utils.buildDuration()} (${GIT_COMMIT.take(8)})\n"
msg += '| | | | | |\n'
msg += '|-|-|-|-|-|\n'
msg += "| [Android](${urls.Apk}) ([e2e](${urls.Apke2e})) "
msg += "| [iOS](${urls.iOS}) ([e2e](${urls.iOSe2e})) |"
if (urls.Mac != null) {
msg += " [MacOS](${urls.Mac}) | [AppImage](${urls.App}) | [Windows](${urls.Win}) |"
} else {
msg += " ~~MacOS~~ | ~~AppImage~~ | ~~Windows~~~ |"
}
notify(msg)
}
def notifyPRFailure() {
def d = ":small_orange_diamond:"
def msg = "#### :x: "
msg += "[${env.JOB_NAME}${currentBuild.displayName}](${currentBuild.absoluteUrl}) ${d} "
msg += "${ghcmgr.utils.buildDuration()} ${d} ${GIT_COMMIT.take(8)} ${d} "
msg += "[:page_facing_up: build log](${currentBuild.absoluteUrl}/consoleText)"
//msg += "Failed in stage: ${env.STAGE_NAME}\n"
//msg += "```${currentBuild.rawBuild.getLog(5)}```"
notify(msg)
}
def notifyPRSuccess() {
def d = ":small_blue_diamond:"
def msg = "#### :heavy_check_mark: "
def type = ghcmgr.utils.isE2EBuild() ? ' e2e' : ''
msg += "[${env.JOB_NAME}${currentBuild.displayName}](${currentBuild.absoluteUrl}) ${d} "
msg += "${ghcmgr.utils.buildDuration()} ${d} ${GIT_COMMIT.take(8)} ${d} "
msg += "[:package: ${env.TARGET}${type} package](${env.PKG_URL})"
notify(msg)
}
/* Releases -------------------------------------------------------*/
def getPrevRelease() {
return sh(returnStdout: true,
script: "git for-each-ref --format '%(refname)' --sort=committerdate refs/remotes/origin/release"
).trim().split('\\r?\\n').last()
}
def getDiffUrl(prev, current) {
prev = prev.replaceAll(/.*origin\//, '')
return "https://github.com/status-im/status-react/compare/${prev}...${current}"
}
def getReleaseChanges() {
def prevRelease = getPrevRelease()
def curRelease = ghcmgr.utils.branchName()
def changes = ''
try {
changes = sh(returnStdout: true,
script: """
git log \
--cherry-pick \
--right-only \
--no-merges \
--format='%h %s' \
${prevRelease}..HEAD
"""
).trim()
} catch (Exception ex) {
println 'ERROR: Failed to retrieve changes.'
println ex.message
return ':warning: __Please fill me in!__'
}
/* remove single quotes to not confuse formatting */
changes = changes.replace('\'', '')
return changes + '\nDiff:' + getDiffUrl(prevRelease, curRelease)
}
def releaseExists(Map args) {
def output = sh(
returnStdout: true,
script: """
github-release info --json \
-u '${args.user}' \
-r '${args.repo}' \
| jq '.Releases[].tag_name'
"""
)
return output.contains(args.version)
}
def releaseDelete(Map args) {
if (!releaseExists(args)) {
return
}
sh """
github-release delete \
-u '${args.user}' \
-r '${args.repo}' \
-t '${args.version}' \
"""
}
def releaseUpload(Map args) {
args.files.each {
sh """
github-release upload \
-u '${args.user}' \
-r '${args.repo}' \
-t '${args.version}' \
-n ${it} \
-f ${it}
"""
}
}
def releasePublish(Map args) {
sh """
github-release release \
-u '${args.user}' \
-r '${args.repo}' \
-n '${args.version}' \
-t '${args.version}' \
-c '${args.branch}' \
-d '${args.desc}' \
${args.draft ? '--draft' : ''}
"""
}
def publishRelease(Map args) {
def config = [
draft: true,
user: 'status-im',
repo: 'status-react',
files: args.files,
version: args.version,
branch: ghcmgr.utils.branchName(),
desc: getReleaseChanges(),
]
/* we release only for mobile right now */
withCredentials([usernamePassword(
credentialsId: 'status-im-auto',
usernameVariable: 'GITHUB_USER',
passwordVariable: 'GITHUB_TOKEN'
)]) {
releaseDelete(config) /* so we can re-release it */
releasePublish(config)
releaseUpload(config)
}
}
def publishReleaseMobile(path='pkg') {
def found = findFiles(glob: "${path}/*")
if (found.size() == 0) {
sh "ls ${path}"
error("No file to release in ${path}")
}
publishRelease(
version: ghcmgr.utils.getVersion(),
files: found.collect { it.path },
)
}
def notifyPR(success) {
if (ghcmgr.utils.changeId() == null) { return }
try {
ghcmgr.postBuild(success)
} catch (ex) { /* fallback to posting directly to GitHub */
println "Failed to use GHCMGR: ${ex}"
switch (success) {
case true: notifyPRSuccess(); break
}
}
}
return this

View File

@ -1,102 +0,0 @@
nix = load('ci/nix.groovy')
utils = load('ci/utils.groovy')
def plutil(name, value) {
return """
plutil -replace ${name} -string ${value} ios/StatusIm/Info.plist;
"""
}
def bundle() {
def btype = utils.getBuildType()
def target
switch (btype) {
case 'release': target = 'release'; break;
case 'testflight': target = 'release'; break;
case 'e2e': target = 'e2e'; break;
default: target = 'nightly';
}
/* configure build metadata */
nix.shell(plutil('CFBundleShortVersionString', utils.getVersion()), attr: 'shells.ios')
nix.shell(plutil('CFBundleVersion', utils.genBuildNumber()), attr: 'shells.ios')
nix.shell(plutil('CFBundleBuildUrl', currentBuild.absoluteUrl), attr: 'shells.ios')
/* the dir might not exist */
sh 'mkdir -p status-e2e'
/* build the actual app */
withCredentials([
string(credentialsId: "slave-pass-${env.NODE_NAME}", variable: 'KEYCHAIN_PASSWORD'),
string(credentialsId: 'fastlane-match-password', variable: 'MATCH_PASSWORD'),
usernamePassword(
credentialsId: 'fastlane-match-apple-id',
usernameVariable: 'FASTLANE_APPLE_ID',
passwordVariable: 'FASTLANE_PASSWORD'
),
]) {
nix.shell(
"bundle exec --gemfile=fastlane/Gemfile fastlane ios ${target}",
keepEnv: [
'FASTLANE_DISABLE_COLORS',
'FASTLANE_PASSWORD', 'KEYCHAIN_PASSWORD',
'MATCH_PASSWORD', 'FASTLANE_APPLE_ID',
],
attr: 'shells.ios'
)
}
/* rename built file for uploads and archivization */
def pkg = ''
if (btype == 'release') {
pkg = utils.pkgFilename('release', 'ipa')
sh "cp status_appstore/StatusIm.ipa ${pkg}"
} else if (btype == 'e2e') {
pkg = utils.pkgFilename('e2e', 'app.zip')
sh "cp status-e2e/StatusIm.app.zip ${pkg}"
} else if (btype != 'testflight') {
pkg = utils.pkgFilename(btype, 'ipa')
sh "cp status-adhoc/StatusIm.ipa ${pkg}"
}
/* necessary for Diawi upload */
env.DIAWI_IPA = pkg
return pkg
}
def uploadToDiawi() {
withCredentials([
string(credentialsId: 'diawi-token', variable: 'DIAWI_TOKEN'),
]) {
/* This can silently fail with 'File is not processed.' */
nix.shell(
'bundle exec --verbose --gemfile=fastlane/Gemfile fastlane ios upload_diawi',
keepEnv: ['FASTLANE_DISABLE_COLORS', 'DIAWI_TOKEN'],
attr: 'shells.fastlane'
)
}
diawiUrl = readFile "${env.WORKSPACE}/fastlane/diawi.out"
/* Save the URL in the build description */
currentBuild.description = "<a href=\"${diawiUrl}\">Diawi Link</a>"
return diawiUrl
}
def uploadToSauceLabs() {
def changeId = utils.getParentRunEnv('CHANGE_ID')
if (changeId != null) {
env.SAUCE_LABS_NAME = "${changeId}.app.zip"
} else {
env.SAUCE_LABS_NAME = "im.status.ethereum-e2e-${utils.gitCommit()}.app.zip"
}
withCredentials([
usernamePassword(
credentialsId: 'sauce-labs-api',
usernameVariable: 'SAUCE_USERNAME',
passwordVariable: 'SAUCE_ACCESS_KEY'
),
]) {
nix.shell(
'bundle exec --gemfile=fastlane/Gemfile fastlane ios saucelabs',
keepEnv: ['FASTLANE_DISABLE_COLORS', 'SAUCE_ACCESS_KEY', 'SAUCE_USERNAME'],
attr: 'shells.fastlane'
)
}
return env.SAUCE_LABS_NAME
}
return this

View File

@ -1,92 +0,0 @@
import jenkins.model.CauseOfInterruption.UserInterruption
import hudson.model.Result
import hudson.model.Run
utils = load 'ci/utils.groovy'
@NonCPS
def abortPreviousRunningBuilds() {
/* Aborting makes sense only for PR builds, since devs start so many of them */
if (!env.JOB_NAME.contains('status-react/prs')) {
println ">> Not aborting any previous jobs. Not a PR build."
return
}
Run previousBuild = currentBuild.rawBuild.getPreviousBuildInProgress()
while (previousBuild != null) {
if (previousBuild.isInProgress()) {
def executor = previousBuild.getExecutor()
if (executor != null) {
println ">> Aborting older build #${previousBuild.number}"
executor.interrupt(Result.ABORTED, new UserInterruption(
"newer build #${currentBuild.number}"
))
}
}
previousBuild = previousBuild.getPreviousBuildInProgress()
}
}
def strParam(key, value) {
/* just a helper for making string params */
return [
name: key,
value: value,
$class: 'StringParameterValue',
]
}
def Build(name = null) {
/**
* Generate parameters to pass from current params
* This allows utils.updateEnv() to work in sub-jobs
**/
parameters = params.keySet().collectEntries { key ->
[(key): strParam(key, params.get(key))]
}
/* default to current build type */
parameters['BUILD_TYPE'] = strParam('BUILD_TYPE', utils.getBuildType())
/* need to drop origin/ to match definitions of child jobs */
parameters['BRANCH'] = strParam('BRANCH', utils.branchName())
/* necessary for updating GitHub PRs */
parameters['CHANGE_ID'] = strParam('CHANGE_ID', env.CHANGE_ID)
/* always pass the BRANCH and BUILD_TYPE params with current branch */
def b = build(
job: name,
/* this allows us to analize the job even after failure */
propagate: false,
parameters: parameters.values()
)
/* BlueOcean seems to not show child-build links */
println "Build: ${b.getAbsoluteUrl()} (${b.result})"
if (b.result != 'SUCCESS') {
error("Build Failed")
}
return b
}
def copyArts(build) {
/**
* The build argument is of class RunWrapper.
* https://javadoc.jenkins.io/plugin/workflow-support/org/jenkinsci/plugins/workflow/support/steps/build/RunWrapper.html
**/
copyArtifacts(
projectName: build.fullProjectName,
target: 'pkg',
flatten: true,
selector: specific("${build.number}")
)
}
def setBuildDesc(Map links) {
def desc = 'Links: \n'
links.each { type, url ->
if (url != null) {
desc += "<a href=\"${url}\">${type}</a> \n"
}
}
currentBuild.description = desc
}
return this

View File

@ -1,170 +0,0 @@
/**
* Arguments:
* - pure - Use --pure mode with Nix for more deterministic behaviour
* - args - Map of arguments to provide to --argstr
* - keepEnv - List of env variables to keep even in pure mode
**/
def shell(Map opts = [:], String cmd) {
def defaults = [
pure: true,
args: ['target': env.TARGET ? env.TARGET : 'default'],
keepEnv: ['LOCALE_ARCHIVE_2_27'],
sandbox: true,
]
/* merge defaults with received opts */
opts = defaults + opts
/* previous merge overwrites the array */
opts.keepEnv = (opts.keepEnv + defaults.keepEnv).unique()
/* not all targets can use a pure build */
if (env.TARGET in ['windows', 'ios']) {
opts.pure = false
}
sh("""
set +x
. ~/.nix-profile/etc/profile.d/nix.sh
set -x
nix-shell --run \'${cmd}\' ${_getNixCommandArgs(opts, true)}
""")
}
/**
* Arguments:
* - pure - Use --pure mode with Nix for more deterministic behaviour
* - link - Bu default build creates a `result` directory, you can turn that off
* - conf - Map of config values to provide to --arg config
* - args - Map of arguments to provide to --argstr
* - attr - Name of attribute to use with --attr flag
* - keepEnv - List of env variables to pass through to Nix build
* - safeEnv - Name of env variables to pass securely through to Nix build (they won't get captured in Nix derivation file)
* - sandbox - If build process should run inside of a sandbox
* - sandboxPaths - List of file paths to make available in Nix sandbox
**/
def build(Map opts = [:]) {
def defaults = [
pure: true,
link: true,
args: ['target': env.TARGET],
conf: [:],
attr: null,
keepEnv: [],
safeEnv: [],
sandbox: true,
sandboxPaths: [],
]
/* merge defaults with received opts */
opts = defaults + opts
/* Previous merge overwrites the array */
opts.args = defaults.args + opts.args
opts.keepEnv = (opts.keepEnv + defaults.keepEnv).unique()
def nixPath = sh(
returnStdout: true,
script: """
set +x
. ~/.nix-profile/etc/profile.d/nix.sh
set -x
nix-build ${_getNixCommandArgs(opts, false)}
"""
).trim()
/* if not linking, copy results, but only if there's just one path */
if (!opts.link && nixPath && !nixPath.contains('\n')) {
copyResults(nixPath)
}
return nixPath
}
private def copyResults(path) {
def resultsPath = "${env.WORKSPACE}/result"
sh "rm -fr ${resultsPath}"
sh "mkdir -p ${resultsPath}"
sh "cp -fr ${path}/* ${resultsPath}/"
sh "chmod -R 755 ${resultsPath}"
}
private makeNixBuildEnvFile(Map opts = [:]) {
File envFile = File.createTempFile("nix-env", ".tmp")
if (!opts.safeEnv.isEmpty()) {
// Export the environment variables we want to keep into a temporary script we can pass to Nix and source it from the build script
def exportCommandList = opts.safeEnv.collect { envVarName -> """
echo \"export ${envVarName}=\\\"\$(printenv ${envVarName})\\\"\" >> ${envFile.absolutePath}
""" }
def exportCommands = exportCommandList.join("")
sh """
${exportCommands}
chmod u+x ${envFile.absolutePath}
"""
opts.args = opts.args + [ 'secrets-file': envFile.absolutePath ]
opts.sandboxPaths = opts.sandboxPaths + envFile.absolutePath
}
return envFile
}
private def _getNixCommandArgs(Map opts = [:], boolean isShell) {
def keepFlags = []
def entryPoint = "\'${env.WORKSPACE}/shell.nix\'"
if (!isShell || opts.attr != null) {
entryPoint = "\'${env.WORKSPACE}/default.nix\'"
}
/* don't let nix.conf control sandbox status */
def extraSandboxPathsFlag = "--option sandbox ${opts.sandbox}"
if (isShell) {
keepFlags = opts.keepEnv.collect { var -> "--keep ${var} " }
} else {
def envVarsList = opts.keepEnv.collect { var -> "${var}=\"${env[var]}\";" }
keepFlags = ["--arg env \'{${envVarsList.join("")}}\'"]
/* Export the environment variables we want to keep into
* a Nix attribute set we can pass to Nix and source it from the build script */
def envFile = makeNixBuildEnvFile(opts)
envFile.deleteOnExit()
}
def configFlag = ''
def argsFlags = opts.args.collect { key,val -> "--argstr ${key} \'${val}\'" }
def attrFlag = ''
if (opts.attr != null) {
attrFlag = "--attr '${opts.attr}'"
}
if (opts.conf != null && opts.conf != [:]) {
def configFlags = opts.conf.collect { key,val -> "${key}=\"${val}\";" }
configFlag = "--arg config \'{${configFlags.join('')}}\'"
}
if (opts.sandboxPaths != null && !opts.sandboxPaths.isEmpty()) {
extraSandboxPathsFlag += " --option extra-sandbox-paths \"${opts.sandboxPaths.join(' ')}\""
}
return [
opts.pure ? "--pure" : "",
opts.link ? "" : "--no-out-link",
configFlag,
keepFlags.join(" "),
argsFlags.join(" "),
extraSandboxPathsFlag,
attrFlag,
entryPoint,
].join(" ")
}
def prepEnv() {
if (env.TARGET in ['linux', 'windows', 'android']) {
def glibcLocales = sh(
returnStdout: true,
script: """
. ~/.nix-profile/etc/profile.d/nix.sh && \\
nix-build --no-out-link '<nixpkgs>' -A glibcLocales
"""
).trim()
/**
* This is a hack to fix missing locale errors.
* See:
* - https://github.com/NixOS/nixpkgs/issues/38991
* - https://qiita.com/kimagure/items/4449ceb0bda5c10ca50f
**/
env.LOCALE_ARCHIVE_2_27 = "${glibcLocales}/lib/locale/locale-archive"
}
}
return this

View File

@ -1,151 +0,0 @@
def getVersion() {
def path = "${env.WORKSPACE}/VERSION"
return readFile(path).trim()
}
def branchName() {
return env.GIT_BRANCH.replaceAll(/.*origin\//, '')
}
def parentOrCurrentBuild() {
def c = currentBuild.rawBuild.getCause(hudson.model.Cause$UpstreamCause)
if (c == null) { return currentBuild }
return c.getUpstreamRun()
}
def timestamp() {
/* we use parent if available to make timestmaps consistent */
def now = new Date(parentOrCurrentBuild().timeInMillis)
return now.format('yyMMdd-HHmmss', TimeZone.getTimeZone('UTC'))
}
def gitCommit() {
return GIT_COMMIT.take(6)
}
def pkgFilename(type, ext, arch=null) {
/* the grep removes the null arch */
return [
"StatusIm", timestamp(), gitCommit(), type, arch,
].grep().join('-') + ".${ext}"
}
def doGitRebase() {
if (!isPRBuild()) {
println 'Skipping rebase due for non-PR build...'
return
}
def rebaseBranch = 'develop'
if (env.CHANGE_TARGET) { /* This is available for PR builds */
rebaseBranch = env.CHANGE_TARGET
}
sh 'git status'
sh "git fetch --force origin ${rebaseBranch}:${rebaseBranch}"
try {
sh "git rebase ${rebaseBranch}"
} catch (e) {
sh 'git rebase --abort'
throw e
}
}
def genBuildNumber() {
def number = sh(
returnStdout: true,
script: "${env.WORKSPACE}/scripts/version/gen_build_no.sh"
).trim()
println "Build Number: ${number}"
return number
}
def readBuildNumber() {
def number = sh(
returnStdout: true,
script: "${env.WORKSPACE}/scripts/version/build_no.sh"
).trim()
return number
}
def getDirPath(path) {
return path.tokenize('/')[0..-2].join('/')
}
def getFilename(path) {
return path.tokenize('/')[-1]
}
def getEnv(build, envvar) {
return build.getBuildVariables().get(envvar)
}
def buildDuration() {
def duration = currentBuild.durationString
return '~' + duration.take(duration.lastIndexOf(' and counting'))
}
def pkgFind(glob) {
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 isPRBuild() {
return env.JOB_NAME.startsWith('status-react/prs')
}
def isE2EBuild() {
return env.JOB_NAME.contains('e2e')
}
def getBuildType() {
def jobName = env.JOB_NAME
if (isE2EBuild()) {
return 'e2e'
}
if (isPRBuild()) {
return 'pr'
}
if (jobName.startsWith('status-react/nightly')) {
return 'nightly'
}
if (jobName.startsWith('status-react/release')) {
return 'release'
}
return params.BUILD_TYPE
}
def getParentRunEnv(name) {
def c = currentBuild.rawBuild.getCause(hudson.model.Cause$UpstreamCause)
if (c == null) { return null }
return c.getUpstreamRun().getEnvironment()[name]
}
def changeId() {
/* CHANGE_ID can be provided via the build parameters or from parent */
def changeId = env.CHANGE_ID
changeId = params.CHANGE_ID ? params.CHANGE_ID : changeId
changeId = getParentRunEnv('CHANGE_ID') ? getParentRunEnv('CHANGE_ID') : changeId
if (!changeId) {
println('This build is not related to a PR, CHANGE_ID missing.')
println('GitHub notification impossible, skipping...')
return null
}
return changeId
}
def updateEnv(type) {
def envFile = "${env.WORKSPACE}/.env.jenkins"
/* select .env based on type of build */
if (['nightly', 'release', 'e2e'].contains(type)) {
envFile = "${env.WORKSPACE}/.env.${type}"
}
sh "ln -sf ${envFile} .env"
/* show contents for debugging purposes */
sh "cat ${envFile}"
}
return this

View File

@ -22,7 +22,7 @@ if [[ -z ${KEYSTORE_PATH} ]]; then
KEYSTORE_PATH=$(property_gradle 'KEYSTORE_FILE')
fi
# Replace ~ with proper absolute path
KEYSTORE_PATH=${KEYSTORE_FILE/#\~/$HOME}
KEYSTORE_PATH=${KEYSTORE_PATH/#\~/$HOME}
if [[ -e "${KEYSTORE_PATH}" ]]; then
echo -e "${YLW}Keystore file already exists:${RST} ${KEYSTORE_PATH}" > /dev/stderr