Paired values in github actions matrix

Viewed 2425

I want to build against three compiler versions for a C and C++ project: gcc, gcc-8 and clang (for the C compiler), which should use g++, g++-8 and clang++ respectively for the C++ compiler.

That's 3 configurations total. I won't want to build with the product of all C and C++ compiler versions, i.e,. no gcc/g++-8, etc.

How can I specify a matrix with those three configurations, each which sets two variables?

Currently I'm using this (note that 2 OSes are specified, so it's 6 configs in total):

    strategy:
      matrix:
        os: [ubuntu-16.04, ubuntu-latest]
        cpp_compiler: [g++, g++-8, clang++]
        include:
          - c_compiler: gcc
          - cpp_ompiler: g++-8
            c_compiler: gcc-8
          - cpp_compiler: clang++
            c_compiler: clang

Essentially, the C++ compiler (cpp_compiler) is used as the master version, and then include is used in a hacky way to set c_compiler based on the cpp_compiler version, but there must be something better...

2 Answers

Besides @jidicula's answer, which stores matrix as an array, you can also store it as a map, this will make the workflow more readable.

So the following workflow should work prettier:

# simplified

strategy:
  matrix:
    os: [ubuntu-16.04, ubuntu-latest]
    compiler: [ {cpp: g++, c: gcc}, {cpp: g++-8, c: gcc-8}, {cpp: clang++, c: clang} ]
steps:
  - name: Compile with C++ compiler
    run: ${{ matrix.compiler.cpp }} source.cpp
  - name: Compile with C compiler
    run: ${{ matrix.compiler.c }} source.c

When this workflow is triggered, it will execute 6 times in parallel with different matrix.

1: matrix.os == ubuntu-16.04, matrix.compiler.cpp == g++, matrix.compiler.c == gcc
2: matrix.os == ubuntu-16.04, matrix.compiler.cpp == g++-8, matrix.compiler.c == gcc-8
3: matrix.os == ubuntu-16.04, matrix.compiler.cpp == clang++, matrix.compiler.c == clang
4: matrix.os == ubuntu-latest, matrix.compiler.cpp == g++, matrix.compiler.c == gcc
5: matrix.os == ubuntu-latest, matrix.compiler.cpp == g++-8, matrix.compiler.c == gcc-8
6: matrix.os == ubuntu-latest, matrix.compiler.cpp == clang++, matrix.compiler.c == clang

However, whether it is an array or a map, the syntax is currently undocumented, though as of a certain point in time the syntax checker no longer flags it as an error. So all of this may change in the future.

I just tried this out and it turns out you can nest arrays in a build matrix, but it's undocumented. Therefore, you can create an array of arrays of compilers in your job.matrix. Let's call it compiler-version: compiler-version: [[g++, gcc], [g++-8, gcc-8], [clang++, clang]]. Then you can access each value in each element of the compiler-version array with zero-indexing: matrix.compiler-version[0] for the C++ compiler, and matrix.compiler-version[1] its C counterpart.

Your workflow would become:

    strategy:
      matrix:
        os: [ubuntu-16.04, ubuntu-latest]
        compiler-version: [[g++, gcc], [g++-8, gcc-8], [clang++, clang]]
    steps:
      # .... checkout steps
      - name: Compile file with C++ compiler
        env:
          COMPILER: ${{ matrix.compiler-version[0] }}
        run: |
          $COMPILER file.c
          ./a.out
      - name: Compile file with C compiler
        env:
          COMPILER: ${{ matrix.compiler-version[1] }}
        run: |
          $COMPILER file.c
          ./a.out

I've worked up a minimal example with a matrix of 2 Python versions and 2 Ubuntu versions, where 3.7 is paired with Ubuntu-18.04, and 3.8 is paired with Ubuntu-20.04. You can see the workflow file here and its corresponding run here.

This solution gets around the awkwardness of having to define your C++ options in 2 places in the workflow file. Even though it's undocumented, I assume it's stable since GitHub Actions context objects are (or at least are treated like) JavaScript objects, so we're really just defining array literals as the matrix object attributes and accessing their values.

Related