diff --git a/android/app/build.gradle b/android/app/build.gradle index eb3f3f1338..c5d63c01b1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -116,6 +116,15 @@ def getVersionName = { -> } } +def getBuildUrl = { -> + new ByteArrayOutputStream().withStream { stdOut -> + if (project.hasProperty("buildUrl")) { + return project.buildUrl + } + return 'Local Build' + } +} + android { compileSdkVersion 27 @@ -130,6 +139,13 @@ android { abiFilters "armeabi-v7a", "arm64-v8a", "x86" } } + /** + * Arbitrary project metadata + * https://docs.gradle.org/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html + **/ + project.ext { + buildUrl = getBuildUrl() + } /** * Fix for: (https://github.com/ReactiveX/RxJava/issues/4445) * Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'. diff --git a/ci/Jenkinsfile.android b/ci/Jenkinsfile.android index 6da3dfed61..714b14f9e9 100644 --- a/ci/Jenkinsfile.android +++ b/ci/Jenkinsfile.android @@ -3,9 +3,9 @@ pipeline { options { buildDiscarder(logRotator( - numToKeepStr: '10', + numToKeepStr: '20', daysToKeepStr: '30', - artifactNumToKeepStr: '6', + artifactNumToKeepStr: '10', )) } diff --git a/ci/Jenkinsfile.combined b/ci/Jenkinsfile.combined index 56ff8feee9..8c306debd2 100644 --- a/ci/Jenkinsfile.combined +++ b/ci/Jenkinsfile.combined @@ -13,48 +13,47 @@ pipeline { stages { stage('Tag') { steps { script { - common = load('ci/common.groovy') + cmn = load('ci/common.groovy') /* to avoid missing build tag parallel builds */ - print "Build Number: ${common.tagBuild()}" + print "Build Number: ${cmn.tagBuild(true)}" } } } stage('Build') { parallel { stage('MacOS') { steps { script { - osx = common.buildBranch('status-react/combined/desktop-macos') + osx = cmn.buildBranch('status-react/combined/desktop-macos') } } } stage('Linux') { steps { script { - nix = common.buildBranch('status-react/combined/desktop-linux') + nix = cmn.buildBranch('status-react/combined/desktop-linux') } } } stage('iOS') { steps { script { - ios = common.buildBranch('status-react/combined/mobile-ios') + ios = cmn.buildBranch('status-react/combined/mobile-ios') } } } stage('Android') { steps { script { - dro = common.buildBranch('status-react/combined/mobile-android') + dro = cmn.buildBranch('status-react/combined/mobile-android') } } } stage('Android e2e') { steps { script { - e2e = common.buildBranch('status-react/combined/mobile-android', 'e2e') + e2e = cmn.buildBranch('status-react/combined/mobile-android', 'e2e') } } } } } stage('Archive') { steps { script { sh('rm -f pkg/*') - common.copyArts('status-react/combined/desktop-macos', osx.number) - common.copyArts('status-react/combined/desktop-linux', nix.number) - common.copyArts('status-react/combined/mobile-android', dro.number) - common.copyArts('status-react/combined/mobile-android', e2e.number) + cmn.copyArts('status-react/combined/desktop-macos', osx.number) + cmn.copyArts('status-react/combined/desktop-linux', nix.number) + cmn.copyArts('status-react/combined/mobile-android', dro.number) + cmn.copyArts('status-react/combined/mobile-android', e2e.number) archiveArtifacts('pkg/*') } } } stage('Upload') { when { expression { params.BUILD_TYPE == 'nightly' } } steps { script { - def pkg = "StatusIm-${GIT_COMMIT.take(6)}" - e2eUrl = common.uploadArtifact('pkg', "${pkg}-e2e.apk") - apkUrl = common.uploadArtifact('pkg', "${pkg}.apk") - dmgUrl = common.uploadArtifact('pkg', "${pkg}.dmg") - appUrl = common.uploadArtifact('pkg', "${pkg}.AppImage") + e2eUrl = cmn.uploadArtifact(findFiles(glob: 'pkg/*.e2e.apk')[0].path) + apkUrl = cmn.uploadArtifact(findFiles(glob: "pkg/*.${params.BUILD_TYPE}.apk")[0].path) + dmgUrl = cmn.uploadArtifact(findFiles(glob: 'pkg/*.dmg')[0].path) + appUrl = cmn.uploadArtifact(findFiles(glob: 'pkg/*.AppImage')[0].path) /* special case for iOS Diawi link */ ipaUrl = ios.getBuildVariables().get('DIAWI_URL') } } @@ -67,11 +66,12 @@ pipeline { "<${currentBuild.absoluteUrl}|${currentBuild.displayName}> "+ "(${currentBuild.durationString})\n"+ (params.BUILD_TYPE == 'nightly' ? - "E2E: ${e2eUrl}\n"+ - "APK: ${apkUrl}\n"+ - "IPA: ${ipaUrl}\n"+ - "DMG: ${dmgUrl}\n"+ - "APP: ${appUrl}\n" + "Packages: "+ + "<${apkUrl}|Android>, "+ + "(<${e2eUrl}|e2e>), "+ + "<${ipaUrl}|iOS>, "+ + "<${dmgUrl}|MacOS>, "+ + "<${appUrl}|AppImage>" : '') ), color: 'good' diff --git a/ci/Jenkinsfile.ios b/ci/Jenkinsfile.ios index e4275603af..ece021d276 100644 --- a/ci/Jenkinsfile.ios +++ b/ci/Jenkinsfile.ios @@ -11,9 +11,9 @@ pipeline { options { buildDiscarder(logRotator( - numToKeepStr: '10', + numToKeepStr: '20', daysToKeepStr: '30', - artifactNumToKeepStr: '1', + artifactNumToKeepStr: '10', )) } diff --git a/ci/Jenkinsfile.linux b/ci/Jenkinsfile.linux index 1747d94be4..c0db1c9eda 100644 --- a/ci/Jenkinsfile.linux +++ b/ci/Jenkinsfile.linux @@ -3,9 +3,9 @@ pipeline { options { buildDiscarder(logRotator( - numToKeepStr: '10', + numToKeepStr: '20', daysToKeepStr: '30', - artifactNumToKeepStr: '1', + artifactNumToKeepStr: '10', )) } @@ -37,7 +37,7 @@ pipeline { } stage('Bundle') { steps { - script { app = desktop.bundleLinux() } + script { app = desktop.bundleLinux(params.BUILD_TYPE) } } } stage('Archive') { diff --git a/ci/Jenkinsfile.macos b/ci/Jenkinsfile.macos index b19f194228..28126e266c 100644 --- a/ci/Jenkinsfile.macos +++ b/ci/Jenkinsfile.macos @@ -3,9 +3,9 @@ pipeline { options { buildDiscarder(logRotator( - numToKeepStr: '10', + numToKeepStr: '20', daysToKeepStr: '30', - artifactNumToKeepStr: '1', + artifactNumToKeepStr: '10', )) } @@ -37,7 +37,7 @@ pipeline { } stage('Bundle') { steps { - script { dmg = desktop.bundleMacOS() } + script { dmg = desktop.bundleMacOS(params.BUILD_TYPE) } } } stage('Archive') { diff --git a/ci/android.groovy b/ci/android.groovy index 1de9e3bf6b..a044f8de9e 100644 --- a/ci/android.groovy +++ b/ci/android.groovy @@ -15,15 +15,15 @@ def uploadArtifact() { def compile(type = 'nightly') { common.tagBuild() - def gradleOpt = '' + def gradleOpt = "-PbuildUrl='${currentBuild.absoluteUrl}' " if (type == 'release') { - gradleOpt = "-PreleaseVersion=${common.version()}" + gradleOpt += "-PreleaseVersion='${common.version()}'" } dir('android') { sh './gradlew react-native-android:installArchives' sh "./gradlew assembleRelease ${gradleOpt}" } - def pkg = "StatusIm-${GIT_COMMIT.take(6)}${(type == 'e2e' ? '-e2e' : '')}.apk" + def pkg = common.pkgFilename(type, 'apk') sh "cp android/app/build/outputs/apk/release/app-release.apk ${pkg}" return pkg } diff --git a/ci/common.groovy b/ci/common.groovy index dc354fd6e4..27bdc7a532 100644 --- a/ci/common.groovy +++ b/ci/common.groovy @@ -44,7 +44,8 @@ def doGitRebase() { } } -def tagBuild() { +def tagBuild(increment = false) { + def opts = (increment ? '--increment' : '') withCredentials([[ $class: 'UsernamePasswordMultiBinding', credentialsId: 'status-im-auto', @@ -53,14 +54,23 @@ def tagBuild() { ]]) { return sh( returnStdout: true, - script: './scripts/build_no.sh --increment' + script: "./scripts/build_no.sh ${opts}" ).trim() } } -def uploadArtifact(path, filename) { +def getDirPath(path) { + return path.tokenize('/')[0..-2].join('/') +} + +def getFilename(path) { + return path.tokenize('/')[-1] +} + +def uploadArtifact(path) { + /* defaults for upload */ def domain = 'ams3.digitaloceanspaces.com' - def bucket = 'status-im-desktop' + def bucket = 'status-im' withCredentials([usernamePassword( credentialsId: 'digital-ocean-access-keys', usernameVariable: 'DO_ACCESS_KEY', @@ -73,11 +83,23 @@ def uploadArtifact(path, filename) { --host-bucket='%(bucket)s.${domain}' \\ --access_key=${DO_ACCESS_KEY} \\ --secret_key=${DO_SECRET_KEY} \\ - put ${path}/${filename} s3://${bucket}/ + put ${path} s3://${bucket}/ """ } - def url = "https://${bucket}.${domain}/${filename}" - return url + return "https://${bucket}.${domain}/${getFilename(path)}" +} + +def timestamp() { + def now = new Date(currentBuild.timeInMillis) + return now.format('yyMMdd.HHmmss', TimeZone.getTimeZone('UTC')) +} + +def gitCommit() { + return GIT_COMMIT.take(6) +} + +def pkgFilename(type, ext) { + return "StatusIm.${timestamp()}.${gitCommit()}.${type}.${ext}" } return this diff --git a/ci/desktop.groovy b/ci/desktop.groovy index 4ee1ff3a45..f8d398a580 100644 --- a/ci/desktop.groovy +++ b/ci/desktop.groovy @@ -102,8 +102,8 @@ def compileLinux() { } } -def bundleLinux() { - def appFile +def bundleLinux(type = 'nightly') { + def pkg dir(packageFolder) { sh 'rm -rf StatusImAppImage' @@ -146,10 +146,10 @@ def bundleLinux() { dir(packageFolder) { sh 'ldd AppDir/usr/bin/StatusIm' sh 'rm -rf StatusIm.AppImage' - appFile = "StatusIm-${GIT_COMMIT.take(6)}.AppImage" - sh "mv ../StatusIm-x86_64.AppImage ${appFile}" + pkg = common.pkgFilename(type, 'AppImage') + sh "mv ../StatusIm-x86_64.AppImage ${pkg}" } - return "${packageFolder}/${appFile}".drop(2) + return "${packageFolder}/${pkg}".drop(2) } def compileMacOS() { @@ -169,8 +169,8 @@ def compileMacOS() { } } -def bundleMacOS() { - def dmgFile +def bundleMacOS(type = 'nightly') { + def pkg = common.pkgFilename(type, 'dmg') dir(packageFolder) { sh 'git clone https://github.com/vkjr/StatusAppFiles.git' sh 'unzip StatusAppFiles/StatusIm.app.zip' @@ -182,10 +182,9 @@ def bundleMacOS() { -qmldir='${workspace}/node_modules/react-native/ReactQt/runtime/src/qml/' """ sh 'rm -fr StatusAppFiles' - dmgFile = "StatusIm-${GIT_COMMIT.take(6)}.dmg" - sh "mv StatusIm.dmg ${dmgFile}" + sh "mv StatusIm.dmg ${pkg}" } - return "${packageFolder}/${dmgFile}".drop(2) + return "${packageFolder}/${pkg}".drop(2) } return this diff --git a/ci/ios.groovy b/ci/ios.groovy index 2a4254bf29..e24d937a59 100644 --- a/ci/ios.groovy +++ b/ci/ios.groovy @@ -1,5 +1,16 @@ +common = load('ci/common.groovy') + +def plutil(name, value) { + sh "plutil -replace ${name} -string ${value} ios/StatusIm/Info.plist" +} + def compile(type = 'nightly') { def target = (type == 'release' ? 'adhoc' : 'nightly') + /* configure build metadata */ + plutil('CFBundleShortVersionString', common.version()) + plutil('CFBundleVersion', common.tagBuild()) + plutil('CFBundleBuildUrl', currentBuild.absoluteUrl) + /* build the actual app */ withCredentials([ string(credentialsId: 'SLACK_URL', variable: 'SLACK_URL'), string(credentialsId: "slave-pass-${env.NODE_NAME}", variable: 'KEYCHAIN_PASSWORD'), @@ -7,11 +18,9 @@ def compile(type = 'nightly') { string(credentialsId: 'APPLE_ID', variable: 'APPLE_ID'), string(credentialsId: 'fastlane-match-password', variable:'MATCH_PASSWORD') ]) { - sh "plutil -replace CFBundleShortVersionString -string ${common.version()} ios/StatusIm/Info.plist" - sh "plutil -replace CFBundleVersion -string ${common.tagBuild()} ios/StatusIm/Info.plist" sh "fastlane ios ${target}" } - def pkg = "StatusIm-${GIT_COMMIT.take(6)}.ipa" + def pkg = common.pkgFilename(type, 'ipa') sh "cp status-adhoc/StatusIm.ipa ${pkg}" return pkg } diff --git a/ci/mobile.groovy b/ci/mobile.groovy index 8be516bec5..37cb386f84 100644 --- a/ci/mobile.groovy +++ b/ci/mobile.groovy @@ -2,7 +2,7 @@ common = load 'ci/common.groovy' ios = load 'ci/ios.groovy' android = load 'ci/android.groovy' -def prep(type = 'debug') { +def prep(type = 'nightly') { /* select type of build */ switch (type) { case 'nightly': diff --git a/ios/StatusIm/Info.plist b/ios/StatusIm/Info.plist index 6ab44cfbb4..6c42a4d77c 100644 --- a/ios/StatusIm/Info.plist +++ b/ios/StatusIm/Info.plist @@ -31,6 +31,8 @@ 1.0 CFBundleSignature ???? + CFBundleBuildUrl + ???? CFBundleVersion 1 LSApplicationQueriesSchemes diff --git a/scripts/build_no.sh b/scripts/build_no.sh index f5e7cbcb8a..4ee0f83453 100755 --- a/scripts/build_no.sh +++ b/scripts/build_no.sh @@ -20,37 +20,61 @@ set -e # * https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # -getNumber () { - echo "$BUILD" | sed 's/[^0-9]*//g' -} - REGEX='^build-[0-9]\+$' -# make sure we have all the tags -git fetch --tags --quiet >/dev/null || >&2 echo "Could not fetch tags from remote" +getNumber () { + echo "$1" | sed 's/[^0-9]*//g' +} -# even if the current commit has a tag already, it is normal that the same commit -# is built multiple times (with different build configurations, for instance), -# so we increment the build number every time. - -# find the last used build number -BUILD=$(git tag -l --sort=-v:refname | grep -e "$REGEX" | head -n 1) -# extract the number -BUILD_NO=$(getNumber "$BUILD") - -if [ "$1" = "--increment" ]; then - # These need to be provided by Jenkins - if [ -z "${GIT_USER}" ] || [ -z "${GIT_PASS}" ]; then - echo "Git credentials not specified! (GIT_USER, GIT_PASS)" >&2 - exit 1 +findNumber () ( + # check if current commit has a build tag + # since we are building in separate jobs we have to check for a tag + BUILD_TAG=$(git tag --points-at HEAD | grep -e "$REGEX" | tail -n1) + + # use already existing build number if applicable + if [ -n "$BUILD_TAG" ]; then + echo "Current commit already tagged: $BUILD_TAG" >&2 + getNumber $BUILD_TAG fi +) + +tagBuild () { + echo "Tagging HEAD: build-$1" >&2 + git tag "build-$1" HEAD + if [ -n "$GIT_USER" ] && [ -n "$GIT_PASS" ]; then + git push --tags \ + https://${GIT_USER}:${GIT_PASS}@github.com/status-im/status-react + else + git push --tags git@github.com:status-im/status-react + fi +} + +increment () { + # find the last used build number + BUILD=$(git tag -l --sort=-v:refname | grep -e "$REGEX" | head -n 1) + # extract the number + BUILD_NO=$(getNumber "$BUILD") + # increment BUILD_NO="$((BUILD_NO+1))" + # finally print build number + echo "$BUILD_NO" +} - echo "Tagging HEAD: build-$BUILD_NO" >&2 - git tag "build-$BUILD_NO" HEAD - git push --tags https://${GIT_USER}:${GIT_PASS}@github.com/status-im/status-react +##################################################################### + +# make sure we have all the tags +git fetch --tags --quiet >/dev/null || \ + >&2 echo "Could not fetch tags from remote" + +# check if this commit already has a build number +NUMBER=$(findNumber) + +# if it doesn't, or we are forcing via cli option, increment +if [ -z "$NUMBER" ] || [ "$1" = "--increment" ]; then + NUMBER=$(increment) + tagBuild $NUMBER fi -# finally print build number -echo "$BUILD_NO" +# print build number +echo $NUMBER