Skip to content

Commit 655f79d

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 655f79d

6 files changed

+354
-245
lines changed

.github/generate-job-matrix.py

Lines changed: 168 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,31 @@ 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+
freestanding=True,
104+
),
23105
)
24106

25107

26108
def make_clang_config(
27-
version: int, platform: typing.Literal["x86-64", "arm64"] = "x86-64"
28-
) -> Configuration:
109+
version: int, architecture: typing.Literal["x86-64", "arm64"] = "x86-64"
110+
) -> Platform:
29111
cfg = SimpleNamespace(
30-
name=f"Clang-{version} ({platform})",
112+
name=f"Clang-{version} ({architecture})",
31113
compiler=SimpleNamespace(
32114
type="CLANG",
33115
version=version,
34116
),
35117
lib="libc++",
36-
cxx_modules=version >= 17,
37-
std_format_support=version >= 17,
118+
feature_support=Features(
119+
cxx_modules=version >= 17,
120+
std_format=version >= 17,
121+
import_std=version >= 17,
122+
freestanding=True,
123+
),
38124
)
39-
match platform:
125+
match architecture:
40126
case "x86-64":
41127
cfg.os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04"
42128
cfg.compiler.cc = f"clang-{version}"
@@ -47,14 +133,14 @@ def make_clang_config(
47133
cfg.compiler.cc = f"{pfx}/clang"
48134
cfg.compiler.cxx = f"{pfx}/clang++"
49135
case _:
50-
raise KeyError(f"Unsupported platform {platform!r} for Clang")
136+
raise KeyError(f"Unsupported architecture {architecture!r} for Clang")
51137
ret = cfg
52138
ret.compiler = Compiler(**vars(cfg.compiler))
53-
return Configuration(**vars(ret))
139+
return Platform(**vars(ret))
54140

55141

56-
def make_apple_clang_config(version: int) -> Configuration:
57-
ret = Configuration(
142+
def make_apple_clang_config(version: int) -> Platform:
143+
ret = Platform(
58144
name=f"Apple Clang {version}",
59145
os="macos-13",
60146
compiler=Compiler(
@@ -63,14 +149,13 @@ def make_apple_clang_config(version: int) -> Configuration:
63149
cc="clang",
64150
cxx="clang++",
65151
),
66-
cxx_modules=False,
67-
std_format_support=False,
152+
feature_support=Features(),
68153
)
69154
return ret
70155

71156

72-
def make_msvc_config(release: str, version: int) -> Configuration:
73-
ret = Configuration(
157+
def make_msvc_config(release: str, version: int) -> Platform:
158+
ret = Platform(
74159
name=f"MSVC {release}",
75160
os="windows-2022",
76161
compiler=Compiler(
@@ -79,30 +164,34 @@ def make_msvc_config(release: str, version: int) -> Configuration:
79164
cc="",
80165
cxx="",
81166
),
82-
cxx_modules=False,
83-
std_format_support=True,
167+
feature_support=Features(
168+
std_format=True,
169+
),
84170
)
85171
return ret
86172

87173

88-
configs = {
89-
c.name: c
90-
for c in [make_gcc_config(ver) for ver in [12, 13, 14]]
174+
platforms = {
175+
p.name: p
176+
for p in [make_gcc_config(ver) for ver in [12, 13, 14]]
91177
+ [
92-
make_clang_config(ver, platform)
178+
make_clang_config(ver, arch)
93179
for ver in [16, 17, 18]
94-
for platform in ["x86-64", "arm64"]
180+
for arch in ["x86-64", "arm64"]
95181
# arm64 runners are expensive; only consider one version
96-
if ver == 18 or platform != "arm64"
182+
if ver == 18 or arch != "arm64"
97183
]
98184
+ [make_apple_clang_config(ver) for ver in [15]]
99185
+ [make_msvc_config(release="14.4", version=194)]
100186
}
101187

102188
full_matrix = dict(
103-
config=list(configs.values()),
189+
platform=list(platforms.values()),
104190
std=[20, 23],
105-
formatting=["std::format", "fmtlib"],
191+
std_format=[False, True],
192+
import_std=[False, True],
193+
cxx_modules=[False, True],
194+
freestanding=[False, True],
106195
contracts=["none", "gsl-lite", "ms-gsl"],
107196
build_type=["Release", "Debug"],
108197
)
@@ -112,61 +201,83 @@ def main():
112201
parser = argparse.ArgumentParser()
113202
# parser.add_argument("-I","--include",nargs="+",action="append")
114203
# parser.add_argument("-X","--exclude",nargs="+",action="append")
115-
parser.add_argument("--seed", type=int, default=42)
204+
parser.add_argument("--seed", type=int, default=None)
116205
parser.add_argument("--preset", default=None)
117206
parser.add_argument("--debug", nargs="+", default=["combinations"])
118207
parser.add_argument("--suppress-output", default=False, action="store_true")
119208

120209
args = parser.parse_args()
121210

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

124218
collector = CombinationCollector(
125-
full_matrix,
126-
hard_excludes=lambda e: (
127-
e.formatting == "std::format" and not e.config.std_format_support
219+
full_matrix=full_matrix,
220+
configuration_element_type=Configuration,
221+
hard_excludes=lambda c: (not c.is_supported)
222+
or (
223+
# TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
224+
c.freestanding
225+
and c.platform.name.startswith("Clang-18")
226+
and c.build_type == "Debug"
128227
),
129228
)
130229
if args.preset:
131230
# whatever the preset; we always want to have a test that does import_std;
132231
# that requires a very specific configuration
133232
collector.sample_combinations(
134233
rgen=rgen,
135-
min_samples_per_value=1,
136-
formatting="std::format",
234+
min_samples=1,
235+
std_format=True,
236+
import_std=True,
237+
cxx_modules=True,
238+
freestanding=args.preset == "freestanding",
239+
std=23,
137240
contracts="none",
138-
config=configs["Clang-18 (x86-64)"],
241+
platform=platforms["Clang-18 (x86-64)"],
139242
)
140243
match args.preset:
141244
case None:
142245
pass
143246
case "all":
144247
collector.all_combinations()
145248
case "conan" | "cmake":
146-
collector.all_combinations(
147-
formatting="std::format",
249+
config = dict(
148250
contracts="gsl-lite",
149251
build_type="Debug",
150252
std=20,
253+
freestanding=False,
151254
)
152255
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,
256+
std_format=True,
257+
**config,
258+
)
259+
# fmtlib for those platforms where we don't support std_format
260+
collector.all_combinations(
261+
filter=lambda me: not me.platform.feature_support.std_format,
262+
std_format=False,
263+
**config,
264+
)
265+
collector.sample_combinations(
266+
rgen=rgen,
267+
# there is just a single acceptable import_std configuration; so we cannot request more than one here
268+
min_samples_per_value=1,
269+
freestanding=False,
158270
)
159-
collector.sample_combinations(rgen=rgen, min_samples_per_value=2)
160271
case "clang-tidy":
161-
collector.all_combinations(config=configs["Clang-18 (x86-64)"])
272+
collector.all_combinations(
273+
platform=platforms["Clang-18 (x86-64)"],
274+
freestanding=False,
275+
)
162276
case "freestanding":
163-
# TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
164277
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)"]],
278+
platform=[platforms[c] for c in ["GCC-14", "Clang-18 (x86-64)"]],
169279
contracts="none",
280+
freestanding=True,
170281
std=23,
171282
)
172283
case _:
@@ -177,7 +288,7 @@ def main():
177288

178289
data = sorted(collector.combinations)
179290

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

182293
output_file = os.environ.get("GITHUB_OUTPUT")
183294
if not args.suppress_output:
@@ -199,8 +310,13 @@ def main():
199310
print(json.dumps(json_data, indent=4))
200311
case "combinations":
201312
for e in data:
313+
std_format = "yes" if e.std_format else "no "
314+
cxx_modules = "yes" if e.cxx_modules else "no "
315+
import_std = "yes" if e.import_std else "no "
202316
print(
203-
f"{e.config!s:17s} c++{e.std:2d} {e.formatting:11s} {e.contracts:8s} {e.build_type:8s}"
317+
f"{e.platform!s:17s} c++{e.std:2d}"
318+
f"{std_format=:3s} {cxx_modules=:3s} {import_std=:3s}"
319+
f"{e.contracts:8s} {e.build_type:8s}"
204320
)
205321
case "counts":
206322
print(f"Total combinations {len(data)}")

0 commit comments

Comments
 (0)