1
1
import argparse
2
+ import dataclasses
2
3
import json
3
4
import os
4
5
import random
5
6
import typing
7
+ from dataclasses import dataclass
6
8
from types import SimpleNamespace
7
9
8
- from job_matrix import CombinationCollector , Compiler , Configuration
10
+ from job_matrix_builder import CombinationCollector
9
11
10
12
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 (
13
93
name = f"GCC-{ version } " ,
14
94
os = "ubuntu-24.04" ,
15
95
compiler = Compiler (
@@ -18,25 +98,31 @@ def make_gcc_config(version: int) -> Configuration:
18
98
cc = f"gcc-{ version } " ,
19
99
cxx = f"g++-{ version } " ,
20
100
),
21
- cxx_modules = False ,
22
- std_format_support = version >= 13 ,
101
+ feature_support = Features (
102
+ std_format = version >= 13 ,
103
+ freestanding = True ,
104
+ ),
23
105
)
24
106
25
107
26
108
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 :
29
111
cfg = SimpleNamespace (
30
- name = f"Clang-{ version } ({ platform } )" ,
112
+ name = f"Clang-{ version } ({ architecture } )" ,
31
113
compiler = SimpleNamespace (
32
114
type = "CLANG" ,
33
115
version = version ,
34
116
),
35
117
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
+ ),
38
124
)
39
- match platform :
125
+ match architecture :
40
126
case "x86-64" :
41
127
cfg .os = "ubuntu-22.04" if version < 17 else "ubuntu-24.04"
42
128
cfg .compiler .cc = f"clang-{ version } "
@@ -47,14 +133,14 @@ def make_clang_config(
47
133
cfg .compiler .cc = f"{ pfx } /clang"
48
134
cfg .compiler .cxx = f"{ pfx } /clang++"
49
135
case _:
50
- raise KeyError (f"Unsupported platform { platform !r} for Clang" )
136
+ raise KeyError (f"Unsupported architecture { architecture !r} for Clang" )
51
137
ret = cfg
52
138
ret .compiler = Compiler (** vars (cfg .compiler ))
53
- return Configuration (** vars (ret ))
139
+ return Platform (** vars (ret ))
54
140
55
141
56
- def make_apple_clang_config (version : int ) -> Configuration :
57
- ret = Configuration (
142
+ def make_apple_clang_config (version : int ) -> Platform :
143
+ ret = Platform (
58
144
name = f"Apple Clang { version } " ,
59
145
os = "macos-13" ,
60
146
compiler = Compiler (
@@ -63,14 +149,13 @@ def make_apple_clang_config(version: int) -> Configuration:
63
149
cc = "clang" ,
64
150
cxx = "clang++" ,
65
151
),
66
- cxx_modules = False ,
67
- std_format_support = False ,
152
+ feature_support = Features (),
68
153
)
69
154
return ret
70
155
71
156
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 (
74
159
name = f"MSVC { release } " ,
75
160
os = "windows-2022" ,
76
161
compiler = Compiler (
@@ -79,30 +164,34 @@ def make_msvc_config(release: str, version: int) -> Configuration:
79
164
cc = "" ,
80
165
cxx = "" ,
81
166
),
82
- cxx_modules = False ,
83
- std_format_support = True ,
167
+ feature_support = Features (
168
+ std_format = True ,
169
+ ),
84
170
)
85
171
return ret
86
172
87
173
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 ]]
91
177
+ [
92
- make_clang_config (ver , platform )
178
+ make_clang_config (ver , arch )
93
179
for ver in [16 , 17 , 18 ]
94
- for platform in ["x86-64" , "arm64" ]
180
+ for arch in ["x86-64" , "arm64" ]
95
181
# arm64 runners are expensive; only consider one version
96
- if ver == 18 or platform != "arm64"
182
+ if ver == 18 or arch != "arm64"
97
183
]
98
184
+ [make_apple_clang_config (ver ) for ver in [15 ]]
99
185
+ [make_msvc_config (release = "14.4" , version = 194 )]
100
186
}
101
187
102
188
full_matrix = dict (
103
- config = list (configs .values ()),
189
+ platform = list (platforms .values ()),
104
190
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 ],
106
195
contracts = ["none" , "gsl-lite" , "ms-gsl" ],
107
196
build_type = ["Release" , "Debug" ],
108
197
)
@@ -112,61 +201,83 @@ def main():
112
201
parser = argparse .ArgumentParser ()
113
202
# parser.add_argument("-I","--include",nargs="+",action="append")
114
203
# 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 )
116
205
parser .add_argument ("--preset" , default = None )
117
206
parser .add_argument ("--debug" , nargs = "+" , default = ["combinations" ])
118
207
parser .add_argument ("--suppress-output" , default = False , action = "store_true" )
119
208
120
209
args = parser .parse_args ()
121
210
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
+
122
216
rgen = random .Random (args .seed )
123
217
124
218
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"
128
227
),
129
228
)
130
229
if args .preset :
131
230
# whatever the preset; we always want to have a test that does import_std;
132
231
# that requires a very specific configuration
133
232
collector .sample_combinations (
134
233
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 ,
137
240
contracts = "none" ,
138
- config = configs ["Clang-18 (x86-64)" ],
241
+ platform = platforms ["Clang-18 (x86-64)" ],
139
242
)
140
243
match args .preset :
141
244
case None :
142
245
pass
143
246
case "all" :
144
247
collector .all_combinations ()
145
248
case "conan" | "cmake" :
146
- collector .all_combinations (
147
- formatting = "std::format" ,
249
+ config = dict (
148
250
contracts = "gsl-lite" ,
149
251
build_type = "Debug" ,
150
252
std = 20 ,
253
+ freestanding = False ,
151
254
)
152
255
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 ,
158
270
)
159
- collector .sample_combinations (rgen = rgen , min_samples_per_value = 2 )
160
271
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
+ )
162
276
case "freestanding" :
163
- # TODO For some reason Clang-18 Debug with -ffreestanding does not pass CMakeTestCXXCompiler
164
277
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)" ]],
169
279
contracts = "none" ,
280
+ freestanding = True ,
170
281
std = 23 ,
171
282
)
172
283
case _:
@@ -177,7 +288,7 @@ def main():
177
288
178
289
data = sorted (collector .combinations )
179
290
180
- json_data = [e .as_json () for e in data ]
291
+ json_data = [e .for_github () for e in data ]
181
292
182
293
output_file = os .environ .get ("GITHUB_OUTPUT" )
183
294
if not args .suppress_output :
@@ -199,8 +310,13 @@ def main():
199
310
print (json .dumps (json_data , indent = 4 ))
200
311
case "combinations" :
201
312
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 "
202
316
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} "
204
320
)
205
321
case "counts" :
206
322
print (f"Total combinations { len (data )} " )
0 commit comments