Manually Trigger Android Build Tasks Using GitHub Actions

Let’s say you have multiple tasks that you have running on a schedule; could be things like UI tests, unit tests, or checking dependencies

While it’s great to have these on an automated schedule, you may sometimes want to run these in an ad hoc fashion to gather quick feedback. Or, maybe you have some task that you want to automate, but not frequently enough to put on a specific schedule.

Currently, the GitHub Actions UI doesn’t allow the manual execution of workflows.

However, we can make use of a repository_dispatch event sent using the GitHub api to trigger a workflow. This process was covered in my previous posts:

Not only can we trigger a workflow with a repository_dispatch event, but we can attach additional data to that event. That data can then be used to customize the control flow of our workflow.

In this post, we’re going to walk through a couple of examples on how we might make use of these triggered workflows for building Android projects.

Defining our workflow

We’re going to start out by defining a simple GitHub Actions workflow with a couple of tasks.

name: Triggered Tasks

on:
  repository_dispatch:

jobs:
  build:
    name: Check Dependencies & Tests
    runs-on: ubuntu-latest
    steps:

      - name: Checkout
        uses: actions/checkout@v1

      - name: Check Dependency Versions
        run: ./gradlew dependencyUpdates

      - name: Run Tests
        run: ./gradlew test

This workflow does two things when triggered:

  1. Check for dependency updates
  2. Runs the tests

These could certainly be integrated into your primary build and run on every commit, or PR. However, you might not care about dependency updates on every commit, or maybe you only need to run your complete test suite once in a while.

The example is meant to simulate tasks that you want to automate, but don’t need to run all the time.

With this workflow in place, we can trigger the dependency update check, and the running of our tests, any time we want. If this workflow doesn’t make sense, it might be helpful to review some of my previous posts before continuing on.

Triggering explicit workflow tasks

There are a couple of potential drawabacks to this current workflow:

  1. The entire workflow is run anytime a repository_dispatch event is received
  2. All of the tasks are run when the workflow is executed

Depending on what types, and how many, tasks we want to be able to trigger, we may want the ability to trigger only specific tasks at a time.

For example, you may want to check for dependency updates without running the test suite.

To solve this problem, we have a couple of options:

  1. We could define multiple workflows; each responding to a unique repository_dispatch type. We could then control which workflows are run by modifying the event_type property of our repository_dispatch event.
  2. We could send custom properties with the repository_dispatch.client_payload and then control which workflow tasks are run based on those property values.

Both of these approaches have their merits, and we’re going to walk through them both in this post.

Controlling workflow execution using event_type

In our working example, we have two specific tasks as part of our workflow:

  • dependencyUpdates
  • test

To enable us to trigger these tasks independently of one another, we could create two separate workflows.

We’ll start by creating a workflow for checking dependency versions:

name: Triggered Dependency Checks

on:
  repository_dispatch:
    types: check-dependencies

jobs:
  build:
    name: Check Dependencies
    runs-on: ubuntu-latest
    steps:

      - name: Checkout
        uses: actions/checkout@v1

      - name: Check Dependency Versions
        run: ./gradlew dependencyUpdates

Notice that we’ve updated the repository_dispatch trigger to only respond to the type check-dependencies.

Next, we’ll create a workflow to run our tests:

name: Triggered Tests

on:
  repository_dispatch:
    types: run-tests

jobs:
  build:
    name: Run Tests
    runs-on: ubuntu-latest
    steps:

      - name: Checkout
        uses: actions/checkout@v1

      - name: Run Tests
        run: ./gradlew test

Here, we’ve specified a repository_dispatch type of run-tests

With these workflows in place, we can trigger them individually by changing the type.

To check for dependency updates we could use the following curl command:

curl -H "Accept: application/vnd.github.everest-preview+json" \
    -H "Authorization: token <your-token-here>" \
    --request POST \
    --data '{"event_type": "check-dependencies"}' \
    https://api.github.com/repos/n8ebel/GitHubActionsAutomationSandbox/dispatches

To run the test suite, the curl command might look like this:

curl -H "Accept: application/vnd.github.everest-preview+json" \
    -H "Authorization: token <your-token-here>" \
    --request POST \
    --data '{"event_type": "run-tests"}' \
    https://api.github.com/repos/n8ebel/GitHubActionsAutomationSandbox/dispatches

This approach gives us the ability to trigger specific tasks independenctly of one another. It also makes each workflow specific to a unique task; making them easier to understand.

There are a couple of potential drawbacks to this approach however:

  1. Running all the triggerable workflows requires sending multiple POST requests with different event_type values
  2. You have to remember what the different event_type values are and somehow make them easy to find when sharing how to trigger the workflows.

By making use of the repository_dispatch.client_payload we can improve upon these drawbacks.

Controlling workflow execution using client_payload

Rather than creating separate workflows and event_type values for each task we may want to trigger, we can create a single workflow and control the execution of individual tasks based on the values present in the repository_dispatch.client_payload.

We can introduce conditional logic to our workflow tasks by using the following syntax:

jobs.<job_id>.steps.if

With this in mind, we can create a single workflow that looks like the following:

name: Triggered Tasks

on:
  repository_dispatch:

jobs:
  build:
    name: Check Dependencies & Tests
    runs-on: ubuntu-latest
    steps:

      - name: Checkout
        uses: actions/checkout@v1

      - name: Check Dependency Versions
        if: github.event.client_payload.dependencies == true
        run: ./gradlew dependencyUpdates

      - name: Run Tests
        if: github.event.client_payload.tests == true
        run: ./gradlew test

Now, each task corresponds to a client_payload property. To change which tasks are executed when our workflow is triggered, we simply update the boolean values of those properties.

To run both tasks when triggering the repository_dispatch event, we could pass the following POST body:

{
   "event_type": "checks",
   "client_payload": {
     "dependencies": true,
     "tests": true
   }
 }

To run tests, but not check for dependencies, we could update the body as follows:

{
   "event_type": "checks",
   "client_payload": {
     "dependencies": false,
     "tests": true
   }
 }

This approach enables us to use a single request to control the execution of the workflow; whether we want 1, or many, tasks to be run. Additionally, since the properties are present in the POST body, it’s immediately obvious, when sharing the curl command, what can be configured.

We could continue to add properties to the client_payload to add further granularity to what we want to run.

For example, if we added specific test tasks, we could differentiate between different types of tests to trigger:

{
   "event_type": "checks",
   "client_payload": {
     "dependencies": true,
     "unit": true,
     "integration": false
   }
 }

Using this approach, our workflow file may become a little more complex as all tasks are consolidated into a single file, and as conditional logic is added, but it also makes it very clear where to store these types of manually triggerable tasks.

This approach also makes it easier to share a curl or Postman request with the rest of your team because the request body contains all the available types and properties.

Conclusions

Whether you need this type of customization in your workflows will depend on your needs. Hopefully this post has shown that there are multiple solutions for customizing the behavior of a manually triggered GitHub Actions workflow.

If you want to explore a working example, check out my GitHub repo.