Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building an Android CI Pipeline with GitHub Actions - Chicago Roboto 2020

Nate Ebel
September 28, 2020

Building an Android CI Pipeline with GitHub Actions - Chicago Roboto 2020

Learn to build a multi-functional continuous integration pipeline for Android using GitHub Actions.

Learn More:
[Samples] GitHubActionsAutomationSandbox:
https://github.com/n8ebel/GitHubActionsAutomationSandbox

[YouTube] Setup CI for Android Using GitHub Actions:
https://www.youtube.com/watch?v=K9w01h4-Wnc

[Doc] GitHub Actions Quickstart Guide:
https://docs.github.com/en/free-pro-team@latest/actions/quickstart

[Browse] GitHub Actions Marketplace:
https://github.com/marketplace?type=actions

Nate Ebel

September 28, 2020
Tweet

More Decks by Nate Ebel

Other Decks in Programming

Transcript

  1. ANDROID DEVELOPER
    @n8ebel www.goobar.io
    Nate Ebel
    BUILDING AN ANDROID

    CI PIPELINE WITH

    GITHUB ACTIONS
    #chicagoroboto
    ANDROID DEVELOPER
    @n8ebel www.goobar.io
    Nate Ebel

    View Slide

  2. CHICAGO ROBOTO
    #chicagoroboto

    View Slide

  3. GITHUB ACTIONS

    MAKES ANDROID CI

    EASY

    View Slide

  4. CI

    What is it?

    View Slide

  5. Continuous Integration:
    The process of regularly checking-in,
    building, and validating code

    View Slide

  6. Consistently build the

    Right thing at the

    Right time

    View Slide

  7. Automate the validation
    and distribution of your
    code

    View Slide

  8. Save your team time,
    energy, and headaches

    View Slide

  9. CONTINUOUS INTEGRATION PROVIDERS
    CircleCi Bitrise
    GitHub Actions

    View Slide

  10. #// concepts are transferable

    View Slide

  11. GitHub Actions

    How to start building?

    View Slide

  12. Build:
    A set of repeatable, automated tasks run
    on a remote server

    View Slide

  13. Github Actions:
    Self-contained commands that package
    specific, reusable functionality for your
    build

    View Slide

  14. Workflow:
    A triggerable set of build commands.
    Different workflows can be used for
    different build types.

    View Slide

  15. Job:
    A set of build steps run on the same
    virtual machine

    View Slide

  16. Step:
    Smallest individual element of a
    workflow. Steps perform tasks using a
    GitHub Action or your own scripts/
    commands.

    View Slide

  17. Building Blocks

    View Slide

  18. Building Blocks
    Build Pull Request
    Create Workflow

    View Slide

  19. Building Blocks
    Build Pull Request
    Add Job
    Job

    View Slide

  20. Building Blocks
    Build Pull Request
    Add Multiple Jobs
    Job 1
    Job 2
    Job 3

    View Slide

  21. Building Blocks
    Build Pull Request
    Job 1
    Job 2
    Job 3
    Add Multiple Jobs

    View Slide

  22. Building Blocks
    Build Pull Request
    Job 1
    Job 2
    Job 3
    Step 1
    Step 2
    Step 3
    Add Steps

    View Slide

  23. Building Blocks
    Build Pull Request
    Job 1
    Job 2
    Job 3
    Step 1 - Action
    Step 2 - Command
    Step 3 - Action
    Use Actions

    Use Commands

    View Slide

  24. Building Pull Requests

    How to create our first

    Android CI workflow?

    View Slide

  25. DEFINING A WORKFLOW

    View Slide

  26. Create a Pull Request Workflow File
    .github/workflows/build_pull_request.yml
    name: Build Pull Request

    View Slide

  27. Run on ‘push’
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: push

    View Slide

  28. Run on ‘pull_request’
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request

    View Slide

  29. Add a job
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:

    View Slide

  30. Checkout the code
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2

    View Slide

  31. Assemble your apk
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Assemble Debug
    run: ./gradlew assembleDebug

    View Slide

  32. Push to GitHub
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Assemble Debug
    run: ./gradlew assembleDebug

    View Slide

  33. EXPANDING OUR BUILD

    View Slide

  34. Add Gradle caching
    .github/workflows/build_pull_request.yml
    name: Build Pull Request
    on: pull_request
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Assemble Debug
    run: ./gradlew assembleDebug

    View Slide

  35. Add Gradle caching
    .github/workflows/build_pull_request.yml
    jobs:
    test_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - name: Restore Cache
    uses: actions/cache@v2
    with:
    path: |
    ~/.gradle/caches
    ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
    ${{ runner.os }}-gradle-

    View Slide

  36. Send notification to Slack
    .github/workflows/build_pull_request.yml
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - uses: technote-space/workflow-conclusion-action@v1
    - uses: 8398a7/action-slack@v3
    with:
    status: ${{ env.WORKFLOW_CONCLUSION }}
    fields: commit,ref,workflow,eventName
    author_name: ${{ github.actor }}
    icon_emoji: ':robot_face:'
    username: "Pull Request Build Status"
    text: |
    ${{ env.WORKFLOW_CONCLUSION }}:
    https://github.com/n8ebel/GitHubActionsAutomationSandbox/actions/runs/${{ github.run_id }}
    env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

    View Slide

  37. Store keys, app ids, web hooks, etc using Secrets

    View Slide

  38. #github-actions-demo in Chicago Roboto Slack community

    View Slide

  39. Send notification to Slack
    .github/workflows/build_pull_request.yml
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - uses: technote-space/workflow-conclusion-action@v1
    - uses: 8398a7/action-slack@v3
    with:
    status: ${{ env.WORKFLOW_CONCLUSION }}
    fields: commit,ref,workflow,eventName
    author_name: ${{ github.actor }}
    icon_emoji: ':robot_face:'
    username: "Pull Request Build Status"
    text: |
    ${{ env.WORKFLOW_CONCLUSION }}:
    https://github.com/n8ebel/GitHubActionsAutomationSandbox/actions/runs/${{ github.run_id }}
    env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

    View Slide

  40. Add additional steps
    .github/workflows/build_pull_request.yml
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - name: Run Debug Tests
    run: ./gradlew testDebugUnitTest
    - name: Run Debug ktlint
    run: ./gradlew ktlintDebugCheck
    - name: Run Debug Lint
    run: ./gradlew lintDebug

    View Slide

  41. Store build outputs as artifacts
    .github/workflows/build_pull_request.yml
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - name: Upload Test Reports
    if: ${{ always() }}
    uses: actions/upload-artifact@v2
    with:
    name: test-reports
    path: '**/build/reports/tests/'
    - name: Upload APK
    uses: actions/upload-artifact@v2
    with:
    name: apk
    path: app/build/outputs/apk/debug/**.apk

    View Slide

  42. PARALLELIZING JOBS

    View Slide

  43. Add a Test job
    .github/workflows/build_pull_request.yml
    test_job:
    name: Test
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Restore Cache
    uses: actions/cache@v2
    with:
    path: |
    ~/.gradle/caches
    ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
    ${{ runner.os }}-gradle-
    - name: Run Debug Tests
    run: ./gradlew testDebugUnitTest --continue
    - name: Upload Test Reports
    if: ${{ always() }}
    uses: actions/upload-artifact@v2
    with:
    name: test-reports
    path: '**/build/reports/tests/'

    View Slide

  44. Add an Android Lint job
    .github/workflows/build_pull_request.yml
    lint_job:
    name: Lint
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Restore Cache
    uses: actions/cache@v2
    with:
    path: |
    ~/.gradle/caches
    ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
    ${{ runner.os }}-gradle-
    - name: Run Debug Lint
    run: ./gradlew lintDebug
    - name: Upload Lint Reports
    if: ${{ always() }}
    uses: actions/upload-artifact@v2
    with:
    name: lint-report
    path: '**/build/reports/lint-results-*'

    View Slide

  45. Add an Assemble job
    .github/workflows/build_pull_request.yml
    assemble_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2
    - name: Restore Cache
    uses: actions/cache@v2
    with:
    path: |
    ~/.gradle/caches
    ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
    restore-keys: |
    ${{ runner.os }}-gradle-
    - name: Assemble Debug
    run: ./gradlew assembleDebug
    - name: Upload APK
    uses: actions/upload-artifact@v2
    with:
    name: apk
    path: app/build/outputs/apk/debug/**.apk

    View Slide

  46. Add a Slack notification job
    .github/workflows/build_pull_request.yml
    notification_job:
    needs: [test_job, lint_job, assemble_job]
    name: Notify Build Status
    runs-on: ubuntu-latest
    steps:
    - uses: technote-space/workflow-conclusion-action@v1
    - uses: 8398a7/action-slack@v3
    with:
    status: ${{ env.WORKFLOW_CONCLUSION }}
    fields: commit,ref,workflow,eventName
    author_name: ${{ github.actor }}
    icon_emoji: ':robot_face:'
    username: "Pull Request Build Status"
    text: |
    ${{ env.WORKFLOW_CONCLUSION }}:
    https://github.com/n8ebel/GitHubActionsAutomationSandbox/actions/runs/${{ github.run_id }}
    env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

    View Slide

  47. Handling job dependency failures
    .github/workflows/build_pull_request.yml
    notification_job:
    needs: [test_job, lint_job, assemble_job]
    name: Notify Build Status
    runs-on: ubuntu-latest
    steps:

    View Slide

  48. Handling job dependency failures
    .github/workflows/build_pull_request.yml
    test_job:
    name: Test
    runs-on: ubuntu-latest
    steps:

    View Slide

  49. Handling job dependency failures
    .github/workflows/build_pull_request.yml
    test_job:
    name: Test
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:

    View Slide

  50. SINGLE VS MULTIPLE JOBS
    Build

    Cost
    Build

    Measurement
    Build

    Times
    Build

    Complexity

    View Slide

  51. Nightly Builds

    How to schedule
    workflows?

    View Slide

  52. Create a Nightly build Workflow File
    .github/workflows/build_nightly.yml
    name: Nightly Build

    View Slide

  53. Add a scheduled trigger using cron syntax
    .github/workflows/build_nightly.yml
    name: Nightly Build
    on:
    schedule:
    - cron: '0 0 * * *'

    View Slide

  54. RUNNING UI TESTS

    View Slide

  55. Add job for Android UI tests
    .github/workflows/build_nightly.yml
    android_test_job:
    name: Android Test
    runs-on: macos-latest
    steps:
    - name: Checkout
    uses: actions/checkout@v2

    View Slide

  56. Use android-emulator-runner action
    .github/workflows/build_nightly.yml
    - name: Run Android Tests
    uses: reactivecircus/android-emulator-runner@v2
    with:
    api-level: 29
    script: ./gradlew connectedDebugAndroidTest

    View Slide

  57. Store test results
    .github/workflows/build_nightly.yml
    - name: Run Android Tests
    uses: reactivecircus/android-emulator-runner@v2
    with:
    api-level: 29
    script: ./gradlew connectedDebugAndroidTest
    - name: Upload Android Test Reports
    if: ${{ always() }}
    uses: actions/upload-artifact@v2
    with:
    name: android-test-reports
    path: '**/build/reports/androidTests/'

    View Slide

  58. DISPLAYING BUILD STATUS

    View Slide

  59. Building Feature Branches

    How to customize workflows
    for specific branches?

    View Slide

  60. Create a Feature branch workflow
    .github/workflows/build_feature_branch.yml
    name: Build Feature Branch

    View Slide

  61. Restrict workflow to specific branch naming
    pattern
    .github/workflows/build_feature_branch.yml
    name: Build Feature Branch
    on:
    push:
    branches: 'feature/**'

    View Slide

  62. SETTING VERSION CODES

    View Slide

  63. Pull VERSION_CODE from build environment
    app-versioning.gradle
    def DEFAULT_VERSION_CODE = 1
    ext.getUniqueVersionCode = {
    def code = System.getenv("VERSION_CODE")
    return (code != null && !code.isEmpty()) ? code.toInteger() : DEFAULT_VERSION_CODE
    }

    View Slide

  64. Set VERSION_CODE from GitHub Run Id
    app-versioning.gradle
    - name: Assemble
    env:
    VERSION_CODE: ${{ github.run_id }}
    run: ./gradlew assembleDebug

    View Slide

  65. DISTRIBUTING TO TESTERS

    View Slide

  66. Upload to Firebase App Distribution
    app-versioning.gradle
    - name: Upload to Firebase App Distribution
    uses: wzieba/[email protected]
    with:
    appId: ${{ secrets.FIREBASE_APP_ID }}
    token: ${{ secrets.FIREBASE_TOKEN }}
    groups: qa
    file: app/build/outputs/apk/debug/app-debug.apk

    View Slide

  67. View Slide

  68. Simplifying Releases

    How to automate release
    workflows?

    View Slide

  69. Create a Release workflow
    .github/workflows/build_release.yml
    name: Build Release

    View Slide

  70. Create a Release workflow
    .github/workflows/build_release.yml
    name: Build Release
    on:
    release:
    types: [published]

    View Slide

  71. Assemble Release variant
    .github/workflows/build_release.yml
    name: Build Release
    on:
    release:
    types: [published]
    jobs:
    assemble_job:
    name: Assemble
    runs-on: ubuntu-latest
    steps:

    - name: Assemble Release
    run: ./gradlew assembleRelease

    View Slide

  72. Sign Release apk
    .github/workflows/build_release.yml

    - name: Assemble Release
    run: ./gradlew assembleRelease
    - name: Sign Release
    uses: r0adkll/sign-android-release@v1
    with:
    releaseDirectory: app/build/outputs/apk/release
    signingKeyBase64: ${{ secrets.SIGNING_KEY }}
    alias: ${{ secrets.ALIAS }}
    keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
    keyPassword: ${{ secrets.KEY_PASSWORD }}

    View Slide

  73. Add APK as Release binary
    .github/workflows/build_release.yml
    - name: Sign Release
    uses: r0adkll/sign-android-release@v1
    with:
    releaseDirectory: app/build/outputs/apk/release
    signingKeyBase64: ${{ secrets.SIGNING_KEY }}
    alias: ${{ secrets.ALIAS }}
    keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
    keyPassword: ${{ secrets.KEY_PASSWORD }}
    - name: Upload APK to Release
    uses: skx/github-action-publish-binaries@master
    env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    with:
    args: app/build/outputs/apk/release/**.apk

    View Slide

  74. Upload APK to Google Play
    .github/workflows/build_release.yml
    - name: Upload APK to Release
    uses: skx/github-action-publish-binaries@master
    env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    with:
    args: app/build/outputs/apk/release/**.apk
    - name: Upload to Google Play
    uses: r0adkll/upload-google-play@v1
    with:
    serviceAccountJson: ${{ SERVICE_ACCOUNT_JSON }}
    packageName: com.n8ebel.githubactionsautomationsandbox
    releaseFile: app/build/outputs/apk/release/app-release-signed.apk
    track: internal
    whatsNewDirectory: distribution/whatsnew
    mappingFile: app/build/outputs/mapping/release/mapping.txt

    View Slide

  75. GITHUB ACTIONS

    GO FAR BEYOND ANDROID

    View Slide

  76. ACTION EXAMPLES
    First Time
    Contributors
    Danger
    Integration
    Semantic
    Versioning
    Auto

    Commit
    apk Size
    Comparison
    Code

    Coverage

    View Slide

  77. Real World Experience

    How do GitHub Actions perform
    in production projects?

    View Slide

  78. EXPERIENCES WITH GITHUB ACTIONS
    Faster

    Builds
    Reduced

    Complexity
    More

    Approachable
    Improved

    Failure

    Visibility
    Increased

    Automation
    Great

    Documentation

    View Slide

  79. Where are the pain points?

    View Slide

  80. Where is time lost?

    View Slide

  81. Build only what you need
    to take action

    View Slide

  82. #// where to go next?

    View Slide

  83. LEARN MORE
    Marketplace

    https:#//github.com/marketplace?type=actions
    Documentation

    https:#//docs.github.com/en/free-pro-team@latest/actions

    View Slide

  84. View Slide

  85. THANKS FOR WATCHING
    ANDROID DEVELOPER
    @n8ebel www.goobar.io
    Nate Ebel
    #chicagoroboto

    View Slide