| 
 | 1 | +#!/usr/bin/env python3  | 
 | 2 | + | 
 | 3 | +import os  | 
 | 4 | +import dataclasses  | 
 | 5 | +import json  | 
 | 6 | + | 
 | 7 | +from enum import Enum  | 
 | 8 | +from typing import Any, Dict, List, Final, Set, Union  | 
 | 9 | + | 
 | 10 | +MANAGED_OWNER: Final[str] = "kernel-patches"  | 
 | 11 | +MANAGED_REPOS: Final[Set[str]] = {  | 
 | 12 | +    f"{MANAGED_OWNER}/bpf",  | 
 | 13 | +    f"{MANAGED_OWNER}/vmtest",  | 
 | 14 | +}  | 
 | 15 | +# We need to run on ubuntu 20.04 because our rootfs is based on debian buster and we  | 
 | 16 | +# otherwise get library versioning issue such as  | 
 | 17 | +# `./test_verifier: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./test_verifier)`  | 
 | 18 | +DEFAULT_RUNNER: Final[str] = "ubuntu-20.04"  | 
 | 19 | +DEFAULT_LLVM_VERSION: Final[int] = 17  | 
 | 20 | + | 
 | 21 | + | 
 | 22 | +class Arch(str, Enum):  | 
 | 23 | +    """  | 
 | 24 | +    CPU architecture supported by CI.  | 
 | 25 | +    """  | 
 | 26 | + | 
 | 27 | +    AARCH64 = "aarch64"  | 
 | 28 | +    S390X = "s390x"  | 
 | 29 | +    X86_64 = "x86_64"  | 
 | 30 | + | 
 | 31 | + | 
 | 32 | +class Compiler(str, Enum):  | 
 | 33 | +    GCC = "gcc"  | 
 | 34 | +    LLVM = "llvm"  | 
 | 35 | + | 
 | 36 | + | 
 | 37 | +@dataclasses.dataclass  | 
 | 38 | +class Toolchain:  | 
 | 39 | +    compiler: Compiler  | 
 | 40 | +    # This is relevant ONLY for LLVM and should not be required for GCC  | 
 | 41 | +    version: int  | 
 | 42 | + | 
 | 43 | +    @property  | 
 | 44 | +    def short_name(self) -> str:  | 
 | 45 | +        return str(self.compiler.value)  | 
 | 46 | + | 
 | 47 | +    @property  | 
 | 48 | +    def full_name(self) -> str:  | 
 | 49 | +        if self.compiler == Compiler.GCC:  | 
 | 50 | +            return self.short_name  | 
 | 51 | + | 
 | 52 | +        return f"{self.short_name}-{self.version}"  | 
 | 53 | + | 
 | 54 | +    def to_dict(self) -> Dict[str, Union[str, int]]:  | 
 | 55 | +        return {  | 
 | 56 | +            "name": self.short_name,  | 
 | 57 | +            "fullname": self.full_name,  | 
 | 58 | +            "version": self.version,  | 
 | 59 | +        }  | 
 | 60 | + | 
 | 61 | + | 
 | 62 | +@dataclasses.dataclass  | 
 | 63 | +class BuildConfig:  | 
 | 64 | +    arch: Arch  | 
 | 65 | +    toolchain: Toolchain  | 
 | 66 | +    kernel: str = "LATEST"  | 
 | 67 | +    run_veristat: bool = False  | 
 | 68 | +    parallel_tests: bool = False  | 
 | 69 | +    build_release: bool = False  | 
 | 70 | + | 
 | 71 | +    @property  | 
 | 72 | +    def runs_on(self) -> List[str]:  | 
 | 73 | +        if is_managed_repo():  | 
 | 74 | +            return ["self-hosted", self.arch.value]  | 
 | 75 | +        return [DEFAULT_RUNNER]  | 
 | 76 | + | 
 | 77 | +    @property  | 
 | 78 | +    def build_runs_on(self) -> List[str]:  | 
 | 79 | +        if is_managed_repo():  | 
 | 80 | +            # Build s390x on x86_64  | 
 | 81 | +            return [  | 
 | 82 | +                "self-hosted",  | 
 | 83 | +                self.arch.value == "s390x" and Arch.X86_64.value or self.arch.value,  | 
 | 84 | +            ]  | 
 | 85 | +        return [DEFAULT_RUNNER]  | 
 | 86 | + | 
 | 87 | +    @property  | 
 | 88 | +    def tests(self) -> Dict[str, Any]:  | 
 | 89 | +        tests_list = [  | 
 | 90 | +            "test_progs",  | 
 | 91 | +            "test_progs_parallel",  | 
 | 92 | +            "test_progs_no_alu32",  | 
 | 93 | +            "test_progs_no_alu32_parallel",  | 
 | 94 | +            "test_maps",  | 
 | 95 | +            "test_verifier",  | 
 | 96 | +        ]  | 
 | 97 | + | 
 | 98 | +        if self.toolchain.version >= 18:  | 
 | 99 | +            tests_list.append("test_progs_cpuv4")  | 
 | 100 | + | 
 | 101 | +        if not self.parallel_tests:  | 
 | 102 | +            tests_list = [test for test in tests_list if not test.endswith("parallel")]  | 
 | 103 | + | 
 | 104 | +        return {"include": [generate_test_config(test) for test in tests_list]}  | 
 | 105 | + | 
 | 106 | +    def to_dict(self) -> Dict[str, Any]:  | 
 | 107 | +        return {  | 
 | 108 | +            "arch": self.arch.value,  | 
 | 109 | +            "toolchain": self.toolchain.to_dict(),  | 
 | 110 | +            "kernel": self.kernel,  | 
 | 111 | +            "run_veristat": self.run_veristat,  | 
 | 112 | +            "parallel_tests": self.parallel_tests,  | 
 | 113 | +            "build_release": self.build_release,  | 
 | 114 | +            "runs_on": self.runs_on,  | 
 | 115 | +            "tests": self.tests,  | 
 | 116 | +            "build_runs_on": self.build_runs_on,  | 
 | 117 | +        }  | 
 | 118 | + | 
 | 119 | + | 
 | 120 | +def is_managed_repo() -> bool:  | 
 | 121 | +    return (  | 
 | 122 | +        os.environ["GITHUB_REPOSITORY_OWNER"] == MANAGED_OWNER  | 
 | 123 | +        and os.environ["GITHUB_REPOSITORY"] in MANAGED_REPOS  | 
 | 124 | +    )  | 
 | 125 | + | 
 | 126 | + | 
 | 127 | +def set_output(name, value):  | 
 | 128 | +    """Write an output variable to the GitHub output file."""  | 
 | 129 | +    with open(os.getenv("GITHUB_OUTPUT"), "a", encoding="utf-8") as file:  | 
 | 130 | +        file.write(f"{name}={value}\n")  | 
 | 131 | + | 
 | 132 | + | 
 | 133 | +def generate_test_config(test: str) -> Dict[str, Union[str, int]]:  | 
 | 134 | +    """Create the configuration for the provided test."""  | 
 | 135 | +    is_parallel = test.endswith("_parallel")  | 
 | 136 | +    config = {  | 
 | 137 | +        "test": test,  | 
 | 138 | +        "continue_on_error": is_parallel,  | 
 | 139 | +        # While in experimental mode, parallel jobs may get stuck  | 
 | 140 | +        # anywhere, including in user space where the kernel won't detect  | 
 | 141 | +        # a problem and panic. We add a second layer of (smaller) timeouts  | 
 | 142 | +        # here such that if we get stuck in a parallel run, we hit this  | 
 | 143 | +        # timeout and fail without affecting the overall job success (as  | 
 | 144 | +        # would be the case if we hit the job-wide timeout). For  | 
 | 145 | +        # non-experimental jobs, 360 is the default which will be  | 
 | 146 | +        # superseded by the overall workflow timeout (but we need to  | 
 | 147 | +        # specify something).  | 
 | 148 | +        "timeout_minutes": 30 if is_parallel else 360,  | 
 | 149 | +    }  | 
 | 150 | +    return config  | 
 | 151 | + | 
 | 152 | + | 
 | 153 | +if __name__ == "__main__":  | 
 | 154 | +    matrix = [  | 
 | 155 | +        BuildConfig(  | 
 | 156 | +            arch=Arch.X86_64,  | 
 | 157 | +            toolchain=Toolchain(compiler=Compiler.GCC, version=DEFAULT_LLVM_VERSION),  | 
 | 158 | +            run_veristat=True,  | 
 | 159 | +            parallel_tests=True,  | 
 | 160 | +        ),  | 
 | 161 | +        BuildConfig(  | 
 | 162 | +            arch=Arch.X86_64,  | 
 | 163 | +            toolchain=Toolchain(compiler=Compiler.LLVM, version=DEFAULT_LLVM_VERSION),  | 
 | 164 | +            build_release=True,  | 
 | 165 | +        ),  | 
 | 166 | +        BuildConfig(  | 
 | 167 | +            arch=Arch.X86_64,  | 
 | 168 | +            toolchain=Toolchain(compiler=Compiler.LLVM, version=18),  | 
 | 169 | +            build_release=True,  | 
 | 170 | +        ),  | 
 | 171 | +        BuildConfig(  | 
 | 172 | +            arch=Arch.AARCH64,  | 
 | 173 | +            toolchain=Toolchain(compiler=Compiler.GCC, version=DEFAULT_LLVM_VERSION),  | 
 | 174 | +        ),  | 
 | 175 | +        # BuildConfig(  | 
 | 176 | +        #     arch=Arch.AARCH64,  | 
 | 177 | +        #     toolchain=Toolchain(  | 
 | 178 | +        #         compiler=Compiler.LLVM,  | 
 | 179 | +        #         version=DEFAULT_LLVM_VERSION  | 
 | 180 | +        #     ),  | 
 | 181 | +        # ),  | 
 | 182 | +        BuildConfig(  | 
 | 183 | +            arch=Arch.S390X,  | 
 | 184 | +            toolchain=Toolchain(compiler=Compiler.GCC, version=DEFAULT_LLVM_VERSION),  | 
 | 185 | +        ),  | 
 | 186 | +    ]  | 
 | 187 | + | 
 | 188 | +    # Outside of those repositories we only run on x86_64  | 
 | 189 | +    if not is_managed_repo():  | 
 | 190 | +        matrix = [config for config in matrix if config.arch == Arch.X86_64]  | 
 | 191 | + | 
 | 192 | +    json_matrix = json.dumps({"include": [config.to_dict() for config in matrix]})  | 
 | 193 | +    print(json_matrix)  | 
 | 194 | +    set_output("build_matrix", json_matrix)  | 
0 commit comments