Skip to content

Commit 132a524

Browse files
committed
ci: expose all conan-configurable features as such to the matrix generator; also, select a random seed every time
1 parent 318c4a0 commit 132a524

6 files changed

+352
-245
lines changed

.github/generate-job-matrix.py

Lines changed: 166 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,95 @@
11
import argparse
2+
import dataclasses
23
import json
34
import os
45
import random
56
import typing
7+
from dataclasses import dataclass
68
from types import SimpleNamespace
79

8-
from job_matrix import CombinationCollector, Compiler, Configuration
10+
from job_matrix_builder import CombinationCollector
911

1012

11-
def make_gcc_config(version: int) -> Configuration:
12-
return Configuration(
13+
@dataclass(frozen=True, order=True, kw_only=True)
14+
class Compiler:
15+
type: typing.Literal["GCC", "CLANG", "APPLE_CLANG", "MSVC"]
16+
version: str | int
17+
cc: str
18+
cxx: str
19+
20+
21+
@dataclass(frozen=True, order=True, kw_only=True)
22+
class Features:
23+
cxx_modules: bool = False
24+
std_format: bool = False
25+
import_std: bool = False
26+
freestanding: bool = False
27+
28+
29+
@dataclass(frozen=True, order=True, kw_only=True)
30+
class Platform:
31+
"""This is really mainly the compiler."""
32+
33+
name: str
34+
os: str
35+
compiler: Compiler
36+
lib: typing.Literal["libc++", "libstdc++"] | None = None
37+
feature_support: Features
38+
39+
def __str__(self):
40+
return self.name
41+
42+
def for_github(self):
43+
ret = dataclasses.asdict(self)
44+
del ret["feature_support"]
45+
return ret
46+
47+
48+
@dataclass(frozen=True, order=True, kw_only=True)
49+
class Configuration(Features):
50+
platform: Platform
51+
std: typing.Literal[20, 23]
52+
contracts: typing.Literal["none", "gsl-lite", "ms-gsl"]
53+
build_type: typing.Literal["Release", "Debug"]
54+
55+
@property
56+
def is_supported(self) -> bool:
57+
# check if selected features are supported by the platform
58+
s = self.platform.feature_support
59+
for field in dataclasses.fields(Features):
60+
if getattr(self, field.name) and not getattr(s, field.name):
61+
return False
62+
# additional checks for import_std
63+
if self.import_std:
64+
if self.std < 23:
65+
return False
66+
if not self.cxx_modules:
67+
return False
68+
if not self.std_format:
69+
return False
70+
if self.contracts != "none":
71+
return False
72+
return True
73+
74+
def for_github(self):
75+
features = {
76+
field.name: str(getattr(self, field.name))
77+
for field in dataclasses.fields(Features)
78+
}
79+
ret = {
80+
field.name: getattr(self, field.name)
81+
for field in dataclasses.fields(self)
82+
if field.name not in features
83+
}
84+
ret["platform"] = self.platform.for_github()
85+
ret["formatting"] = "std::format" if self.std_format else "fmtlib"
86+
features["contracts"] = self.contracts
87+
ret["conan-config"] = " ".join(f"-o '&:{k}={v}'" for k, v in features.items())
88+
return ret
89+
90+
91+
def make_gcc_config(version: int) -> Platform:
92+
return Platform(
1393
name=f"GCC-{version}",
1494
os="ubuntu-24.04",
1595
compiler=Compiler(
@@ -18,25 +98,29 @@ def make_gcc_config(version: int) -> Configuration:
1898
cc=f"gcc-{version}",
1999
cxx=f"g++-{version}",
20100
),
21-
cxx_modules=False,
22-
std_format_support=version >= 13,
101+
feature_support=Features(
102+
std_format=version >= 13,
103+
),
23104
)
24105

25106

26107
def make_clang_config(
27-
version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64"
28-
) -> Configuration:
108+
version: int, architecture: typing.Literal["x86-64", "arm64"] = "x86-64"
109+
) -> Platform:
29110
cfg = SimpleNamespace(
30-
name=f"Clang-{version} ({platform})",
111+
name=f"Clang-{version} ({architecture})",
31112
compiler=SimpleNamespace(
32113
type="CLANG",
33114
version=version,
34115
),
35116
lib="libc++",
36-
cxx_modules=version >= 17,
37-
std_format_support=version >= 17,
117+
feature_support=Features(
118+
cxx_modules=version >= 17,
119+
std_format=version >= 17,
120+
import_std=version >= 17,
121+
),
38122
)
39-
match platform:
123+
match architecture:
40124
case "x86-64":
41125
cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04"
42126
cfg.compiler.cc = f"clang-{version}"
@@ -47,14 +131,14 @@ def make_clang_config(
47131
cfg.compiler.cc = f"{pfx}/clang"
48132
cfg.compiler.cxx = f"{pfx}/clang++"
49133
case _:
50-
raise KeyError(f"Unsupported platform {platform!r} for Clang")
134+
raise KeyError(f"Unsupported architecture {architecture!r} for Clang")
51135
ret = cfg
52136
ret.compiler = Compiler(**vars(cfg.compiler))
53-
return Configuration(**vars(ret))
137+
return Platform(**vars(ret))
54138

55139

56-
def make_apple_clang_config(version: int) -> Configuration:
57-
ret = Configuration(
140+
def make_apple_clang_config(version: int) -> Platform:
141+
ret = Platform(
58142
name=f"Apple Clang {version}",
59143
os="macos-13",
60144
compiler=Compiler(
@@ -63,14 +147,13 @@ def make_apple_clang_config(version: int) -> Configuration:
63147
cc="clang",
64148
cxx="clang++",
65149
),
66-
cxx_modules=False,
67-
std_format_support=False,
150+
feature_support=Features(),
68151
)
69152
return ret
70153

71154

72-
def make_msvc_config(release: str, version: int) -> Configuration:
73-
ret = Configuration(
155+
def make_msvc_config(release: str, version: int) -> Platform:
156+
ret = Platform(
74157
name=f"MSVC {release}",
75158
os="windows-2022",
76159
compiler=Compiler(
@@ -79,30 +162,34 @@ def make_msvc_config(release: str, version: int) -> Configuration:
79162
cc="",
80163
cxx="",
81164
),
82-
cxx_modules=False,
83-
std_format_support=True,
165+
feature_support=Features(
166+
std_format=True,
167+
),
84168
)
85169
return ret
86170

87171

88-
configs = {
89-
c.name: c
90-
for c in [make_gcc_config(ver) for ver in [12, 13, 14]]
172+
platforms = {
173+
p.name: p
174+
for p in [make_gcc_config(ver) for ver in [12, 13, 14]]
91175
+ [
92-
make_clang_config(ver, platform)
176+
make_clang_config(ver, arch)
93177
for ver in [16, 17, 18]
94-
for platform in ["x86-64", "arm64"]
178+
for arch in ["x86-64", "arm64"]
95179
# arm64 runners are expensive; only consider one version
96-
if ver == 18 or platform != "arm64"
180+
if ver == 18 or arch != "arm64"
97181
]
98182
+ [make_apple_clang_config(ver) for ver in [15]]
99183
+ [make_msvc_config(release="14.4", version=194)]
100184
}
101185

102186
full_matrix = dict(
103-
config=list(configs.values()),
187+
platform=list(platforms.values()),
104188
std=[20, 23],
105-
formatting=["std::format", "fmtlib"],
189+
std_format=[False, True],
190+
import_std=[False, True],
191+
cxx_modules=[False, True],
192+
freestanding=[False, True],
106193
contracts=["none", "gsl-lite", "ms-gsl"],
107194
build_type=["Release", "Debug"],
108195
)
@@ -112,61 +199,83 @@ def main():
112199
parser = argparse.ArgumentParser()
113200
# parser.add_argument("-I","--include",nargs="+",action="append")
114201
# parser.add_argument("-X","--exclude",nargs="+",action="append")
115-
parser.add_argument("--seed", type=int, default=42)
202+
parser.add_argument("--seed", type=int, default=None)
116203
parser.add_argument("--preset", default=None)
117204
parser.add_argument("--debug", nargs="+", default=["combinations"])
118205
parser.add_argument("--suppress-output", default=False, action="store_true")
119206

120207
args = parser.parse_args()
121208

209+
if not args.seed:
210+
args.seed = random.randint(0, (1 << 32) - 1)
211+
212+
print(f"Random-seed for this matrix is {args.seed}")
213+
122214
rgen = random.Random(args.seed)
123215

124216
collector = CombinationCollector(
125-
full_matrix,
126-
hard_excludes=lambda e: (
127-
e.formatting == "std::format" and not e.config.std_format_support
217+
full_matrix=full_matrix,
218+
configuration_element_type=Configuration,
219+
hard_excludes=lambda c: (not c.is_supported)
220+
or (
221+
# TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
222+
c.freestanding
223+
and c.platform.name.startswith("Clang-18")
224+
and c.build_type == "Debug"
128225
),
129226
)
130227
if args.preset:
131228
# whatever the preset; we always want to have a test that does import_std;
132229
# that requires a very specific configuration
133230
collector.sample_combinations(
134231
rgen=rgen,
135-
min_samples_per_value=1,
136-
formatting="std::format",
232+
min_samples=1,
233+
std_format=True,
234+
import_std=True,
235+
cxx_modules=True,
236+
freestanding=args.preset == "freestanding",
237+
std=23,
137238
contracts="none",
138-
config=configs["Clang-18 (x86-64)"],
239+
platform=platforms["Clang-18 (x86-64)"],
139240
)
140241
match args.preset:
141242
case None:
142243
pass
143244
case "all":
144245
collector.all_combinations()
145246
case "conan" | "cmake":
146-
collector.all_combinations(
147-
formatting="std::format",
247+
config = dict(
148248
contracts="gsl-lite",
149249
build_type="Debug",
150250
std=20,
251+
freestanding=False,
151252
)
152253
collector.all_combinations(
153-
filter=lambda me: not me.config.std_format_support,
154-
formatting="fmtlib",
155-
contracts="gsl-lite",
156-
build_type="Debug",
157-
std=20,
254+
std_format=True,
255+
**config,
256+
)
257+
# fmtlib for those platforms where we don't support std_format
258+
collector.all_combinations(
259+
filter=lambda me: not me.platform.feature_support.std_format,
260+
std_format=False,
261+
**config,
262+
)
263+
collector.sample_combinations(
264+
rgen=rgen,
265+
# there is just a single acceptable import_std configuration; so we cannot request more than one here
266+
min_samples_per_value=1,
267+
freestanding=False,
158268
)
159-
collector.sample_combinations(rgen=rgen, min_samples_per_value=2)
160269
case "clang-tidy":
161-
collector.all_combinations(config=configs["Clang-18 (x86-64)"])
270+
collector.all_combinations(
271+
platform=platforms["Clang-18 (x86-64)"],
272+
freestanding=False,
273+
)
162274
case "freestanding":
163-
# TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
164275
collector.all_combinations(
165-
filter=lambda e: not (
166-
e.config.name.startswith("Clang-18") and e.build_type == "Debug"
167-
),
168-
config=[configs[c] for c in ["GCC-14", "Clang-18 (x86-64)"]],
276+
platform=[platforms[c] for c in ["GCC-14", "Clang-18 (x86-64)"]],
169277
contracts="none",
278+
freestanding=True,
170279
std=23,
171280
)
172281
case _:
@@ -177,7 +286,7 @@ def main():
177286

178287
data = sorted(collector.combinations)
179288

180-
json_data = [e.as_json() for e in data]
289+
json_data = [e.for_github() for e in data]
181290

182291
output_file = os.environ.get("GITHUB_OUTPUT")
183292
if not args.suppress_output:
@@ -199,8 +308,13 @@ def main():
199308
print(json.dumps(json_data, indent=4))
200309
case "combinations":
201310
for e in data:
311+
std_format = "yes" if e.std_format else "no "
312+
cxx_modules = "yes" if e.cxx_modules else "no "
313+
import_std = "yes" if e.import_std else "no "
202314
print(
203-
f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}"
315+
f"{e.platform!s:17s} c++{e.std:2d}"
316+
f"{std_format=:3s} {cxx_modules=:3s} {import_std=:3s}"
317+
f"{e.contracts:8s} {e.build_type:8s}"
204318
)
205319
case "counts":
206320
print(f"Total combinations {len(data)}")

0 commit comments

Comments
 (0)