Skip to main content

Android Release with GitHub Actions

·8 mins

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;

workflow_steps.png

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 😎

Please share this article if you think other people can benefit from it. You can always reach me from my email for questions or suggestions.