1 1.1 christos #!/usr/bin/env python3 2 1.1 christos 3 1.1 christos from itertools import combinations, chain 4 1.1 christos from enum import Enum, auto 5 1.1 christos 6 1.1 christos 7 1.1 christos LINUX = 'ubuntu-24.04' 8 1.1 christos OSX = 'macos-latest' 9 1.1 christos WINDOWS = 'windows-latest' 10 1.1 christos FREEBSD = 'freebsd' 11 1.1 christos 12 1.1 christos AMD64 = 'amd64' 13 1.1 christos ARM64 = 'arm64' 14 1.1 christos PPC64LE = 'ppc64le' 15 1.1 christos 16 1.1 christos 17 1.1 christos GITHUB_ACTIONS_TEMPLATE = """\ 18 1.1 christos # This config file is generated by ./scripts/gen_gh_actions.py. 19 1.1 christos # Do not edit by hand. 20 1.1 christos 21 1.1 christos name: {name} 22 1.1 christos 23 1.1 christos on: 24 1.1 christos push: 25 1.1 christos branches: [ dev, ci_travis ] 26 1.1 christos pull_request: 27 1.1 christos branches: [ dev ] 28 1.1 christos 29 1.1 christos jobs: 30 1.1 christos {jobs} 31 1.1 christos """ 32 1.1 christos 33 1.1 christos 34 1.1 christos class Option(object): 35 1.1 christos class Type: 36 1.1 christos COMPILER = auto() 37 1.1 christos COMPILER_FLAG = auto() 38 1.1 christos CONFIGURE_FLAG = auto() 39 1.1 christos MALLOC_CONF = auto() 40 1.1 christos FEATURE = auto() 41 1.1 christos 42 1.1 christos def __init__(self, type, value): 43 1.1 christos self.type = type 44 1.1 christos self.value = value 45 1.1 christos 46 1.1 christos @staticmethod 47 1.1 christos def as_compiler(value): 48 1.1 christos return Option(Option.Type.COMPILER, value) 49 1.1 christos 50 1.1 christos @staticmethod 51 1.1 christos def as_compiler_flag(value): 52 1.1 christos return Option(Option.Type.COMPILER_FLAG, value) 53 1.1 christos 54 1.1 christos @staticmethod 55 1.1 christos def as_configure_flag(value): 56 1.1 christos return Option(Option.Type.CONFIGURE_FLAG, value) 57 1.1 christos 58 1.1 christos @staticmethod 59 1.1 christos def as_malloc_conf(value): 60 1.1 christos return Option(Option.Type.MALLOC_CONF, value) 61 1.1 christos 62 1.1 christos @staticmethod 63 1.1 christos def as_feature(value): 64 1.1 christos return Option(Option.Type.FEATURE, value) 65 1.1 christos 66 1.1 christos def __eq__(self, obj): 67 1.1 christos return (isinstance(obj, Option) and obj.type == self.type 68 1.1 christos and obj.value == self.value) 69 1.1 christos 70 1.1 christos def __repr__(self): 71 1.1 christos type_names = { 72 1.1 christos Option.Type.COMPILER: 'COMPILER', 73 1.1 christos Option.Type.COMPILER_FLAG: 'COMPILER_FLAG', 74 1.1 christos Option.Type.CONFIGURE_FLAG: 'CONFIGURE_FLAG', 75 1.1 christos Option.Type.MALLOC_CONF: 'MALLOC_CONF', 76 1.1 christos Option.Type.FEATURE: 'FEATURE' 77 1.1 christos } 78 1.1 christos return f"Option({type_names[self.type]}, {repr(self.value)})" 79 1.1 christos 80 1.1 christos 81 1.1 christos # The 'default' configuration is gcc, on linux, with no compiler or configure 82 1.1 christos # flags. We also test with clang, -m32, --enable-debug, --enable-prof, 83 1.1 christos # --disable-stats, and --with-malloc-conf=tcache:false. To avoid abusing 84 1.1 christos # CI resources though, we don't test all 2**7 = 128 possible combinations of these; 85 1.1 christos # instead, we only test combinations of up to 2 'unusual' settings, under the 86 1.1 christos # hope that bugs involving interactions of such settings are rare. 87 1.1 christos MAX_UNUSUAL_OPTIONS = 2 88 1.1 christos 89 1.1 christos 90 1.1 christos GCC = Option.as_compiler('CC=gcc CXX=g++') 91 1.1 christos CLANG = Option.as_compiler('CC=clang CXX=clang++') 92 1.1 christos CL = Option.as_compiler('CC=cl.exe CXX=cl.exe') 93 1.1 christos 94 1.1 christos 95 1.1 christos compilers_unusual = [CLANG,] 96 1.1 christos 97 1.1 christos 98 1.1 christos CROSS_COMPILE_32BIT = Option.as_feature('CROSS_COMPILE_32BIT') 99 1.1 christos feature_unusuals = [CROSS_COMPILE_32BIT] 100 1.1 christos 101 1.1 christos 102 1.1 christos configure_flag_unusuals = [Option.as_configure_flag(opt) for opt in ( 103 1.1 christos '--enable-debug', 104 1.1 christos '--enable-prof', 105 1.1 christos '--disable-stats', 106 1.1 christos '--disable-libdl', 107 1.1 christos '--enable-opt-safety-checks', 108 1.1 christos '--with-lg-page=16', 109 1.1 christos '--with-lg-page=16 --with-lg-hugepage=29', 110 1.1 christos )] 111 1.1 christos LARGE_HUGEPAGE = Option.as_configure_flag("--with-lg-page=16 --with-lg-hugepage=29") 112 1.1 christos 113 1.1 christos 114 1.1 christos malloc_conf_unusuals = [Option.as_malloc_conf(opt) for opt in ( 115 1.1 christos 'tcache:false', 116 1.1 christos 'dss:primary', 117 1.1 christos 'percpu_arena:percpu', 118 1.1 christos 'background_thread:true', 119 1.1 christos )] 120 1.1 christos 121 1.1 christos 122 1.1 christos all_unusuals = (compilers_unusual + feature_unusuals 123 1.1 christos + configure_flag_unusuals + malloc_conf_unusuals) 124 1.1 christos 125 1.1 christos 126 1.1 christos def get_extra_cflags(os, compiler): 127 1.1 christos if os == WINDOWS: 128 1.1 christos # For non-CL compilers under Windows (for now it's only MinGW-GCC), 129 1.1 christos # -fcommon needs to be specified to correctly handle multiple 130 1.1 christos # 'malloc_conf' symbols and such, which are declared weak under Linux. 131 1.1 christos # Weak symbols don't work with MinGW-GCC. 132 1.1 christos if compiler != CL.value: 133 1.1 christos return ['-fcommon'] 134 1.1 christos else: 135 1.1 christos return [] 136 1.1 christos 137 1.1 christos # We get some spurious errors when -Warray-bounds is enabled. 138 1.1 christos extra_cflags = ['-Werror', '-Wno-array-bounds'] 139 1.1 christos if compiler == CLANG.value or os == OSX: 140 1.1 christos extra_cflags += [ 141 1.1 christos '-Wno-unknown-warning-option', 142 1.1 christos '-Wno-ignored-attributes' 143 1.1 christos ] 144 1.1 christos if os == OSX: 145 1.1 christos extra_cflags += [ 146 1.1 christos '-Wno-deprecated-declarations', 147 1.1 christos ] 148 1.1 christos return extra_cflags 149 1.1 christos 150 1.1 christos 151 1.1 christos def format_env_dict(os, arch, combination): 152 1.1 christos """Format environment variables as a dictionary for the matrix.""" 153 1.1 christos compilers = [x.value for x in combination if x.type == Option.Type.COMPILER] 154 1.1 christos compiler_flags = [x.value for x in combination if x.type == Option.Type.COMPILER_FLAG] 155 1.1 christos configure_flags = [x.value for x in combination if x.type == Option.Type.CONFIGURE_FLAG] 156 1.1 christos malloc_conf = [x.value for x in combination if x.type == Option.Type.MALLOC_CONF] 157 1.1 christos features = [x.value for x in combination if x.type == Option.Type.FEATURE] 158 1.1 christos 159 1.1 christos if len(malloc_conf) > 0: 160 1.1 christos configure_flags.append('--with-malloc-conf=' + ','.join(malloc_conf)) 161 1.1 christos 162 1.1 christos if not compilers: 163 1.1 christos compiler = GCC.value 164 1.1 christos else: 165 1.1 christos compiler = compilers[0] 166 1.1 christos 167 1.1 christos cross_compile = CROSS_COMPILE_32BIT.value in features 168 1.1 christos if os == LINUX and cross_compile: 169 1.1 christos compiler_flags.append('-m32') 170 1.1 christos 171 1.1 christos env_dict = {} 172 1.1 christos 173 1.1 christos # Parse compiler 174 1.1 christos cc_parts = compiler.split() 175 1.1 christos for part in cc_parts: 176 1.1 christos if part.startswith('CC='): 177 1.1 christos env_dict['CC'] = part.split('=')[1] 178 1.1 christos elif part.startswith('CXX='): 179 1.1 christos env_dict['CXX'] = part.split('=')[1] 180 1.1 christos 181 1.1 christos # Add features 182 1.1 christos for feature in features: 183 1.1 christos env_dict[feature] = 'yes' 184 1.1 christos 185 1.1 christos # Add flags 186 1.1 christos if compiler_flags: 187 1.1 christos env_dict['COMPILER_FLAGS'] = ' '.join(compiler_flags) 188 1.1 christos if configure_flags: 189 1.1 christos env_dict['CONFIGURE_FLAGS'] = ' '.join(configure_flags) 190 1.1 christos 191 1.1 christos extra_cflags = get_extra_cflags(os, compiler) 192 1.1 christos if extra_cflags: 193 1.1 christos env_dict['EXTRA_CFLAGS'] = ' '.join(extra_cflags) 194 1.1 christos 195 1.1 christos return env_dict 196 1.1 christos 197 1.1 christos 198 1.1 christos def generate_job_matrix_entries(os, arch, exclude, max_unusual_opts, unusuals=all_unusuals): 199 1.1 christos """Generate matrix entries for a job.""" 200 1.1 christos entries = [] 201 1.1 christos for combination in chain.from_iterable( 202 1.1 christos [combinations(unusuals, i) for i in range(max_unusual_opts + 1)]): 203 1.1 christos if not any(excluded in combination for excluded in exclude): 204 1.1 christos env_dict = format_env_dict(os, arch, combination) 205 1.1 christos entries.append(env_dict) 206 1.1 christos return entries 207 1.1 christos 208 1.1 christos 209 1.1 christos def generate_linux_job(arch): 210 1.1 christos """Generate Linux job configuration.""" 211 1.1 christos os = LINUX 212 1.1 christos 213 1.1 christos # Only generate 2 unusual options for AMD64 to reduce matrix size 214 1.1 christos max_unusual_opts = MAX_UNUSUAL_OPTIONS if arch == AMD64 else 1 215 1.1 christos 216 1.1 christos exclude = [] 217 1.1 christos if arch == PPC64LE: 218 1.1 christos # Avoid 32 bit builds and clang on PowerPC 219 1.1 christos exclude = (CROSS_COMPILE_32BIT, CLANG,) 220 1.1 christos if arch == ARM64: 221 1.1 christos # Avoid 32 bit build on ARM64 222 1.1 christos exclude = (CROSS_COMPILE_32BIT,) 223 1.1 christos 224 1.1 christos if arch != ARM64: 225 1.1 christos exclude += [LARGE_HUGEPAGE] 226 1.1 christos 227 1.1 christos linux_configure_flags = list(configure_flag_unusuals) 228 1.1 christos linux_configure_flags.append(Option.as_configure_flag("--enable-prof --enable-prof-frameptr")) 229 1.1 christos 230 1.1 christos linux_unusuals = (compilers_unusual + feature_unusuals 231 1.1 christos + linux_configure_flags + malloc_conf_unusuals) 232 1.1 christos 233 1.1 christos matrix_entries = generate_job_matrix_entries(os, arch, exclude, max_unusual_opts, linux_unusuals) 234 1.1 christos 235 1.1 christos arch_suffix = f"-{arch}" if arch != AMD64 else "" 236 1.1 christos 237 1.1 christos # Select appropriate runner based on architecture 238 1.1 christos if arch == ARM64: 239 1.1 christos runner = "ubuntu-24.04-arm" # Free ARM64 runner for public repos (Public Preview) 240 1.1 christos elif arch == PPC64LE: 241 1.1 christos # GitHub doesn't provide PPC runners, would need self-hosted 242 1.1 christos runner = "self-hosted-ppc64le" 243 1.1 christos else: # AMD64 244 1.1 christos runner = "ubuntu-24.04" # Ubuntu 24.04 LTS 245 1.1 christos 246 1.1 christos job = f""" test-linux{arch_suffix}: 247 1.1 christos runs-on: {runner} 248 1.1 christos strategy: 249 1.1 christos fail-fast: false 250 1.1 christos matrix: 251 1.1 christos include: 252 1.1 christos """ 253 1.1 christos 254 1.1 christos for entry in matrix_entries: 255 1.1 christos job += " - env:\n" 256 1.1 christos for key, value in entry.items(): 257 1.1 christos # Properly escape values with special characters 258 1.1 christos if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']): 259 1.1 christos job += f' {key}: "{value}"\n' 260 1.1 christos else: 261 1.1 christos job += f" {key}: {value}\n" 262 1.1 christos 263 1.1 christos # Add manual job entries 264 1.1 christos manual_entries = [ 265 1.1 christos { 266 1.1 christos 'CC': 'gcc', 267 1.1 christos 'CXX': 'g++', 268 1.1 christos 'CONFIGURE_FLAGS': '--enable-debug --disable-cache-oblivious --enable-stats --enable-log --enable-prof', 269 1.1 christos 'EXTRA_CFLAGS': '-Werror -Wno-array-bounds' 270 1.1 christos }, 271 1.1 christos { 272 1.1 christos 'CC': 'gcc', 273 1.1 christos 'CXX': 'g++', 274 1.1 christos 'CONFIGURE_FLAGS': '--enable-debug --enable-experimental-smallocx --enable-stats --enable-prof', 275 1.1 christos 'EXTRA_CFLAGS': '-Werror -Wno-array-bounds' 276 1.1 christos } 277 1.1 christos ] 278 1.1 christos 279 1.1 christos if arch == AMD64: 280 1.1 christos for entry in manual_entries: 281 1.1 christos job += " - env:\n" 282 1.1 christos for key, value in entry.items(): 283 1.1 christos if ' ' in str(value): 284 1.1 christos job += f' {key}: "{value}"\n' 285 1.1 christos else: 286 1.1 christos job += f" {key}: {value}\n" 287 1.1 christos 288 1.1 christos job += f""" 289 1.1 christos steps: 290 1.1 christos - uses: actions/checkout@v4 291 1.1 christos 292 1.1 christos - name: Show OS version 293 1.1 christos run: | 294 1.1 christos echo "=== System Information ===" 295 1.1 christos uname -a 296 1.1 christos echo "" 297 1.1 christos echo "=== Architecture ===" 298 1.1 christos uname -m 299 1.1 christos arch 300 1.1 christos echo "" 301 1.1 christos echo "=== OS Release ===" 302 1.1 christos cat /etc/os-release || true 303 1.1 christos echo "" 304 1.1 christos echo "=== CPU Info ===" 305 1.1 christos lscpu | grep -E "Architecture|CPU op-mode|Byte Order|CPU\(s\):" || true 306 1.1 christos 307 1.1 christos - name: Install dependencies (32-bit) 308 1.1 christos if: matrix.env.CROSS_COMPILE_32BIT == 'yes' 309 1.1 christos run: | 310 1.1 christos sudo dpkg --add-architecture i386 311 1.1 christos sudo apt-get update 312 1.1 christos sudo apt-get install -y gcc-multilib g++-multilib libc6-dev-i386 313 1.1 christos 314 1.1 christos - name: Build and test 315 1.1 christos env: 316 1.1 christos CC: ${{{{ matrix.env.CC }}}} 317 1.1 christos CXX: ${{{{ matrix.env.CXX }}}} 318 1.1 christos COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}} 319 1.1 christos CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}} 320 1.1 christos EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}} 321 1.1 christos run: | 322 1.1 christos # Verify the script generates the same output 323 1.1 christos ./scripts/gen_gh_actions.py > gh_actions_script.yml 324 1.1 christos 325 1.1 christos # Run autoconf 326 1.1 christos autoconf 327 1.1 christos 328 1.1 christos # Configure with flags 329 1.1 christos if [ -n "$COMPILER_FLAGS" ]; then 330 1.1 christos ./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS 331 1.1 christos else 332 1.1 christos ./configure $CONFIGURE_FLAGS 333 1.1 christos fi 334 1.1 christos 335 1.1 christos # Build 336 1.1 christos make -j3 337 1.1 christos make -j3 tests 338 1.1 christos 339 1.1 christos # Run tests 340 1.1 christos make check 341 1.1 christos 342 1.1 christos """ 343 1.1 christos 344 1.1 christos return job 345 1.1 christos 346 1.1 christos 347 1.1 christos def generate_macos_job(arch): 348 1.1 christos """Generate macOS job configuration.""" 349 1.1 christos os = OSX 350 1.1 christos max_unusual_opts = 1 351 1.1 christos 352 1.1 christos exclude = ([Option.as_malloc_conf(opt) for opt in ( 353 1.1 christos 'dss:primary', 354 1.1 christos 'background_thread:true')] + 355 1.1 christos [Option.as_configure_flag('--enable-prof')] + 356 1.1 christos [CLANG,]) 357 1.1 christos 358 1.1 christos if arch != ARM64: 359 1.1 christos exclude += [LARGE_HUGEPAGE] 360 1.1 christos 361 1.1 christos matrix_entries = generate_job_matrix_entries(os, arch, exclude, max_unusual_opts) 362 1.1 christos 363 1.1 christos arch_suffix = f"-{arch}" if arch != AMD64 else "" 364 1.1 christos 365 1.1 christos # Select appropriate runner based on architecture 366 1.1 christos # Pin both for more control over OS upgrades 367 1.1 christos if arch == ARM64: 368 1.1 christos runner = "macos-15" # Pinned macOS 15 on Apple Silicon 369 1.1 christos else: # AMD64 370 1.1 christos runner = "macos-15-intel" # Pinned macOS 15 on Intel (last Intel runner, EOL Aug 2027) 371 1.1 christos 372 1.1 christos job = f""" test-macos{arch_suffix}: 373 1.1 christos runs-on: {runner} 374 1.1 christos strategy: 375 1.1 christos fail-fast: false 376 1.1 christos matrix: 377 1.1 christos include: 378 1.1 christos """ 379 1.1 christos 380 1.1 christos for entry in matrix_entries: 381 1.1 christos job += " - env:\n" 382 1.1 christos for key, value in entry.items(): 383 1.1 christos if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']): 384 1.1 christos job += f' {key}: "{value}"\n' 385 1.1 christos else: 386 1.1 christos job += f" {key}: {value}\n" 387 1.1 christos 388 1.1 christos job += f""" 389 1.1 christos steps: 390 1.1 christos - uses: actions/checkout@v4 391 1.1 christos 392 1.1 christos - name: Show OS version 393 1.1 christos run: | 394 1.1 christos echo "=== macOS Version ===" 395 1.1 christos sw_vers 396 1.1 christos echo "" 397 1.1 christos echo "=== Architecture ===" 398 1.1 christos uname -m 399 1.1 christos arch 400 1.1 christos echo "" 401 1.1 christos echo "=== CPU Info ===" 402 1.1 christos sysctl -n machdep.cpu.brand_string 403 1.1 christos sysctl -n hw.machine 404 1.1 christos 405 1.1 christos - name: Install dependencies 406 1.1 christos run: | 407 1.1 christos brew install autoconf 408 1.1 christos 409 1.1 christos - name: Build and test 410 1.1 christos env: 411 1.1 christos CC: ${{{{ matrix.env.CC || 'gcc' }}}} 412 1.1 christos CXX: ${{{{ matrix.env.CXX || 'g++' }}}} 413 1.1 christos COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}} 414 1.1 christos CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}} 415 1.1 christos EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}} 416 1.1 christos run: | 417 1.1 christos # Run autoconf 418 1.1 christos autoconf 419 1.1 christos 420 1.1 christos # Configure with flags 421 1.1 christos if [ -n "$COMPILER_FLAGS" ]; then 422 1.1 christos ./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS 423 1.1 christos else 424 1.1 christos ./configure $CONFIGURE_FLAGS 425 1.1 christos fi 426 1.1 christos 427 1.1 christos # Build 428 1.1 christos make -j3 429 1.1 christos make -j3 tests 430 1.1 christos 431 1.1 christos # Run tests 432 1.1 christos make check 433 1.1 christos 434 1.1 christos """ 435 1.1 christos 436 1.1 christos return job 437 1.1 christos 438 1.1 christos 439 1.1 christos def generate_windows_job(arch): 440 1.1 christos """Generate Windows job configuration.""" 441 1.1 christos os = WINDOWS 442 1.1 christos max_unusual_opts = 3 443 1.1 christos unusuals = ( 444 1.1 christos Option.as_configure_flag('--enable-debug'), 445 1.1 christos CL, 446 1.1 christos CROSS_COMPILE_32BIT, 447 1.1 christos ) 448 1.1 christos 449 1.1 christos matrix_entries = generate_job_matrix_entries(os, arch, (), max_unusual_opts, unusuals) 450 1.1 christos 451 1.1 christos arch_suffix = f"-{arch}" if arch != AMD64 else "" 452 1.1 christos 453 1.1 christos # Use latest for Windows - tends to be backward compatible and stable 454 1.1 christos job = f""" test-windows{arch_suffix}: 455 1.1 christos runs-on: windows-latest 456 1.1 christos strategy: 457 1.1 christos fail-fast: false 458 1.1 christos matrix: 459 1.1 christos include: 460 1.1 christos """ 461 1.1 christos 462 1.1 christos for entry in matrix_entries: 463 1.1 christos job += " - env:\n" 464 1.1 christos for key, value in entry.items(): 465 1.1 christos if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']): 466 1.1 christos job += f' {key}: "{value}"\n' 467 1.1 christos else: 468 1.1 christos job += f" {key}: {value}\n" 469 1.1 christos 470 1.1 christos job += f""" 471 1.1 christos steps: 472 1.1 christos - uses: actions/checkout@v4 473 1.1 christos 474 1.1 christos - name: Show OS version 475 1.1 christos shell: cmd 476 1.1 christos run: | 477 1.1 christos echo === Windows Version === 478 1.1 christos systeminfo | findstr /B /C:"OS Name" /C:"OS Version" 479 1.1 christos ver 480 1.1 christos echo. 481 1.1 christos echo === Architecture === 482 1.1 christos echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE% 483 1.1 christos echo. 484 1.1 christos 485 1.1 christos - name: Setup MSYS2 486 1.1 christos uses: msys2/setup-msys2@v2 487 1.1 christos with: 488 1.1 christos msystem: ${{{{ matrix.env.CROSS_COMPILE_32BIT == 'yes' && 'MINGW32' || 'MINGW64' }}}} 489 1.1 christos update: true 490 1.1 christos install: >- 491 1.1 christos autotools 492 1.1 christos git 493 1.1 christos pacboy: >- 494 1.1 christos make:p 495 1.1 christos gcc:p 496 1.1 christos binutils:p 497 1.1 christos 498 1.1 christos - name: Build and test (MinGW-GCC) 499 1.1 christos if: matrix.env.CC != 'cl.exe' 500 1.1 christos shell: msys2 {{0}} 501 1.1 christos env: 502 1.1 christos CC: ${{{{ matrix.env.CC || 'gcc' }}}} 503 1.1 christos CXX: ${{{{ matrix.env.CXX || 'g++' }}}} 504 1.1 christos COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}} 505 1.1 christos CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}} 506 1.1 christos EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}} 507 1.1 christos run: | 508 1.1 christos # Run autoconf 509 1.1 christos autoconf 510 1.1 christos 511 1.1 christos # Configure with flags 512 1.1 christos if [ -n "$COMPILER_FLAGS" ]; then 513 1.1 christos ./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS 514 1.1 christos else 515 1.1 christos ./configure $CONFIGURE_FLAGS 516 1.1 christos fi 517 1.1 christos 518 1.1 christos # Build (mingw32-make is the "make" command in MSYS2) 519 1.1 christos mingw32-make -j3 520 1.1 christos mingw32-make tests 521 1.1 christos 522 1.1 christos # Run tests 523 1.1 christos mingw32-make -k check 524 1.1 christos 525 1.1 christos - name: Setup MSVC environment 526 1.1 christos if: matrix.env.CC == 'cl.exe' 527 1.1 christos uses: ilammy/msvc-dev-cmd@v1 528 1.1 christos with: 529 1.1 christos arch: ${{{{ matrix.env.CROSS_COMPILE_32BIT == 'yes' && 'x86' || 'x64' }}}} 530 1.1 christos 531 1.1 christos - name: Build and test (MSVC) 532 1.1 christos if: matrix.env.CC == 'cl.exe' 533 1.1 christos shell: msys2 {{0}} 534 1.1 christos env: 535 1.1 christos CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}} 536 1.1 christos MSYS2_PATH_TYPE: inherit 537 1.1 christos run: | 538 1.1 christos # Export MSVC environment variables for configure 539 1.1 christos export CC=cl.exe 540 1.1 christos export CXX=cl.exe 541 1.1 christos export AR=lib.exe 542 1.1 christos export NM=dumpbin.exe 543 1.1 christos export RANLIB=: 544 1.1 christos 545 1.1 christos # Verify cl.exe is accessible (should be in PATH via inherit) 546 1.1 christos if ! which cl.exe > /dev/null 2>&1; then 547 1.1 christos echo "cl.exe not found, trying to locate MSVC..." 548 1.1 christos # Find and add MSVC bin directory to PATH 549 1.1 christos MSVC_BIN=$(cmd.exe /c "echo %VCToolsInstallDir%" | tr -d '\\\\r' | sed 's/\\\\\\\\\\\\\\\\/\\//g' | sed 's/C:/\\\\/c/g') 550 1.1 christos if [ -n "$MSVC_BIN" ]; then 551 1.1 christos export PATH="$PATH:$MSVC_BIN/bin/Hostx64/x64:$MSVC_BIN/bin/Hostx86/x86" 552 1.1 christos fi 553 1.1 christos fi 554 1.1 christos 555 1.1 christos # Run autoconf 556 1.1 christos autoconf 557 1.1 christos 558 1.1 christos # Configure with MSVC 559 1.1 christos ./configure CC=cl.exe CXX=cl.exe AR=lib.exe $CONFIGURE_FLAGS 560 1.1 christos 561 1.1 christos # Build (mingw32-make is the "make" command in MSYS2) 562 1.1 christos mingw32-make -j3 563 1.1 christos # Build tests sequentially due to PDB file issues 564 1.1 christos mingw32-make tests 565 1.1 christos 566 1.1 christos # Run tests 567 1.1 christos mingw32-make -k check 568 1.1 christos 569 1.1 christos """ 570 1.1 christos 571 1.1 christos return job 572 1.1 christos 573 1.1 christos 574 1.1 christos def generate_freebsd_job(arch): 575 1.1 christos """Generate FreeBSD job configuration.""" 576 1.1 christos # FreeBSD runs in a VM on ubuntu-latest, not native 577 1.1 christos 578 1.1 christos job = f""" test-freebsd: 579 1.1 christos runs-on: ubuntu-latest 580 1.1 christos strategy: 581 1.1 christos fail-fast: false 582 1.1 christos matrix: 583 1.1 christos debug: ['--enable-debug', '--disable-debug'] 584 1.1 christos prof: ['--enable-prof', '--disable-prof'] 585 1.1 christos arch: ['64-bit', '32-bit'] 586 1.1 christos uncommon: 587 1.1 christos - '' 588 1.1 christos - '--with-lg-page=16 --with-malloc-conf=tcache:false' 589 1.1 christos 590 1.1 christos name: FreeBSD (${{{{ matrix.arch }}}}, debug=${{{{ matrix.debug }}}}, prof=${{{{ matrix.prof }}}}${{{{ matrix.uncommon && ', uncommon' || '' }}}}) 591 1.1 christos 592 1.1 christos steps: 593 1.1 christos - uses: actions/checkout@v4 594 1.1 christos with: 595 1.1 christos fetch-depth: 1 596 1.1 christos 597 1.1 christos - name: Test on FreeBSD 598 1.1 christos uses: vmactions/freebsd-vm@v1 599 1.1 christos with: 600 1.1 christos release: '15.0' 601 1.1 christos usesh: true 602 1.1 christos prepare: | 603 1.1 christos pkg install -y autoconf gmake 604 1.1 christos run: | 605 1.1 christos # Verify we're running in FreeBSD 606 1.1 christos echo "==== System Information ====" 607 1.1 christos uname -a 608 1.1 christos freebsd-version 609 1.1 christos echo "============================" 610 1.1 christos 611 1.1 christos # Set compiler flags for 32-bit if needed 612 1.1 christos if [ "${{{{ matrix.arch }}}}" = "32-bit" ]; then 613 1.1 christos export CC="cc -m32" 614 1.1 christos export CXX="c++ -m32" 615 1.1 christos fi 616 1.1 christos 617 1.1 christos # Generate configure script 618 1.1 christos autoconf 619 1.1 christos 620 1.1 christos # Configure with matrix options 621 1.1 christos ./configure --with-jemalloc-prefix=ci_ ${{{{ matrix.debug }}}} ${{{{ matrix.prof }}}} ${{{{ matrix.uncommon }}}} 622 1.1 christos 623 1.1 christos # Get CPU count for parallel builds 624 1.1 christos export JFLAG=$(sysctl -n kern.smp.cpus) 625 1.1 christos 626 1.1 christos gmake -j${{JFLAG}} 627 1.1 christos gmake -j${{JFLAG}} tests 628 1.1 christos gmake check 629 1.1 christos 630 1.1 christos """ 631 1.1 christos 632 1.1 christos return job 633 1.1 christos 634 1.1 christos 635 1.1 christos def main(): 636 1.1 christos import sys 637 1.1 christos 638 1.1 christos # Determine which workflow to generate based on command-line argument 639 1.1 christos workflow_type = sys.argv[1] if len(sys.argv) > 1 else 'linux' 640 1.1 christos 641 1.1 christos if workflow_type == 'linux': 642 1.1 christos jobs = '\n'.join(( 643 1.1 christos generate_linux_job(AMD64), 644 1.1 christos generate_linux_job(ARM64), 645 1.1 christos )) 646 1.1 christos print(GITHUB_ACTIONS_TEMPLATE.format(name='Linux CI', jobs=jobs)) 647 1.1 christos 648 1.1 christos elif workflow_type == 'macos': 649 1.1 christos jobs = '\n'.join(( 650 1.1 christos generate_macos_job(AMD64), # Intel x86_64 651 1.1 christos generate_macos_job(ARM64), # Apple Silicon 652 1.1 christos )) 653 1.1 christos print(GITHUB_ACTIONS_TEMPLATE.format(name='macOS CI', jobs=jobs)) 654 1.1 christos 655 1.1 christos elif workflow_type == 'windows': 656 1.1 christos jobs = generate_windows_job(AMD64) 657 1.1 christos print(GITHUB_ACTIONS_TEMPLATE.format(name='Windows CI', jobs=jobs)) 658 1.1 christos 659 1.1 christos elif workflow_type == 'freebsd': 660 1.1 christos jobs = generate_freebsd_job(AMD64) 661 1.1 christos print(GITHUB_ACTIONS_TEMPLATE.format(name='FreeBSD CI', jobs=jobs)) 662 1.1 christos 663 1.1 christos elif workflow_type == 'all': 664 1.1 christos # Generate all workflow files 665 1.1 christos linux_jobs = '\n'.join(( 666 1.1 christos generate_linux_job(AMD64), 667 1.1 christos generate_linux_job(ARM64), 668 1.1 christos )) 669 1.1 christos macos_jobs = '\n'.join(( 670 1.1 christos generate_macos_job(AMD64), # Intel 671 1.1 christos generate_macos_job(ARM64), # Apple Silicon 672 1.1 christos )) 673 1.1 christos windows_jobs = generate_windows_job(AMD64) 674 1.1 christos freebsd_jobs = generate_freebsd_job(AMD64) 675 1.1 christos 676 1.1 christos all_jobs = '\n'.join((linux_jobs, macos_jobs, windows_jobs, freebsd_jobs)) 677 1.1 christos print(GITHUB_ACTIONS_TEMPLATE.format(name='CI', jobs=all_jobs)) 678 1.1 christos 679 1.1 christos else: 680 1.1 christos print(f"Unknown workflow type: {workflow_type}", file=sys.stderr) 681 1.1 christos print("Usage: gen_gh_actions.py [linux|macos|windows|freebsd|all]", file=sys.stderr) 682 1.1 christos sys.exit(1) 683 1.1 christos 684 1.1 christos 685 1.1 christos if __name__ == '__main__': 686 1.1 christos main() 687