How to share matrix between jobs

I’m trying to build a regression test suite that basically builds a client and a server binary from different release versions, and then runs them against each other.

builder:
  strategy:
    matrix:
      cfg:
        - { go: 1.13, commit: v1.0.0 }
        - { go: 1.14, commit: v1.2.0 }
  steps:
    // upload binary named "bin-${{ matrix.cfg.go }}-${{ matrix.cfg.commit }}"
runner:
  strategy:
    matrix:
      server:
        - { go: 1.13, commit: v1.0.0 }
        - { go: 1.14, commit: v1.2.0 }
      client:
        - { go: 1.13, commit: v1.0.0 }
        - { go: 1.14, commit: v1.2.0 }
  steps:
     - // download binary
     - // run each server against each client

As you can see, I have repeat the build matrix three times in my configuration, which is error prone when I remove / add versions there.

My first idea was to just use YAML anchors here, but it turns out that GitHub Actions doesn’t support those for whatever reason.

I also tried to define the matrix as an output in the builder job:

builder:
  outputs:
    matrix: ${{ toJson(matrix) }}

And then use it in the runner job:

runner:
  strategy:
    matrix:
      client: ${{ fromJson(needs.builder.outputs.matrix) }}
      server: ${{ fromJson(needs.builder.outputs.matrix) }}

This just results in a “A mapping was not expected” error message.

Any idea how I can solve this?

1 Like

Hi @marten-seemann,

Glad to see you in Github Community Forum!

To share matrix between jobs, please follow the usage of doc here , in your code the runner job cannot get the value from ${{ toJson(matrix) }} since format issue.

Please try below code for your case:

  job1:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
    - id: set-matrix
      run: |
        echo "::set-output name=matrix::[{\"go\":\"1.13\",\"commit\":\"v1.0.0\"},{\"go\":\"1.14\",\"commit\":\"v1.2.0\"}]"
  builder:
    needs: job1
    runs-on: ubuntu-latest
    strategy:
      matrix: 
        cfg: 
          - ${{fromJson(needs.job1.outputs.matrix)}}
    steps:
    - run: |
        echo bin-${{ matrix.cfg.go }}-${{ matrix.cfg.commit }}
  runner:
    needs: [job1,builder]
    runs-on: ubuntu-latest
    strategy:
      matrix:
        server: 
          - ${{fromJson(needs.job1.outputs.matrix)}}
        client:
          - ${{fromJson(needs.job1.outputs.matrix)}}
    steps:
    - run: |
          echo ${{ matrix.server.go }}-${{ matrix.server.commit }}
          echo ${{ matrix.client.go }}-${{ matrix.client.commit }}

image

You can refer to my workflow for the details.

Thanks

Hi @weide-zhou, thank you for your reply.

I was hoping to avoid the

echo "::set-output name=matrix::[{\"go\":\"1.13\",\"commit\":\"v1.0.0\"},{\"go\":\"1.14\",\"commit\":\"v1.2.0\"}]"

This code is not very readable, especially when I want to add 10 or so more configurations there.

Hi @marten-seemann,

Since the job outputs must be string format(doc here), i’m afraid you have to pull all configurations in a line.

Thanks

Hi @weide-zhou,

I’m wondering if it’s somehow possible to use the toJson function to feed a human-readable JSON object into the configuration file, something like this:

builder:
  strategy:
    matrix:
      cfg:
        - { go: 1.13, commit: v1.0.0 }
        - { go: 1.14, commit: v1.2.0 }
  outputs:
    matrix: ${{ steps.set-matrix.outputs.matrix }}
  steps:
    - id: set-matrix
      run: echo "::set-output name=matrix::${{ toJson(matrix.cfg) }}"
    // upload binary named "bin-${{ matrix.cfg.go }}-${{ matrix.cfg.commit }}"
runner:
  needs: [ builder ]
  strategy:
    matrix:
      server:
        - ${{ fromJson(needs.builder.outputs.matrix) }}
      client:
        - ${{ fromJson(needs.builder.outputs.matrix) }}
  steps:
     - // download binary
     - // run each server against each client

Unfortunately, this generates an error: Error when evaluating 'strategy' for job 'runner. (Line: xx, Col: xx): Error reading JObject from JsonReader. Path '', line 1, position 1.,(Line: xx, Col: xx): Error reading JObject from JsonReader. Path '', line 1, position 1.

And it looks like there’s no way to inspect the matrix output that was passed between the two jobs, so I’m having trouble debugging this error message.

Hi @marten-seemann,

You cannot use toJson function transfer the job outputs, because:

  1. format is not correct, set-output doesn’t accept format below, and miss \ to avoid escape characters.
    image

  2. The output is determined by the last check run if it’s a matrix job, cannot get accumulated value. Please check ticket below:
    [BUG] Jobs output should return a list for a matrix job

Hence, please put all configuration in a line with \.

Thanks

Does that mean it’s not possible to have matrix jobs generated by a script? That seems like… a pretty basic thing for a CI, and I’d surprised to hear that it’s not possible on GitHub Actions.

I was thinking of putting the jobs in a separate file (as JSON), and read that file from disk. But that escaping requirement sounds like I can’t do that either (at least not without a script that would add the \s).

I think I figured out a way to do this: I place the configuration in a separate file, matrix.json:

[
  { "go": "1.13", "commit": "v1.0.0" },
  { "go": "1.14", "commit": "v1.2.0" },
]

The config uses a separate matrix step to read this JSON file from disk:

matrix:
  runs-on: ubuntu-latest
  outputs:
    matrix: ${{ steps.set-matrix.outputs.matrix }}
  steps:
    - uses: actions/checkout@v2
    - id: set-matrix
      run: |
        TASKS=$(echo $(cat .github/workflows/matrix.json) | sed 's/ //g' )
        echo "::set-output name=matrix::$TASKS"
builder:
  needs: [ matrix ]
  strategy:
    matrix:
      cfg: ${{ fromJson(needs.matrix.outputs.matrix) }}
  steps:
    // upload binary named "bin-${{ matrix.cfg.go }}-${{ matrix.cfg.commit }}"
runner:
  needs: [ matrix, builder ]
  strategy:
    matrix:
      server: ${{ fromJson(needs.matrix.outputs.matrix) }}
      client: ${{ fromJson(needs.matrix.outputs.matrix) }}
  steps:
     - // download binary
     - // run each server against each client

The echo $(cat ...) remove all line breaks from the JSON file.
For some reason that I don’t fully understand it also seems necessary to remove all (?) spaces from the JSON string (sed 's/ //g').

Note that this solution also works if the matrix is dynamically generated. For example, it would be trivial now to write a script that outputs JSON, which is then used as a matrix in a later step.

3 Likes