aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMenny Even Danan <menny@evendanan.net>2020-03-02 15:03:03 +0000
committerMenny Even Danan <menny@evendanan.net>2020-03-10 15:12:01 +0000
commit75a0d3e3a6ba8bb187e2957c4f9e34c5b4f6ed5b (patch)
tree4157a08b179fb4c1febc6d3d4c8b6cada633d3ea
parent2e9762f38bb0f779d75990b0e18687d64b5137a1 (diff)
downloadAnySoftKeyboard-75a0d3e3a6ba8bb187e2957c4f9e34c5b4f6ed5b.tar.gz
AnySoftKeyboard-75a0d3e3a6ba8bb187e2957c4f9e34c5b4f6ed5b.tar.bz2
Deploy-Request Gradle task and definition of promoting steps
-rw-r--r--.github/CODEOWNERS2
-rw-r--r--.github/actions/deploy-request/action.yml36
-rwxr-xr-x.github/actions/deploy-request/request.sh43
-rw-r--r--.github/actions/deploy-status/action.yml36
-rw-r--r--.github/actions/deploy/action.yml40
-rwxr-xr-x.github/actions/deploy/deploy.sh123
-rwxr-xr-x.github/actions/deploy/status-request.sh (renamed from .github/actions/deploy-status/request.sh)0
-rw-r--r--.github/workflows/checks.yml26
-rw-r--r--.github/workflows/deploy.yml33
-rw-r--r--.github/workflows/deployment_promote.yml21
-rw-r--r--README.md6
-rw-r--r--buildSrc/build.gradle10
-rw-r--r--buildSrc/src/main/java/deployment/DeploymentCommandLineArgs.java13
-rw-r--r--buildSrc/src/main/java/deployment/DeploymentPlugin.java47
-rw-r--r--buildSrc/src/main/java/deployment/DeploymentProcessConfiguration.java14
-rw-r--r--buildSrc/src/main/java/deployment/DeploymentRequestProcessTask.java105
-rw-r--r--buildSrc/src/main/java/github/Deployment.java103
-rw-r--r--buildSrc/src/main/java/github/GsonCreator.java10
-rw-r--r--buildSrc/src/main/java/github/HttpClientCreator.java15
-rw-r--r--deployment/build.gradle47
-rw-r--r--gradle/apk_module.gradle2
-rwxr-xr-xscripts/ci/ci_setup.sh2
-rwxr-xr-xscripts/retry-on-SIGSEGV.sh10
-rw-r--r--settings.gradle1
24 files changed, 536 insertions, 209 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d6f99749e..8ae18906e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,4 +1,4 @@
-* @AnySoftKeyboard/infra @AnySoftKeyboard/maintainers
+* @AnySoftKeyboard/maintainers
#only organization owner can change the license
/LICENSE @menny
#core infra structure team can approve github configuration
diff --git a/.github/actions/deploy-request/action.yml b/.github/actions/deploy-request/action.yml
deleted file mode 100644
index 2ac16e04f..000000000
--- a/.github/actions/deploy-request/action.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: "deployment-request"
-author: "menny"
-description: "Performs a deploy request"
-
-inputs:
- ref:
- description: "ref (branch) to publish"
- required: true
- sha:
- description: "commit to deploy"
- required: true
- api_user_name:
- description: "the username to use for API calls"
- required: true
- api_user_token:
- description: "the user API token to use for API calls"
- required: true
- reports_folder:
- description: "Where to store requests and responses files."
- required: true
-
-runs:
- using: "docker"
- image: "docker://menny/ndk_ask:1.13.6"
- entrypoint: /bin/bash
- args:
- - .github/actions/deploy-request/request.sh
- - ${{ inputs.ref }}
- - ${{ inputs.sha }}
- - ${{ inputs.api_user_name }}
- - ${{ inputs.api_user_token }}
- - ${{ inputs.reports_folder }}
-
-branding:
- icon: 'package'
- color: 'purple'
diff --git a/.github/actions/deploy-request/request.sh b/.github/actions/deploy-request/request.sh
deleted file mode 100755
index d526f76f4..000000000
--- a/.github/actions/deploy-request/request.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-REF_TO_DEPLOY="${1}"
-#we are using exact SHA to deploy, and not branc (which can move)
-SHA_TO_DEPLOY="${2}"
-API_USERNAME="${3}"
-API_TOKEN="${4}"
-OUTPUT="${5}"
-
-rm -rf "${OUTPUT}" || true
-mkdir -p "${OUTPUT}"
-
-function deployment_request() {
- echo "making request to: ${1}"
- local JSON_TEXT
- JSON_TEXT=$( jq -n \
- --arg jsonRef "${SHA_TO_DEPLOY}" \
- --arg jsonDeployTarget "${1}" \
- --arg jsonDescription "${2}" \
- '{ ref: $jsonRef, task: "deploy", auto_merge: false, environment: $jsonDeployTarget, description: $jsonDescription, required_contexts: [ "master-green-requirement" ] }' )
-
- local JSON_FILENAME="${OUTPUT}/deployment_request_${1}.json"
- echo "${JSON_TEXT}" > "${JSON_FILENAME}"
- cat "${JSON_FILENAME}"
- set +e
- curl --fail -u "${API_USERNAME}:${API_TOKEN}" -o "${OUTPUT}/deployment_response_${1}.json" -d "@${JSON_FILENAME}" https://api.github.com/repos/AnySoftKeyboard/AnySoftKeyboard/deployments
- local curl_exit_code=$?
- set -e
- echo "response with exit-code ${curl_exit_code}:"
- cat "${OUTPUT}/deployment_response_${1}.json"
- if [[ ${curl_exit_code} -ne 0 ]]; then
- exit ${curl_exit_code}
- fi
-}
-
-#some deploy logic
-if [[ "${REF_TO_DEPLOY}" == "refs/heads/master" ]]; then
- deployment_request "app_alpha" "Deployment request by ${API_USERNAME}"
- deployment_request "addons_alpha" "Deployment request by ${API_USERNAME}"
-elif [[ "${REF_TO_DEPLOY}" == "release-branch-v"* ]]; then
- deployment_request "app_beta" "Deployment request by ${API_USERNAME}"
-fi
diff --git a/.github/actions/deploy-status/action.yml b/.github/actions/deploy-status/action.yml
deleted file mode 100644
index bb4592f0c..000000000
--- a/.github/actions/deploy-status/action.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-name: "deployment-status"
-author: "menny"
-description: "updates deployment status"
-
-inputs:
- deployment_id:
- description: "deployment-id"
- required: true
- state:
- description: "commit to deploy"
- required: true
- environment:
- description: "environment to deploy"
- required: true
- api_user_name:
- description: "the username to use for API calls"
- required: true
- api_user_token:
- description: "the user API token to use for API calls"
- required: true
-
-runs:
- using: "docker"
- image: "docker://menny/ndk_ask:1.13.6"
- entrypoint: /bin/bash
- args:
- - .github/actions/deploy-status/request.sh
- - ${{ inputs.deployment_id }}
- - ${{ inputs.environment }}
- - ${{ inputs.state }}
- - ${{ inputs.api_user_name }}
- - ${{ inputs.api_user_token }}
-
-branding:
- icon: 'package'
- color: 'purple'
diff --git a/.github/actions/deploy/action.yml b/.github/actions/deploy/action.yml
index 8463f089a..f13035251 100644
--- a/.github/actions/deploy/action.yml
+++ b/.github/actions/deploy/action.yml
@@ -3,27 +3,45 @@ description: "Deploys the AnySoftKeyboard artifacts to Play Store"
author: "menny"
inputs:
- deploy_target:
- description: "Target to deploy. Should something deploy-request github-action outputs"
+ deployment_id:
+ description: "ID given by github"
+ required: true
+ deployment_environment:
+ description: "Deploy to which environment"
+ required: true
+ deployment_task:
+ description: "Deployment task"
+ required: true
+ api_user:
+ description: "github API user"
+ required: true
+ api_token:
+ description: "github API user token"
required: true
crash_report_email:
default: "none@example.com"
- required: false
+ description: "email address for crash reporting "
+ required: true
keystore_url:
default: ""
- required: false
+ description: "direct download URL to APK signing keystore"
+ required: true
keystore_password:
default: ""
- required: false
+ description: "APK signing keystore password"
+ required: true
keystore_key_password:
default: ""
- required: false
+ description: "APK signing keystore default key password"
+ required: true
publish_service_account_creds_url:
+ description: "direct download URL to Play-Store credentials file"
default: ""
- required: false
+ required: true
publish_service_account:
+ description: "account for Play-Store API"
default: ""
- required: false
+ required: true
runs:
using: "docker"
image: "docker://menny/ndk_ask:1.13.6"
@@ -35,13 +53,17 @@ runs:
entrypoint: /bin/bash
args:
- .github/actions/deploy/deploy.sh
- - ${{ inputs.deploy_target }}
+ - ${{ inputs.deployment_id }}
+ - ${{ inputs.deployment_environment }}
+ - ${{ inputs.deployment_task }}
- ${{ inputs.crash_report_email }}
- ${{ inputs.keystore_url }}
- ${{ inputs.keystore_password }}
- ${{ inputs.keystore_key_password }}
- ${{ inputs.publish_service_account_creds_url }}
- ${{ inputs.publish_service_account }}
+ - ${{ inputs.api_user }}
+ - ${{ inputs.api_token }}
branding:
icon: 'upload-cloud'
diff --git a/.github/actions/deploy/deploy.sh b/.github/actions/deploy/deploy.sh
index 8bea7d299..f7d47a0d5 100755
--- a/.github/actions/deploy/deploy.sh
+++ b/.github/actions/deploy/deploy.sh
@@ -1,7 +1,10 @@
#!/usr/bin/env bash
set -e
-
-DEPLOY_TARGET="${1}"
+DEPLOYMET_ID="${1}"
+shift
+DEPLOYMENT_ENVIRONMENT="${1}"
+shift
+DEPLOYMENT_TASK="${1}"
shift
export ANYSOFTKEYBOARD_CRASH_REPORT_EMAIL="${1}"
shift
@@ -15,61 +18,101 @@ PUBLISH_CERT_FILE_URL="${1}"
shift
export PUBLISH_APK_SERVICE_ACCOUNT_EMAIL="${1}"
shift
+API_USER="${1}"
+shift
+API_TOKEN="${1}"
+shift
+function deployProcessFromEnvironmentName() {
+ #imeMaster_alpha_100
+ [[ $1 =~ ([a-zA-Z]+)_.*_.* ]]
+ echo "${BASH_REMATCH[1]}"
+}
+
+function deployChannelFromEnvironmentName() {
+ #imeMaster_alpha_100
+ [[ $1 =~ .*_([a-zA-Z]+)_.* ]]
+ echo "${BASH_REMATCH[1]}"
+}
+
+function deployFractionFromEnvironmentName() {
+ #imeMaster_alpha_100
+ [[ $1 =~ .*_.*_([0-9]+) ]]
+ local PERCENTAGE="${BASH_REMATCH[1]}"
+ echo "$(echo "${PERCENTAGE}" | cut -c1-1).$(echo "${PERCENTAGE}" | cut -c2-3)"
+}
+
+PROCESS_NAME=$(deployProcessFromEnvironmentName "${DEPLOYMENT_ENVIRONMENT}")
+DEPLOY_CHANNEL=$(deployChannelFromEnvironmentName "${DEPLOYMENT_ENVIRONMENT}")
+FRACTION=$(deployFractionFromEnvironmentName "${DEPLOYMENT_ENVIRONMENT}")
+
+echo "for ${DEPLOYMENT_ENVIRONMENT}: will deploy process ${PROCESS_NAME} to ${DEPLOY_CHANNEL} with ${FRACTION} fraction."
export BUILD_COUNT_FOR_VERSION=${GITHUB_RUN_NUMBER}
+./.github/actions/deploy/status-request.sh "${DEPLOYMET_ID}" "${DEPLOYMENT_ENVIRONMENT}" "in-progress" "${API_USER}" "${API_TOKEN}"
+
+echo "Downloading signature files..."
if [[ -z "${KEYSTORE_FILE_URL}" ]]; then
- echo "Using debug keystore for signing."
- mkdir -p /root/.android/ || true
- cp ./.github/actions/deploy/debug.keystore /root/.android/ || exit 1
+ echo "Could not find secure env variable KEYSTORE_FILE_URL. Can not deploy."
+ exit 1
fi
-DEPLOY_TASKS=( "-PwithAutoVersioning" ":generateFdroidYamls" )
-case "${DEPLOY_TARGET}" in
- dry-run)
- DEPLOY_TASKS+=( "-DdeployChannel=alpha" "assembleRelease" "assembleCanary" "verifyReleaseResources" "generateReleasePlayResources" "generateCanaryPlayResources" )
- ;;
+if [[ -z "${PUBLISH_CERT_FILE_URL}" ]]; then
+ echo "Could not find secure env variable PUBLISH_CERT_FILE_URL. Can not deploy."
+ exit 1
+fi
- app_alpha)
- DEPLOY_TASKS+=( "-DdeployChannel=alpha" "ime:app:assembleCanary" "ime:app:publishCanary" )
- ;;
+wget --tries=5 --waitretry=5 "${KEYSTORE_FILE_URL}" -q -O /tmp/anysoftkeyboard.keystore
+stat /tmp/anysoftkeyboard.keystore
+wget --tries=5 --waitretry=5 "${PUBLISH_CERT_FILE_URL}" -q -O /tmp/apk_upload_key.p12
+stat /tmp/apk_upload_key.p12
- app_beta)
- DEPLOY_TASKS+=( "-DdeployChannel=beta" "ime:app:assembleRelease" "ime:app:publishRelease")
- ;;
+DEPLOY_TASKS=( "-PwithAutoVersioning" ":generateFdroidYamls" "-DdeployChannel=${DEPLOY_CHANNEL}" "--user-fraction" "${FRACTION}" )
+if [[ "${DEPLOYMENT_TASK}" == "deploy" ]]; then
+ case "${PROCESS_NAME}" in
- addons_alpha)
- DEPLOY_TASKS+=( "-DdeployChannel=alpha" "assembleRelease" "publishRelease" "-x" "ime:app:assembleRelease" "-x" "ime:app:publishRelease" )
- ;;
+ imeMaster)
+ DEPLOY_TASKS+=( "ime:app:assembleCanary" "ime:app:publishCanary" )
+ ;;
- *)
- echo "deploy-target '${DEPLOY_TARGET}' is unkown!"
- exit 1
- ;;
-esac
+ imeProduction)
+ DEPLOY_TASKS+=( "ime:app:assembleRelease" "ime:app:publishRelease" )
+ ;;
+
+ addOns)
+ DEPLOY_TASKS+=( "assembleRelease" "publishRelease" "-x" "ime:app:assembleRelease" "-x" "ime:app:publishRelease" )
+ ;;
-echo "Counter is ${BUILD_COUNT_FOR_VERSION}, DEPLOY_TARGET: ${DEPLOY_TARGET}, crash email: ${ANYSOFTKEYBOARD_CRASH_REPORT_EMAIL}, and tasks: ${DEPLOY_TASKS[*]}"
+ *)
+ echo "PROCESS_NAME '${PROCESS_NAME}' is unknown in task ${DEPLOYMENT_TASK}!"
+ exit 1
+ ;;
-if [[ "${DEPLOY_TASKS[*]}" == *"publish"* ]]; then
- echo "Downloading signature files..."
+ esac
+elif [[ "${DEPLOYMENT_TASK}" == "deploy:migration" ]]; then
+ case "${PROCESS_NAME}" in
- if [[ -z "${KEYSTORE_FILE_URL}" ]]; then
- echo "Could not find secure env variable KEYSTORE_FILE_URL. Can not deploy."
- exit 1
- fi
+ imeMaster)
+ DEPLOY_TASKS+=( "ime:app:promoteReleaseArtifact" )
+ ;;
- if [[ -z "${PUBLISH_CERT_FILE_URL}" ]]; then
- echo "Could not find secure env variable PUBLISH_CERT_FILE_URL. Can not deploy."
- exit 1
- fi
+ imeProduction)
+ DEPLOY_TASKS+=( "ime:app:promoteReleaseArtifact" )
+ ;;
- wget --tries=5 --waitretry=5 "${KEYSTORE_FILE_URL}" -q -O /tmp/anysoftkeyboard.keystore
- stat /tmp/anysoftkeyboard.keystore
- wget --tries=5 --waitretry=5 "${PUBLISH_CERT_FILE_URL}" -q -O /tmp/apk_upload_key.p12
- stat /tmp/apk_upload_key.p12
+ addOns)
+ DEPLOY_TASKS+=( "promoteReleaseArtifact" "-x" "ime:app:promoteReleaseArtifact" )
+ ;;
+
+ esac
fi
-# shellcheck disable=SC2086
+echo "Counter is ${BUILD_COUNT_FOR_VERSION}, crash email: ${ANYSOFTKEYBOARD_CRASH_REPORT_EMAIL}, and tasks: ${DEPLOY_TASKS[*]}"
+
./gradlew "${DEPLOY_TASKS[@]}"
+./.github/actions/deploy/status-request.sh "${DEPLOYMET_ID}" "${DEPLOY_TARGET}" "success" "${API_USER}" "${API_TOKEN}"
+
+## TODO: kill previous enabled environments
+
[[ -n "${GITHUB_ACTIONS}" ]] && chmod -R a+rwx .
diff --git a/.github/actions/deploy-status/request.sh b/.github/actions/deploy/status-request.sh
index f2be576f9..f2be576f9 100755
--- a/.github/actions/deploy-status/request.sh
+++ b/.github/actions/deploy/status-request.sh
diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index f50c93807..f23635919 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -165,7 +165,6 @@ jobs:
path: build-logging
deploy-dry-run:
- if: github.event_name == 'pull_request'
runs-on: ubuntu-18.04
container: menny/ndk_ask:1.13.6
steps:
@@ -178,9 +177,12 @@ jobs:
dry-run-deploy-gradle-
- name: setup
run: ./scripts/ci/ci_setup.sh
- - uses: ./.github/actions/deploy
- with:
- deploy_target: dry-run
+ - name: dry-run-release-build
+ run: |
+ mkdir -p /root/.android/ || true
+ cp ./.github/actions/deploy/debug.keystore /root/.android/ || exit 1
+ ./gradlew -PwithAutoVersioning :generateFdroidYamls -DdeployChannel=alpha assembleRelease assembleCanary verifyReleaseResources generateReleasePlayResources generateCanaryPlayResources
+ chmod -R a+rwx .
- uses: actions/upload-artifact@v1.0.0
with:
name: fdroid-metadata-dry-run
@@ -190,19 +192,17 @@ jobs:
needs: [checks, app-all-sdks-tests, app-tests-shards, app-less-tests, deploy-dry-run]
runs-on: ubuntu-18.04
steps:
- - uses: actions/checkout@v2
+ - name: ready
+ run: echo "DONE"
deploy-request:
if: github.event_name == 'push'
needs: [master-green-requirement]
runs-on: ubuntu-18.04
- container: menny/ndk_ask:1.13.6
+ container: menny/android_base:1.13.6
steps:
- uses: actions/checkout@v2
- - uses: ./.github/actions/deploy-request
- with:
- ref: ${{ github.ref }}
- sha: ${{ github.sha }}
- api_user_name: ${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}
- api_user_token: ${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
- reports_folder: build/deploy-reports
+ - name: request-new-deploys
+ run: |
+ ./gradlew :deployment:imeOnMasterPush :deployment:addOnsOnMasterPush \
+ -PrequestDeploy.sha=${{ github.sha }} -PrequestDeploy.api_user_name=${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }} -PrequestDeploy.api_user_token=${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index f44c86db2..c4f92dfdc 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -22,13 +22,6 @@ jobs:
with:
name: github_object
path: build/github_object
- - uses: ./.github/actions/deploy-status
- with:
- deployment_id: ${{ github.event.deployment.id }}
- state: in_progress
- environment: ${{ github.event.deployment.environment }}
- api_user_name: ${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}
- api_user_token: ${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
- name: setup
run: |
./scripts/ci/ci_setup.sh
@@ -37,29 +30,25 @@ jobs:
mkdir -p ime/app/build/outputs/mapping || true
- uses: ./.github/actions/deploy
with:
- deploy_target: ${{ github.event.deployment.environment }}
+ deployment_id: ${{ github.event.deployment.id }}
+ deployment_environment: ${{ github.event.deployment.environment }}
+ deployment_task: ${{ github.event.deployment.task }}
crash_report_email: ${{ secrets.ANYSOFTKEYBOARD_CRASH_REPORT_EMAIL }}
keystore_url: ${{ secrets.ANYSOFTKEYBOARD_KEYSTORE_URL }}
keystore_password: ${{ secrets.ANYSOFTKEYBOARD_KEYSTORE_PASSWORD }}
keystore_key_password: ${{ secrets.ANYSOFTKEYBOARD_KEYSTORE_KEY_PASSWORD }}
publish_service_account_creds_url: ${{ secrets.PUBLISH_CERT_FILE_URL }}
publish_service_account: ${{ secrets.PUBLISH_APK_SERVICE_ACCOUNT_EMAIL }}
- - uses: ./.github/actions/deploy-status
- if: success()
- with:
- deployment_id: ${{ github.event.deployment.id }}
- state: success
- environment: ${{ github.event.deployment.environment }}
- api_user_name: ${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}
- api_user_token: ${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
- - uses: ./.github/actions/deploy-status
+ api_user: ${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}
+ api_token: ${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
+ - name: status-failure
if: failure()
+ run: ./.github/actions/deploy/status-request.sh "${{ github.event.deployment.id }}" "${{ github.event.deployment.environment }}" "failure" "${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}" "${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}"
+ - uses: actions/upload-artifact@v1.0.0
+ if: always()
with:
- deployment_id: ${{ github.event.deployment.id }}
- state: failure
- environment: ${{ github.event.deployment.environment }}
- api_user_name: ${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }}
- api_user_token: ${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
+ name: deploy-logging
+ path: build-logging
- uses: actions/upload-artifact@v1.0.0
with:
name: apks
diff --git a/.github/workflows/deployment_promote.yml b/.github/workflows/deployment_promote.yml
new file mode 100644
index 000000000..b7940f188
--- /dev/null
+++ b/.github/workflows/deployment_promote.yml
@@ -0,0 +1,21 @@
+name: deployment
+
+#always run on the default branch: master
+on:
+ schedule:
+ - cron: '04 04 * * *'
+
+env:
+ TERM: dumb
+ GRADLE_OPTS: "-Dorg.gradle.daemon=false --stacktrace"
+
+jobs:
+ promote-alpha:
+ runs-on: ubuntu-18.04
+ container: menny/android_base:1.13.6
+ steps:
+ - uses: actions/checkout@v2
+ - name: requets-pre-release-promote
+ run: |
+ ./gradlew :deployment:imePromoteMaster :deployment:addOnsPromoteMaster \
+ -PrequestDeploy.sha=${{ github.sha }} -PrequestDeploy.api_user_name=${{ secrets.BOT_MASTER_RW_GITHUB_USERNAME }} -PrequestDeploy.api_user_token=${{ secrets.BOT_MASTER_RW_GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 84333a69b..d13685d3a 100644
--- a/README.md
+++ b/README.md
@@ -32,10 +32,10 @@ Sign up to beta-channel [here](https://play.google.com/apps/testing/com.menny.an
* More on AnySoftKeyboard can be found [here](http://anysoftkeyboard.github.io/).
## Releases
-* Every commit to _master_ branch will deploy a new release to the _ALPHA_ channel in Google Play-Store. You can subscribe to this release channel by joining the [Google Groups](https://groups.google.com/d/forum/anysoftkeyboard-alpha-testers) group, and opt-in by visiting [this link](https://play.google.com/apps/testing/com.menny.android.anysoftkeyboard).
-* Every once in a while, a stable enough _ALPHA_ will be promoted to _BETA_. You can opt-in to this channel by visiting [this link](https://play.google.com/apps/testing/com.menny.android.anysoftkeyboard).
+* Every commit to _master_ branch will [deploy](.github/workflows/checks.yml) a new release to the _ALPHA_ channel in Google Play-Store. You can subscribe to this release channel by joining the [Google Groups](https://groups.google.com/d/forum/anysoftkeyboard-alpha-testers) group, and opt-in by visiting [this link](https://play.google.com/apps/testing/com.menny.android.anysoftkeyboard).
+* Every Wednesday the latest _ALPHA_ will be [promoted](.github/workflows/deployment_promote.yml) to _BETA_. You can opt-in to this channel by visiting [this link](https://play.google.com/apps/testing/com.menny.android.anysoftkeyboard).
* Note about pre-release channels: every few months we will remove all the users in the groups. When that happens, you are required to re-subscribe to the group. This is done to ensure that the members in the groups are active.
-* Once all requirements for a release were finished, a _STABLE_ release branch (in the format of `release-branch-vX.X-rX`) will be cut. Every commit to this branch will be automatically published to Google Play Store, and will roll-out users gradually.
+* Once all requirements for a release were finished, a _STABLE_ release branch (in the format of `release-branch-vX.X-rX`) will be cut. Every commit to this branch will be automatically published to Google Play Store (_STABLE_ channel) and will roll-out users gradually.
## Read more
* Our fancy [web-site](http://anysoftkeyboard.github.io/)
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index f0ea90a8a..59579c2fb 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -4,19 +4,27 @@ apply plugin: 'java-gradle-plugin'
repositories {
maven { url 'https://repo1.maven.org/maven2' /*maven-central with HTTPS*/}
+ maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
implementation 'org.jsoup:jsoup:1.9.1'
implementation gradleApi()
implementation localGroovy()
+
+ implementation 'org.apache.httpcomponents:httpclient:4.5.11'
+ implementation 'com.google.code.gson:gson:2.8.6'
}
gradlePlugin {
plugins {
- simplePlugin {
+ makeDictionaryPlugin {
id = 'make-dictionary'
implementationClass = 'MakeDictionaryPlugin'
}
+ deploymentsPlugin {
+ id = 'anysoftkeyboard-deployment'
+ implementationClass = 'deployment.DeploymentPlugin'
+ }
}
}
diff --git a/buildSrc/src/main/java/deployment/DeploymentCommandLineArgs.java b/buildSrc/src/main/java/deployment/DeploymentCommandLineArgs.java
new file mode 100644
index 000000000..26418e8d0
--- /dev/null
+++ b/buildSrc/src/main/java/deployment/DeploymentCommandLineArgs.java
@@ -0,0 +1,13 @@
+package deployment;
+
+class DeploymentCommandLineArgs {
+ final String sha;
+ final String apiUsername;
+ final String apiUserToken;
+
+ DeploymentCommandLineArgs(String sha, String apiUsername, String apiUserToken) {
+ this.sha = sha;
+ this.apiUsername = apiUsername;
+ this.apiUserToken = apiUserToken;
+ }
+}
diff --git a/buildSrc/src/main/java/deployment/DeploymentPlugin.java b/buildSrc/src/main/java/deployment/DeploymentPlugin.java
new file mode 100644
index 000000000..954d24eae
--- /dev/null
+++ b/buildSrc/src/main/java/deployment/DeploymentPlugin.java
@@ -0,0 +1,47 @@
+package deployment;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+
+public class DeploymentPlugin implements Plugin<Project> {
+
+ @Override
+ public void apply(Project project) {
+ final NamedDomainObjectContainer<DeploymentProcessConfiguration> configs =
+ project.container(DeploymentProcessConfiguration.class);
+ configs.all(
+ config -> {
+ config.environmentSteps = new ArrayList<>();
+ });
+ project.getExtensions().add("deployments", configs);
+
+ project.afterEvaluate(this::createDeployTasks);
+ }
+
+ private void createDeployTasks(Project project) {
+ final NamedDomainObjectContainer<DeploymentProcessConfiguration> configs =
+ (NamedDomainObjectContainer<DeploymentProcessConfiguration>)
+ project.getExtensions().findByName("deployments");
+ configs.all(
+ config -> {
+ for (int stepIndex = 0;
+ stepIndex < config.environmentSteps.size();
+ stepIndex++) {
+ final String stepName = config.environmentSteps.get(stepIndex);
+ project.getTasks()
+ .register(
+ String.format(
+ Locale.ROOT,
+ "deploymentRequest_%s_%s",
+ config.name,
+ stepName),
+ DeploymentRequestProcessTask.class,
+ config,
+ stepIndex);
+ }
+ });
+ }
+}
diff --git a/buildSrc/src/main/java/deployment/DeploymentProcessConfiguration.java b/buildSrc/src/main/java/deployment/DeploymentProcessConfiguration.java
new file mode 100644
index 000000000..4663d4551
--- /dev/null
+++ b/buildSrc/src/main/java/deployment/DeploymentProcessConfiguration.java
@@ -0,0 +1,14 @@
+package deployment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DeploymentProcessConfiguration {
+ public final String name;
+
+ public List<String> environmentSteps = new ArrayList<>();
+
+ public DeploymentProcessConfiguration(String name) {
+ this.name = name;
+ }
+}
diff --git a/buildSrc/src/main/java/deployment/DeploymentRequestProcessTask.java b/buildSrc/src/main/java/deployment/DeploymentRequestProcessTask.java
new file mode 100644
index 000000000..a9da0040b
--- /dev/null
+++ b/buildSrc/src/main/java/deployment/DeploymentRequestProcessTask.java
@@ -0,0 +1,105 @@
+package deployment;
+
+import github.Deployment;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.inject.Inject;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.TaskAction;
+
+public class DeploymentRequestProcessTask extends DefaultTask {
+
+ private final DeploymentProcessConfiguration mConfiguration;
+ private final int mStepIndex;
+
+ @Input
+ public String getEnvironmentKey() {
+ return getEnvironmentName(mConfiguration, mStepIndex);
+ }
+
+ @Inject
+ public DeploymentRequestProcessTask(
+ DeploymentProcessConfiguration configuration, int stepIndex) {
+ mConfiguration = configuration;
+ mStepIndex = stepIndex;
+ setGroup("Publishing");
+ setDescription("Request deployment of " + getEnvironmentName(configuration, stepIndex));
+ }
+
+ @TaskAction
+ public void deploymentRequestAction() {
+ try {
+ deploymentRequest(getProject().getProperties(), mConfiguration, mStepIndex);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void deploymentRequest(
+ Map<String, ?> properties, DeploymentProcessConfiguration configuration, int stepIndex)
+ throws Exception {
+ final DeploymentCommandLineArgs data =
+ new DeploymentCommandLineArgs(
+ properties.get("requestDeploy.sha").toString(),
+ properties.get("requestDeploy.api_user_name").toString(),
+ properties.get("requestDeploy.api_user_token").toString());
+
+ Deployment deployment = new Deployment(data.apiUsername, data.apiUserToken);
+ if (stepIndex == 0) {
+ requestNewDeploy(deployment, data, configuration);
+ } else {
+ throw new UnsupportedOperationException(
+ "step " + stepIndex + " for " + configuration.name + " is not implemented!");
+ }
+ }
+
+ private static void requestNewDeploy(
+ Deployment deployment,
+ DeploymentCommandLineArgs data,
+ DeploymentProcessConfiguration environment)
+ throws Exception {
+ final String environmentToDeploy = getEnvironmentName(environment, 0);
+ final List<String> environmentsToKill =
+ environment.environmentSteps.stream()
+ .map(name -> getEnvironmentName(environment.name, name))
+ .filter(env -> !env.equals(environmentToDeploy))
+ .collect(Collectors.toList());
+
+ final Deployment.Response response =
+ deployment.requestDeployment(
+ new Deployment.Request(
+ data.sha,
+ "deploy",
+ false,
+ environmentToDeploy,
+ String.format(
+ Locale.ROOT,
+ "Deployment for '%s' request by '%s'.",
+ environmentToDeploy,
+ data.apiUsername),
+ Collections.singletonList("master-green-requirement"),
+ new Deployment.RequestPayloadField(environmentsToKill)));
+
+ System.out.println(
+ String.format(
+ Locale.ROOT,
+ "Deploy request response: id %s, sha %s, environment %s, task %s.",
+ response.id,
+ response.sha,
+ response.environment,
+ response.task));
+ }
+
+ private static String getEnvironmentName(String environmentName, String stepName) {
+ return String.format(Locale.ROOT, "%s_%s", environmentName, stepName);
+ }
+
+ private static String getEnvironmentName(
+ DeploymentProcessConfiguration environment, int index) {
+ return getEnvironmentName(environment.name, environment.environmentSteps.get(index));
+ }
+}
diff --git a/buildSrc/src/main/java/github/Deployment.java b/buildSrc/src/main/java/github/Deployment.java
new file mode 100644
index 000000000..985e6ed01
--- /dev/null
+++ b/buildSrc/src/main/java/github/Deployment.java
@@ -0,0 +1,103 @@
+package github;
+
+import com.google.gson.Gson;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+
+public class Deployment {
+
+ private final Gson mGson;
+
+ private final String username;
+ private final String password;
+
+ public Deployment(String username, String password) {
+ this.username = username;
+ this.password = password;
+ mGson = GsonCreator.create();
+ }
+
+ public Response requestDeployment(Request request) throws Exception {
+ final String requestJson = mGson.toJson(request);
+ System.out.println("Request: " + requestJson);
+
+ try (CloseableHttpClient client = HttpClientCreator.create(username, password)) {
+ HttpPost httpPost =
+ new HttpPost(
+ "https://api.github.com/repos/AnySoftKeyboard/AnySoftKeyboard/deployments");
+ httpPost.setHeader("Accept", "application/json");
+ httpPost.setHeader("Content-type", "application/json");
+ httpPost.setEntity(new StringEntity(requestJson, StandardCharsets.UTF_8));
+ try (CloseableHttpResponse httpResponse = client.execute(httpPost)) {
+ System.out.println("Response status: " + httpResponse.getStatusLine());
+ return mGson.fromJson(
+ new InputStreamReader(httpResponse.getEntity().getContent()),
+ Response.class);
+ }
+ }
+ }
+
+ public static class Request {
+ public final String ref;
+ public final String task;
+ public final boolean auto_merge;
+ public final String environment;
+ public final String description;
+ public final List<String> required_contexts;
+ public final RequestPayloadField payload;
+
+ public Request(
+ String ref,
+ String task,
+ boolean auto_merge,
+ String environment,
+ String description,
+ List<String> required_contexts,
+ RequestPayloadField payload) {
+ this.ref = ref;
+ this.task = task;
+ this.auto_merge = auto_merge;
+ this.environment = environment;
+ this.description = description;
+ this.required_contexts = required_contexts;
+ this.payload = payload;
+ }
+ }
+
+ public static class RequestPayloadField {
+ public final List<String> environments_to_kill;
+
+ public RequestPayloadField(List<String> environmentsToKill) {
+ environments_to_kill = environmentsToKill;
+ }
+ }
+
+ public static class Response {
+ public final String id;
+ public final String sha;
+ public final String ref;
+ public final String task;
+ public final RequestPayloadField payload;
+ public final String environment;
+
+ public Response(
+ String id,
+ String sha,
+ String ref,
+ String task,
+ RequestPayloadField payload,
+ String environment) {
+ this.id = id;
+ this.sha = sha;
+ this.ref = ref;
+ this.task = task;
+ this.payload = payload;
+ this.environment = environment;
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/github/GsonCreator.java b/buildSrc/src/main/java/github/GsonCreator.java
new file mode 100644
index 000000000..c5b4077c9
--- /dev/null
+++ b/buildSrc/src/main/java/github/GsonCreator.java
@@ -0,0 +1,10 @@
+package github;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+class GsonCreator {
+ public static Gson create() {
+ return new GsonBuilder().create();
+ }
+}
diff --git a/buildSrc/src/main/java/github/HttpClientCreator.java b/buildSrc/src/main/java/github/HttpClientCreator.java
new file mode 100644
index 000000000..86a2b1a34
--- /dev/null
+++ b/buildSrc/src/main/java/github/HttpClientCreator.java
@@ -0,0 +1,15 @@
+package github;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+class HttpClientCreator {
+ public static CloseableHttpClient create(String username, String password) {
+ BasicCredentialsProvider creds = new BasicCredentialsProvider();
+ creds.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
+ return HttpClientBuilder.create().setDefaultCredentialsProvider(creds).build();
+ }
+}
diff --git a/deployment/build.gradle b/deployment/build.gradle
new file mode 100644
index 000000000..45545a46c
--- /dev/null
+++ b/deployment/build.gradle
@@ -0,0 +1,47 @@
+import java.time.DayOfWeek
+import java.time.Instant
+
+apply plugin: 'anysoftkeyboard-deployment'
+
+deployments {
+ imeMaster {
+ environmentSteps = ['alpha_100', 'beta_100']
+ }
+ imeProduction {
+ environmentSteps = ['production_010', 'production_020', 'production_030', 'production_040', 'production_050', 'production_075', 'production_100']
+ }
+ addOns {
+ environmentSteps = ['beta_100', 'production_050', 'production_100']
+ }
+}
+
+// start - IME
+tasks.register("imeOnMasterPush").configure {
+ it.group "Publishing"
+ it.description "Deployment request on master push for IME"
+ it.dependsOn tasks.named("deploymentRequest_imeMaster_alpha_100")
+}
+
+tasks.register("imePromoteMaster").configure {
+ it.group "Publishing"
+ it.description "Deployment promoting request on master for IME"
+ //promoting ime only on Wednesday
+ it.enabled Instant.now().toCalendar().toDayOfWeek() == DayOfWeek.WEDNESDAY
+ it.dependsOn tasks.named("deploymentRequest_imeMaster_beta_100")
+}
+//TODO: release branch flows
+// end - IME
+
+// start - addons
+tasks.register("addOnsOnMasterPush").configure {
+ it.group "Publishing"
+ it.description "Deployment request on master push for all addons"
+ it.dependsOn tasks.named("deploymentRequest_addOns_beta_100")
+}
+tasks.register("addOnsPromoteMaster").configure {
+ it.group "Publishing"
+ it.description "NO-OP: Deployment request on master push for all addons"
+ //it.dependsOn tasks.named("deploymentRequest_addOns_beta_100")
+}
+//TODO: release branch flows
+// end - addons
diff --git a/gradle/apk_module.gradle b/gradle/apk_module.gradle
index dd22cd68e..6521b2946 100644
--- a/gradle/apk_module.gradle
+++ b/gradle/apk_module.gradle
@@ -96,7 +96,7 @@ if (project.ext.shouldBePublished) {
println("Locale " + Locale.getDefault())
println("file encoding " + CharsetToolkit.defaultSystemCharset)
println("File contents:")
- println("***" + playStoreWhatsNewFile.text + "***")
+ println("***" + playStoreWhatsNewFile.text + "***")
throw new IllegalStateException("whatsnew file can not be longer than 500 characters! Currently " + playStoreWhatsNewFile.text.length())
}
} else {
diff --git a/scripts/ci/ci_setup.sh b/scripts/ci/ci_setup.sh
index 9384fa9cd..91448a91a 100755
--- a/scripts/ci/ci_setup.sh
+++ b/scripts/ci/ci_setup.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
#accepting licenses - creating a folder to store the license CRC
-rm -f "${ANDROID_HOME}/licenses" || true
+rm -rf "${ANDROID_HOME}/licenses" || true
mkdir -p "${ANDROID_HOME}/licenses"
#this value was taken from my local machine, after I accepted it locally.
echo -e "8933bad161af4178b1185d1a37fbf41ea5269c55\nd56f5187479451eabf01fb78af6dfcb131a6481e\n24333f8a63b6825ea9c5514f83c2829b004d1fee\c" > "${ANDROID_HOME}/licenses/android-sdk-license"
diff --git a/scripts/retry-on-SIGSEGV.sh b/scripts/retry-on-SIGSEGV.sh
index a9aa5dcd8..c8f71f76d 100755
--- a/scripts/retry-on-SIGSEGV.sh
+++ b/scripts/retry-on-SIGSEGV.sh
@@ -6,10 +6,14 @@ shift
echo "Will retry '$*' for ${retries} times:"
function needsRetry() {
- local contents
- contents=$(cat "$(ls -t build-logging/*.log | head -1)")
+ if [[ -d "build-logging" ]]; then
+ local contents
+ contents=$(cat "$(ls -t build-logging/*.log | head -1)")
- [[ "$contents" =~ .*"finished with non-zero exit value 134".* ]] && echo "RETRY"
+ [[ "$contents" =~ .*"finished with non-zero exit value 134".* ]] && echo "RETRY"
+ else
+ echo "RETRY-NO-LOGGING-FOLDER"
+ fi
}
set +e
diff --git a/settings.gradle b/settings.gradle
index c49ce8a4d..c537e6d59 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,5 @@
rootProject.name = 'AnySoftKeyboard'
+include ':deployment'
include ':api'