DEV Community

Warren
Warren

Posted on

Github Actions

I've been using different build systems for years, but after playing with github actions quite a bit there was still some things that I was only managing based on trial and error. I've written this guide to try and fill in those gaps. If you've been through the Quickstart for GitHub Actions and Understanding GitHub Actions documentation but still have some questions I'm hoping this post will answer some of them.

First some definitions:

  • Workflows are combinations of jobs
  • Jobs are combinations of steps that are run on a virtual machine, they can even be a workflow of their own
  • Steps are either actions or shell commands

Sharing data in your workflow

This was one of the bits that I felt the documentation didn't explain clearly enough. All the information is there, but spread across multiple pages and can take quite a while to put it all together.

Environment variable types

It's worth baring in mind that because most of what follows uses environment variables under the hood it is a string, so even though the value could be 'true' or 'false' you would need an expression that explicitly compares it to those values. e.g. if: env.isTag == 'true' would work, but if: env.isTag alone would not.

Inputs to workflows however can be string, number or boolean types.

Sharing data between steps in a job

If one of your steps has an output that you need in a later step within the same job you can set it as an environment variable. This is done by sending a statement setting it to the $GITHUB_ENV file. A minimal demonstration of this is:

steps:
  - name: Set myValue
    run: echo "myValue=wibble" >> $GITHUB_ENV
  - name: Echo myValue
    run: echo "$myValue"
Enter fullscreen mode Exit fullscreen mode

Ofcourse the above isn't that useful, the wibble part would usually be a calculated value. You would usually use some of the calculate the value from some other environment variables. You can either calculate your value in the bash script, e.g.

env:
  wibble: wibble
steps:
  - name: Set myValue
    run: echo "myValue=$GITHUB_REF_NAME-$wibble" >> $GITHUB_ENV
Enter fullscreen mode Exit fullscreen mode

or you can use the javascript context ${{ }} and any of the expressions or contexts to calculate your value, e.g.

env:
  wibble: wibble
steps:
  - name: Set myValue
    run: echo "myValue=${{ github.ref_name + "-" + env.wibble }} >> $GITHUB_ENV
Enter fullscreen mode Exit fullscreen mode

These two blocks should be equivalent, but the expressions mean I generally use the JS version.

The key bit is that it is sent to the $GITHUB_ENV file. This is what will make it available as an environment variable to the steps that follow it. But only within that job. What if you want to share that value with different jobs?

Sharing data between jobs within a workflow

In the above you used the $GITHUB_ENV file to make a value available to later steps, but if you want to share a value to a different job you need to use the $GITHUB_OUTPUTS file instead. But that would be too easy. In addition you also need to declare it as an output.

Within your job you will need to add an outputs section that uses the JS context to access the value you will output. You do this by accessing the steps context object. The step that you use will now need an id so that you can access it through steps.<stepId>.
For example, if you want a job that outputs a value that says if this build was triggered by a tag:

jobs:
  isTagJob:
    runs-on: ubuntu-latest
  outputs:
    isTag: ${{ steps.set_istag.outputs.isTag }}
  steps:
    - id: set_istag   # step id used to find the output above
      name: Set isTag # name that will appear in the logging for this section
      run: echo "isTag=${{ startsWith(github.ref, 'refs/tags/') }}" >> $GITHUB_OUTPUT
Enter fullscreen mode Exit fullscreen mode

This will make the isTag value available to any jobs dependant on this one through the needs context. The format is needs.<jobId>.outputs.<outputName> so the above would be needs.isTagJob.outputs.isTag. For example the following will only run the announce_tag step if isTag is 'true'.

...
  announceTag:
    runs-on: ubuntu-latest
    needs: [isTagJob] # This makes it dependent on the `isTagJob` job above
    steps:
      - id: announce_tag
        name: Announce tag
        if: needs.isTag.outputs.isTag == 'true'
        run: echo ::notice::This build is for a tag
Enter fullscreen mode Exit fullscreen mode

Sharing data to reusable workflows

Workflows that are called by other workflows use workflow_call as their on trigger. You can pass values to these workflows by defining inputs in that workflow and passing the values in using with in the calling workflow.

For example the file .github/workflows/build-dotnet.yml shown below takes two inputs projectDir and isTag and then uses them in it's jobs.

name: Build Dotnet

on:
  workflow_call:
    inputs:
      projectDir:
        required: true
        type: string
      isTag:
        required: true
        type: boolean

jobs:
  build-code:
    runs-on: ubuntu-latest
    steps:
      - id: build
        name: "Build ${{ inputs.projectDir}}"
        run: dotnet build ${{ inputs.projectDir}}
      - id: push_artifact
        name: "Push ${{ inputs.projectDir}} artifact"
        if: inputs.isTag # the input is a boolean so don't need to compare to `'true'` like with env vars.
        uses: actions/upload-artifact@v3
          with:
            name: ${{ inputs.projectDir }}
            path: ./temp/${{ inputs.projectDir}}/**/*
Enter fullscreen mode Exit fullscreen mode

The calling workflow will need to ensure it provides the values correctly so that this workflow can use them. The calling workflow code could look like:

name: Build
on:
    push:
jobs:
  isTagJob:
    runs-on: ubuntu-latest
    outputs:
      isTag: ${{ steps.set_istag.outputs.isTag }}
    steps:
      - id: set_istag   # step id used to find the output above
        name: Set isTag # name that will appear in the logging for this section
        run: echo "isTag=${{ startsWith(github.ref, 'refs/tags/') }}" >> $GITHUB_OUTPUT
  build-dotnet-proj-a:
    needs: [isTagJob]
    uses: ./.github/workflows/build-dotnet.yml
    with:
      projectDir: projects/projectA
      isTag: ${{needs.isTagJob.outputs.isTag == 'true'}}
  build-dotnet-proj-b:
    needs: [isTagJob]
    uses: ./.github/workflows/build-dotnet.yml
    with:
      projectDir: projects/projectB
      isTag: ${{needs.isTagJob.outputs.isTag == 'true'}}
Enter fullscreen mode Exit fullscreen mode

The same syntax for inputs in reusable workflows is also used actions.

Top comments (0)