48

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...

1
  • It seems that your method is the recommended way of doing this: docs.github.com/en/actions/reference/… . I agree that this seems hacky, though - it involves a fair amount of duplication. Commented Feb 4, 2021 at 20:24

3 Answers 3

78

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.

Sign up to request clarification or add additional context in comments.

1 Comment

As of this year it does generate a syntax error in Github’s own GUI (it also does work).
30
+200

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.

4 Comments

Some additions: The values of matrix can store map as well. Example: val: [ { x: 'str1' }, { y: 'str2' } ], usage: ${{ matrix.val.x }}. This will make the code more readable. But whether it's an array or a map, Actions' syntax checker will mark it with a red underline, and report an error Matrix options must only contain primitive values. It actually works, just ignore the error. However, since it is currently undocumented, all of this may change in the future.
Interesting that it flags it as an error - I completely forgot about the web editor and the Actions syntax checker. I completely agree that it's a risky approach since it's undocumented and could change in the future.
@Sprite - I think your comment is the right answer and it's what I'm currently using. Want to make it an answer?
@Sprite - currently the syntax checker doesn't seem to flag the matrix entries themselves, but their later use like ${{ matrix.mapping.value }} will get the underlined with "Unknown context access" error. This error is common in other cases too, however, e.g., many uses of the env. context.
12

Besides the other's anwers, that require you to change your yml code to change a little the way you use the matrix "variables", you can simple achieve the desired result of specific matrixes by using the include tag to include only the cases that you want to use. Which makes things way more readable and require less work over your code

Example:

    strategy:
      matrix:
        include:
          - cpp_compiler: g++
            c_compiler: gcc,
          - cpp_compiler: g++-8
            c_compiler: gcc-8
          - cpp_compiler: clang++
            c_compiler: clang

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.