Meaning of ${{foo}} in a GitHub Action

Is it true that the sequence {{ is a pre processing directive for the GitHub pipeline runner and I can’t even have that in a comment without b0rking the pipeline.

I needed to pass my variable to the next action to use it which seems unnecessary complicated:


name: Build project for Unity 2020.x in GitHub actions

on:

  workflow_dispatch:
    inputs:
      webgl-required:
        description: 'webgl-required'     
        required: false
        default: 'false'
      android-required:
        description: 'android-required'
        required: false
        default: 'false'
      ios-required:
        description: 'ios-required'     
        required: false
        default: 'false'
      pc-required:
        description: 'pc-required'
        required: false
        default: 'false'
      macos-required:
        description: 'macos-required'     
        required: false
        default: 'false'
      linux-required:
        description: 'linux-required'
        required: false
        default: 'false'
      all-required:
        description: 'all-required'
        required: false
        default: 'false'

jobs:

  data:
    name: Prepare build info
    runs-on: ubuntu-latest
    outputs:
      build-matrix: ${{ steps.build-matrix.outputs.matrix }}
    steps:
      - name: Prepare build matrix
        id: build-matrix-prep
        run: |
          echo "Create new target array"
          stargets=''
          if [  "${{ github.event.inputs.ios-required }}" == "true" ]; then
            stargets='\"iOS\"'
          fi
          if [[  "${{ github.event.inputs.android-required }}" == "true"  && $stargets == '' ]]; then
            stargets="\\\"Android\\\""
          elif [  "${{ github.event.inputs.android-required }}" == "true" ]; then
            stargets=$stargets,'\"Android\"'
          fi
          if [[  "${{ github.event.inputs.pc-required }}" == "true"  && $stargets == "" ]]; then
            stargets="\\\"StandaloneWindows64\\\""
          elif [  "${{ github.event.inputs.pc-required }}" == "true" ]; then
            stargets="${stargets},\\\"StandaloneWindows64\\\""
          fi
          if [[  "${{ github.event.inputs.webgl-required }}" == "true"  && $stargets == ''  ]]; then
            stargets='\"WebGL\"'
          elif [  "${{ github.event.inputs.webgl-required }}" == "true" ]; then
            stargets="${stargets},\\\"WebGL\\\""
          fi      
          
          echo $stargets  
          # This will break the pipeline even inside a comment ${{stargets}}
          echo "action_trg=${stargets}" >> $GITHUB_ENV
      - name: Use the value
        id: build-matrix
        run: |
          echo "::set-output name=matrix::{\"targetPlatform\":[${{ env.action_trg }}]}"
          # echo "::set-output name=matrix::{\"targetPlatform\":[\"Android\",\"iOS\"]}"

  build:
    name: ${{ matrix.targetPlatform }} - build project
    runs-on: ubuntu-latest
    needs: [data]
    if: github.event.inputs.all-required == 'true' || github.event.inputs.android-required == 'true' || github.event.inputs.ios-required == 'true' || github.event.inputs.webgl-required == 'true'
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.data.outputs.build-matrix)}}
    steps:        
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
          lfs: true
      - uses: actions/cache@v2
        with:
          path: Library
          key: Library-${{ matrix.targetPlatform }}
          restore-keys: Library-
      - name: Free disk space
        run: .github/workflows/scripts/free_disk_space.sh
      - uses: game-ci/unity-builder@v2
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          targetPlatform: ${{ matrix.targetPlatform }}
      - uses: actions/upload-artifact@v2
        with:
          name: Build-${{ matrix.targetPlatform }}
          path: build/${{ matrix.targetPlatform }}
      - name: ${{ matrix.targetPlatform }} - build project
        if: github.event.inputs.webgl-required == 'true'

        uses: actions/download-artifact@v2
        with:
          name: Build-WebGL
      - name: Commit release
        if: github.event.inputs.webgl-required == 'true'
        run: |
          git stash
          git checkout webgl
          rm -rf docs
          mv WebGL docs
          git config --global user.name 'Niklas'
          git config --global user.email 'montao@users.noreply.github.com'
          git add docs
          git commit -m "Automated commit" docs
          git push

${{ ... }} is used to reference workflow variables (note the $). I’d be very surprised if having it in a comment broke anything, but I don’t think I’ve tried that.

Could you explain more what exactly you’re trying to achieve, and how it breaks? It’s not obvious from the workflow.

1 Like

In fact it did break when I put it in a comment! I can try and make that a separate example.

I want to create a build pipeline for Unity game project that creates different artifacts for different targets (platforms such as Android, Linux, Mac…) depending on the inputs.

I try to create a data structure which contains the names of the targets and build them. It works but it seems more like a trick I did. I’m a little surprised that I need two actions and pass the variable to the next action to set the output from the job to the next job:

         echo "action_trg=${stargets}" >> $GITHUB_ENV
      - name: Use the value
        id: build-matrix
        run: |
          echo "::set-output name=matrix::{\"targetPlatform\":[${{ env.action_trg }}]}"
 

The above code works but I was unable to do it in the same action. Is that more clear what I try and what the issue is?

          echo $stargets  # Here's my variable
         # But I cannot substitute and retrieve its value in the command to the runner
          echo "::set-output name=matrix::{\"targetPlatform\":[${stargets}]}"

I read that double quotes should not be used when echoing commands to the runner so maybe I should try something similar to the following instead.

          echo $stargets  # Here's my variable
         # But I cannot substitute and retrieve its value in the command to the runner
          echo '::set-output name=matrix::{\"targetPlatform\":['$stargets']}

It’s so strange.

Why can’t I do this:

          echo $stargets  # my output is correct
          echo "::set-output name=matrix::{\"targetPlatform\":[${stargets}]}"

Instead I must guess and find out that I should do this:

          echo $stargets 
          echo "action_trg=${stargets}" >> $GITHUB_ENV
      - name: Use the value
        id: build-matrix
        run: |
          echo "::set-output name=matrix::{\"targetPlatform\":[${{ env.action_trg }}]}"

Why? What exactly happens if you do? What does the output look like, and what should it look like?

1 Like

Error when evaluating 'strategy' for job 'build'. (Line: 78, Col: 15): Unexpected character encountered while parsing value: \. Path 'targetPlatform', line 1, position 19.,(Line: 78, Col: 15): Unexpected value ''

The variable output is \"WebGL\" but it is not substituted in the expression that follows.


          echo $stargets 
          echo "::set-output name=matrix::{\"targetPlatform\":[${stargets}]}"
  build:
    name: ${{ matrix.targetPlatform }} - build project
    runs-on: ubuntu-latest
    needs: [data]
    if: github.event.inputs.all-required == 'true' || github.event.inputs.android-required == 'true' || github.event.inputs.ios-required == 'true' || github.event.inputs.webgl-required == 'true'
    strategy:
      fail-fast: false
      matrix: ${{fromJson(needs.data.outputs.build-matrix)}}

I want to use the output in the next job.

If you’re writing complex shell scripts to use within a Workflow, I’d recommend creating them outside of the Workflow and then execute the script within the Workflow by passing in the appropriate parameters. For example, create .github/workflows/build-matrix.sh which accepts webgl android ios parameters and execute that from run:

run: ./build-matrix.sh ${{ github.event.inputs.ios-required }} ${{ github.event.inputs.ios-required }}

If you do wish to write shell scripts in-line then you can avoid mixing in GitHub’s expression syntax by using environment variables instead. Step environment variables allow you to separate the script logic from the parameter logic. For example:

run: |
  targets=''
  if [ $IOS == "true" ]; then
    ...
env:
  IOS: "${{ github.event.inputs.ios-required }}"

https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsenv

That said: moving to GitHub from other CI platforms can bring with it bad habits. Personally, I believe the real power of GitHub Actions is the ability to build a Workflow that doesn’t re-invent the wheel and instead use behaviour available through third-party Actions. For example, instead of building the matrix job yourself using a shell script you could look at the GitHub Actions Marketplace and see if something exists for your use-case. I searched the Marketplace for “matrix” and “json” and came across json-array-builder which looks like it offers what you need.

Finally, I think the right solution for this would be to avoid any complex matrix-assembly logic and instead try out a simple fixed matrix that is filtered based on the input. The key benefit of this approach is that if you decide to run this Workflow on other events (such as a push) you don’t need to rewrite the matrix assembly logic: instead, you would just adjust the filtering logic.

Thanks for the answers. The experience reminds me of the saying “Why program by hand in five days what you can spend five years of your life automating?