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:
parent
826b7df982
commit
4e567cf782
|
@ -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' }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
} }
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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' }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) } }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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
|
106
ci/common.groovy
106
ci/common.groovy
|
@ -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
|
|
@ -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
|
|
@ -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
|
205
ci/github.groovy
205
ci/github.groovy
|
@ -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
|
102
ci/ios.groovy
102
ci/ios.groovy
|
@ -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
|
|
@ -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
|
170
ci/nix.groovy
170
ci/nix.groovy
|
@ -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
|
151
ci/utils.groovy
151
ci/utils.groovy
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue