Android Release with GitHub Actions
Table of Contents
In this article I will show you how you can use GitHub Actions to mananage your CI flows. In the end of the article you will be able to publish release with a single command on your terminal.
The YAML file we will write will include, building an APK, signing the APK, creating release and some directives to make some triggers.
Adding Github Actions to your project #
We need to put out action
scripts in special directory(.github/workflows
). GitHub will check this directory and figure out the actions
. Those actions are configured in YAML format.
Now, create the directory .github/workflows
and create a file named pipeline.yml
in that newly created directory.
In this YAML file will include a job and a directive about how will it trigger. A workflow may include one or more jobs. These jobs can be executes in parallel or consecutively.
Name the Workflow #
I will share the script part by part and explain what it does along the way. In the end of this article you can find the final script 🙂
We will name the workflow in the beginning of the file. This name will show up in the Actions tab in GitHub.
You can do this by adding name
attribute.
name: CI
Triggering #
Now, we are adding the conditions for this workflow to trigger. GitHub Releases are often in relation with git tags. This way it is easier to checkout to the version for a specific release.
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Trigger conditions are defined with on keyword. Following conditions above shows that the workflow will be triggered when a tag with a pattern v*
has pushed.
By adding workflow_dispatch
we can enable running this workflow manually in GitHub Actions tab.
So far this conditions are suitable for creating releases. If you want to run tests or lint after every commit, you can create another workflow for that. That conditions may look like this(depending on your branch name);
push:
branches: [ main ]
Jobs #
This workflow contains a single job called build. Jobs in YAML file will go under a keyword jobs
. Create a job with name you choose with an indent, in this case I called it build. And in that job define a type of runner that the job will run on with runs-on
keyword. With the information so far, the job will look like this:
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
...
...
Possible values for runs-on
can be found in here
I passed ubuntu-latest
for my workflow. This allows me to run by workflow on latest version of ubuntu.
I briefly mentioned about the steps in build job. Those steps are;
- Checkout
- JDK envirionment setup
- APK output
- Signing APK
- Upload APK (to be able to use outside the workflow, it is needed for creating release artifact)
- Creating a Release
- Saving the artifact name to use in next step
- Upload the artifact
Lets begin with the first step. These steps will be in a decleration named(you guessed it) steps
.
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
uses: actions/checkout@v2
Every step hes a name. These names will be visible on Actions tab after running the workflow showing it is either success or failed. It will be looking like this;
uses
keywords defines which repository will be used for this step. You can check action repositories from https://github.com/actions. Every repository has detailed information about how to use it.
With the first step, we give job to access to the repository. In the next step we will setup the JDK environment. I used Java version 11 for my workflow. You can use different versions like ‘1.8’.
- name: Set up our JDK environment
uses: actions/setup-java@v1.4.3
with:
java-version: 11
In the next step we build the APKs
## Build all our Build Types at once ##
- name: Build all artifacts
id: buildAllApks
uses: eskatos/gradle-command-action@v1.3.3
with:
gradle-version: current
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: assembleRelease
Next step is signing the APK;
## Sign our artifact##
- name: Sign artifact
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
alias: ${{ secrets.KEYALIAS }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
Before going forward, lets look at what we did here. We used a feature called secrets
that we didn’t use before.
Secrets are the sensitive information you don’t want to display publicly. Like keys or passwords. GitHub can access them in workflows.
Normally we use JKS files to sign an APK. While creating a JKS file, some of the information in the snippet above are used. We can add them the GitHub from Secrets section to use in our workflows.
To add them in Secrets; go to your project page and click the Settings tab. In the menu on the left, click Secrets in the Security group. Then select Actions. In the opened page you will see Environment and Repository Secrets. To add a secret to use in our workflow, go and click New Repository Secret. We will add 4 secrets to use in our workflow. Those are KEYSTORE, KEYALIAS, KEYSTORE_PASSWORD and KEY_PASSWORD.
Adding Keystore to GitHub as a Secret #
We cannot just add the JKS file to GitHub, therefore we need to add it in the format that GitHub will understand. That format is BASE64. To print out your JKS file in BASE64 format open your terminal, go to the directory that your JKS file is located and type the command below(considering you already have openssl installed).
openssl base64 -A -in <your-key-file>.jks
This command will print out our file in BASE64 format. Copy the value and go back to the GitHub page and paste it in value
section. In the name section type KEYSTORE
. Then click Add secret.
Congrats! You just added a secret, now repeat the process to add KEYALIAS
, KEYSTORE_PASSWORD
and KEY_PASSWORD
, using the values you used when creating the JKS file.
In the next step, we will upload the APK as an artifact. This step allows us to use the APK outside of the job, which we will need when creating artifacts.
- name: Upload our APK
uses: actions/upload-artifact@v2.2.0
with:
name: Release artifact
path: ${{steps.sign_app.outputs.signedReleaseFile}}
Next step is creating release.
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
In this step we used a special secret value called GITHUB_TOKEN
. You don’t need to add it to your project. It is added by default, and recognize by GitHub and can be used in workflows.
With this step we can create a release. Name of the release will be like Release v1.0.2. You can configure this by your needs.
Next step, we save artifact names to use in other steps;
- name: Save name of our Artifact
id: set-result-artifact
run: |
ARTIFACT_PATHNAME_APK=${{steps.sign_app.outputs.signedReleaseFile}}
ARTIFACT_NAME_APK=$(basename $ARTIFACT_PATHNAME_APK)
echo "ARTIFACT_NAME_APK is " ${ARTIFACT_NAME_APK}
echo "ARTIFACT_PATHNAME_APK=${ARTIFACT_PATHNAME_APK}" >> $GITHUB_ENV
echo "ARTIFACT_NAME_APK=${ARTIFACT_NAME_APK}" >> $GITHUB_ENV
We did somethings different in this step. When mentioning steps, I said name
filed is required. In prior steps, we used uses
keyword and passed some repository locations.
If you don’t want to use any action repository and just run commands, use run
. Each step needs to use either uses
or run
.
Next step, we upload the APK as an artifact asset;
- name: Upload our Artifact Assets
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.ARTIFACT_PATHNAME_APK }}
asset_name: ${{ env.ARTIFACT_NAME_APK }}
asset_content_type: application/zip
Final Script #
Gathering all the parts we used, our final script will look like this;
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
# Sequence of patterns matched against refs/tags
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
uses: actions/checkout@v2
- name: Set up our JDK environment
uses: actions/setup-java@v1.4.3
with:
java-version: 11
## Build all our Build Types at once ##
- name: Build all artifacts
id: buildAllApks
uses: eskatos/gradle-command-action@v1.3.3
with:
gradle-version: current
wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true
arguments: assembleRelease
## Sign our artifact##
- name: Sign artifact
id: sign_app
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: app/build/outputs/apk/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
alias: ${{ secrets.KEYALIAS }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Upload our APK
uses: actions/upload-artifact@v2.2.0
with:
name: Release artifact
path: ${{steps.sign_app.outputs.signedReleaseFile}}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- name: Save name of our Artifact
id: set-result-artifact
run: |
ARTIFACT_PATHNAME_APK=${{steps.sign_app.outputs.signedReleaseFile}}
ARTIFACT_NAME_APK=$(basename $ARTIFACT_PATHNAME_APK)
echo "ARTIFACT_NAME_APK is " ${ARTIFACT_NAME_APK}
echo "ARTIFACT_PATHNAME_APK=${ARTIFACT_PATHNAME_APK}" >> $GITHUB_ENV
echo "ARTIFACT_NAME_APK=${ARTIFACT_NAME_APK}" >> $GITHUB_ENV
- name: Upload our Artifact Assets
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ${{ env.ARTIFACT_PATHNAME_APK }}
asset_name: ${{ env.ARTIFACT_NAME_APK }}
asset_content_type: application/zip
After we went through all this process all you need to do to create a release is add a tag in your local and push to your remote repository(GitHub)
To Add a tag, type this in your project directory;
git tag v1.0.0
And you use this command to push the tag to your GitHub;
git push origin v1.0.0
After this operations, Github will run workflow regarding your YAML file and as a result, it will create a release named Release v1.0.0
You can see this file and the project I have used from https://github.com/aslansari/pokedeck
Hope this article is useful to you 😎