From 06adf0b5393955bbd503b6f96dbe8d0125e753a6 Mon Sep 17 00:00:00 2001 From: Stefan Schueller Date: Sat, 29 Aug 2020 22:33:23 +0200 Subject: [PATCH] Automated builds --- .gitlab-ci.yml | 159 ++++++++++++++++++++++++++++++ Dockerfile | 26 ++++- REPRODUCIBLE_BUILDS.md | 2 +- app/build.gradle | 53 +++++++--- build.gradle | 2 +- ci-scripts/make-github-release.sh | 52 ++++++++++ ci-scripts/update-changelog.sh | 11 +++ 7 files changed, 289 insertions(+), 16 deletions(-) create mode 100644 .gitlab-ci.yml create mode 100644 ci-scripts/make-github-release.sh create mode 100644 ci-scripts/update-changelog.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..c53201e --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,159 @@ +stages: + - environment + - build + - test + - internal + - alpha + - beta + - production + - stop + +.updateContainerJob: + image: docker:stable + stage: environment + services: + - docker:dind + script: + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true + - docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + tags: + - shell + +updateContainer: + extends: .updateContainerJob + only: + changes: + - Dockerfile + +ensureContainer: + extends: .updateContainerJob + allow_failure: true + before_script: + - "mkdir -p ~/.docker && echo '{\"experimental\": \"enabled\"}' > ~/.docker/config.json" + - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY + # Skip update container `script` if the container already exists + # via https://gitlab.com/gitlab-org/gitlab-ce/issues/26866#note_97609397 -> https://stackoverflow.com/a/52077071/796832 + - docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG > /dev/null && exit || true + +.build_job: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: build + before_script: + # We store this binary file in a variable as hex with this command, `xxd -p android-signing-keystore.jks > jks.txt` (remove all \n) + # Then we convert the hex back to a binary file + - pwd + - echo "$signing_jks_file_hex" | xxd -r -p - > android-signing-keystore.jks + - md5sum android-signing-keystore.jks + # get next version from latest changelog + - "export VERSION_CODE=`ls -f ./fastlane/metadata/android/en-US/changelogs | cut -d_ -f3 | sort -n | tail -1 | rev | cut -c5- | rev` && echo $VERSION_CODE" + # We add 200 to get this high enough above current versionCodes that are published + # - "export VERSION_CODE=$((200 + $CI_PIPELINE_IID)) && echo $VERSION_CODE" + - "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA" + - "export VERSION_NAME=${VERSION_CODE:0:1}.${VERSION_CODE:1:1}.${VERSION_CODE:2} && echo $VERSION_NAME" + after_script: + - rm -f android-signing-keystore.jks || true + artifacts: + paths: + - app/build/outputs + tags: + - docker + +buildDebug: + extends: .build_job + script: + - bundle exec fastlane buildDebug + +buildRelease: + extends: .build_job + script: + - bundle exec fastlane buildRelease + environment: + name: production + only: + - /^v[0-9]*\.[0-9]*\.[0-9]*$/i + except: + - branches + +testDebug: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: test + dependencies: + - buildDebug + script: + - bundle exec fastlane test + tags: + - docker + +publishGithub: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: internal + dependencies: + - buildRelease + when: manual + script: + - "export VERSION_CODE=`ls -f ./fastlane/metadata/android/en-US/changelogs | cut -d_ -f3 | sort -n | tail -1 | rev | cut -c5- | rev` && echo $VERSION_CODE" + - "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA" + - "export VERSION_NAME=${VERSION_CODE:0:1}.${VERSION_CODE:1:1}.${VERSION_CODE:2} && echo $VERSION_NAME" + - ci-scripts/make-github-release.sh + tags: + - docker + only: + - /^v[0-9]*\.[0-9]*\.[0-9]*$/i + +publishInternal: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + stage: internal + dependencies: + - buildRelease + when: manual + before_script: + - echo $google_play_service_account_api_key_json > ../google_play_api_key.json + - md5sum /builds/sschueller/google_play_api_key.json + after_script: + - rm -f ../google_play_api_key.json + script: + - bundle exec fastlane internal + tags: + - docker + only: + - /^v[0-9]*\.[0-9]*\.[0-9]*$/i + +.promote_job: + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + when: manual + dependencies: [] + before_script: + - echo $google_play_service_account_api_key_json > ../google_play_api_key.json + - md5sum ../google_play_api_key.json + after_script: + - rm -f ../google_play_api_key.json + +promoteAlpha: + extends: .promote_job + stage: alpha + script: + - bundle exec fastlane promote_internal_to_alpha + tags: + - docker + +promoteBeta: + extends: .promote_job + stage: beta + script: + - bundle exec fastlane promote_alpha_to_beta + tags: + - docker + +promoteProduction: + extends: .promote_job + stage: production + # We only allow production promotion on `master` because + # it has its own production scoped secret variables + only: + - tags + script: + - bundle exec fastlane promote_beta_to_production + tags: + - docker \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 99189c4..24e2d5c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,4 +18,28 @@ RUN yes | ${ANDROID_HOME}/tools/bin/sdkmanager --licenses RUN $ANDROID_HOME/tools/bin/sdkmanager --update RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ "platforms;android-${ANDROID_VERSION}" \ - "platform-tools" \ No newline at end of file + "platform-tools" + +# install OS packages +RUN apt-get --quiet update --yes + +# Installing build tools +RUN apt-get update && \ + apt-get install -y \ + build-essential \ + ruby \ + jq \ + ruby-dev + +# We use this for xxd hex->binary +RUN apt-get --quiet install --yes vim-common + +# install FastLane +COPY Gemfile.lock . +COPY Gemfile . +RUN gem update --system 3.0.8 # https://github.com/rubygems/rubygems/issues/3068 +RUN gem install bundler +RUN bundle install + +# at least 1.5G memory is required for the gitlab runner to succeed +#RUN echo "org.gradle.jvmargs=-Xmx1536m" >> local.properties \ No newline at end of file diff --git a/REPRODUCIBLE_BUILDS.md b/REPRODUCIBLE_BUILDS.md index dc08153..7be7bdf 100644 --- a/REPRODUCIBLE_BUILDS.md +++ b/REPRODUCIBLE_BUILDS.md @@ -1,6 +1,6 @@ # Reproducible Builds -Note: reproducible builds work starting version 1.0.45 +Note: This does not work at this time ## Install Docker diff --git a/app/build.gradle b/app/build.gradle index ca28fbb..fe3e6df 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,14 +17,28 @@ ext.readPropertyWithDefault = { paramName, defaultValue -> } } +// Try reading secrets from file +def secretsPropertiesFile = rootProject.file("secrets.properties") +def secretProperties = new Properties() + +if (secretsPropertiesFile.exists()) { + secretProperties.load(new FileInputStream(secretsPropertiesFile)) +} +// Otherwise read from environment variables, this happens in CI +else { + secretProperties.setProperty("signing_keystore_password", "${System.getenv('signing_keystore_password')}") + secretProperties.setProperty("signing_key_password", "${System.getenv('signing_key_password')}") + secretProperties.setProperty("signing_key_alias", "${System.getenv('signing_key_alias')}") +} + android { compileSdkVersion 29 defaultConfig { applicationId "net.schueller.peertube" minSdkVersion 21 targetSdkVersion 29 - versionCode 1047 - versionName "1.0.47" + versionCode Integer.valueOf(System.getenv("VERSION_CODE") ?: 1) + versionName System.getenv("VERSION_NAME") + "-" + System.getenv("VERSION_SHA") buildConfigField "long", "BUILD_TIME", readPropertyWithDefault('buildTimestamp', System.currentTimeMillis()) + 'L' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ext { @@ -44,12 +58,12 @@ android { implementation fileTree(dir: 'libs', include: ['*.jar']) // Layouts and design - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.legacy:legacy-support-v13:1.0.0' - implementation 'com.google.android.material:material:1.1.0' + implementation 'com.google.android.material:material:1.2.0' implementation 'de.hdodenhof:circleimageview:3.0.0' // font awesome @@ -57,7 +71,7 @@ android { implementation 'com.mikepenz:fontawesome-typeface:5.3.1.1@aar' // http client / REST - implementation 'com.squareup.okhttp3:okhttp:4.3.1' + implementation 'com.squareup.okhttp3:okhttp:4.8.0' implementation 'com.squareup.retrofit2:retrofit:2.5.0' // image downloading and caching library @@ -87,15 +101,28 @@ android { implementation 'org.apache.maven:maven-artifact:3.5.0' // testing - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + testImplementation 'junit:junit:4.13' + androidTestImplementation 'androidx.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } } + signingConfigs { + release { + // You need to specify either an absolute path or include the + // keystore file in the same directory as the build.gradle file. + storeFile file("../android-signing-keystore.jks") + storePassword "${secretProperties['signing_keystore_password']}" + keyAlias "${secretProperties['signing_key_alias']}" + keyPassword "${secretProperties['signing_key_password']}" + } + } + buildTypes { release { minifyEnabled false + testCoverageEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } } compileOptions { @@ -113,10 +140,10 @@ dependencies { def room_version = "2.2.5" def archLifecycleVersion = '2.1.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta7' - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.1' + implementation 'com.google.android.material:material:1.2.0' // database lib implementation "androidx.room:room-runtime:$room_version" diff --git a/build.gradle b/build.gradle index e4e9daa..f81d2d2 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0' + classpath 'com.android.tools.build:gradle:4.0.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/ci-scripts/make-github-release.sh b/ci-scripts/make-github-release.sh new file mode 100644 index 0000000..ce7d128 --- /dev/null +++ b/ci-scripts/make-github-release.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +repo="sschueller/easyrepost" +username="sschueller" + +# set work dir +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +# get release notes +release_notes=$(cat fastlane/metadata/android/en-US/changelogs/"${VERSION_CODE}".txt) + +echo "Commit Tag: ${CI_COMMIT_TAG}" + +if [[ "${CI_COMMIT_TAG}" = "" ]] ; then + echo "No CI_COMMIT_TAG" >&2; exit 1 +fi + +# Check release exists? +echo "Check release exists..." +res=$(curl -X GET -s -H "Content-Type:application/json" -H "Authorization: token ${github_token}" https://${username}@api.github.com/repos/${repo}/releases/tags/$CI_COMMIT_TAG) + +rel=$(echo "${res}" | jq ".id") + +if ! [[ "${rel}" = "null" ]] || [[ "${rel}" = "" ]] ; then + echo "Release exists ${CI_COMMIT_TAG}, stopping" >&2; exit 1 +else + echo "Release does not exist."; +fi + +# escape release notes +release_notes=$(echo "${release_notes}" | jq -aRs .) +postdata="{\"tag_name\":\"${CI_COMMIT_TAG}\",\"target_commitish\":\"master\",\"name\":\"Release ${CI_COMMIT_TAG}\",\"body\":${release_notes},\"draft\":false,\"prerelease\":false}" + +# Generate Release +echo "Generate Release..." +echo "${postdata}" | jq + +res=$(curl -s -X POST -H "Content-Type:application/json" -H "Authorization: token ${github_token}" https://${username}@api.github.com/repos/${repo}/releases -d "${postdata}") +echo "${res}" | jq + +release_id=$(echo "${res}" | jq '.id') + +echo "Release ID: ${release_id}" + +re='^[0-9]+$' +if ! [[ ${release_id} =~ ${re} ]] ; then + echo "Invalid ID ${release_id}" >&2; exit 1 +fi + +echo "Attaching artifact..." +# Attach artifact +curl -X POST -H "Authorization: token ${github_token}" -F 'data=@app/build/outputs/apk/release/app-release.apk' https://${username}@uploads.github.com/repos/${repo}/releases/"${release_id}"/assets?name=app-release.apk diff --git a/ci-scripts/update-changelog.sh b/ci-scripts/update-changelog.sh new file mode 100644 index 0000000..e558bfb --- /dev/null +++ b/ci-scripts/update-changelog.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# External environment variables +VERSION_CODE="$1" +export VERSION_NAME=${VERSION_CODE:0:1}.${VERSION_CODE:1:1}.${VERSION_CODE:2} + +CHANGES=$(cat fastlane/metadata/android/en-US/changelogs/$VERSION_CODE.txt) + +DATE=$(date '+%Y-%m-%d') +NEW="### Version ${VERSION_NAME} Tag: v${VERSION_NAME} (${DATE})\n${CHANGES}\n" +echo -e "${NEW}\n$(cat CHANGELOG.md)" > CHANGELOG.md \ No newline at end of file