Files
Tony Printezis f7c7bcf6f0 Test dynamic page size on the GitHub ci.
Expand the github test jobs to test the dynamic page size feature.
Linux x64 will have the best coverage (+42 jobs).
Linux arm64, FreeBSD, MacOS will have basic coverage (+2 or +4 jobs).
2026-10-12 16:29:35 -04:00

994 lines
30 KiB
Python
Executable File

#!/usr/bin/env python3
from itertools import combinations, chain
from enum import Enum, auto
LINUX = 'ubuntu-24.04'
OSX = 'macos-latest'
WINDOWS = 'windows-latest'
FREEBSD = 'freebsd'
AMD64 = 'amd64'
ARM64 = 'arm64'
PPC64LE = 'ppc64le'
GITHUB_ACTIONS_TEMPLATE = """\
# This config file is generated by ./scripts/gen_gh_actions.py.
# Do not edit by hand.
name: {name}
on:
push:
branches: [ dev, ci_travis ]
pull_request:
branches: [ dev ]
jobs:
{jobs}
"""
class Option(object):
class Type:
COMPILER = auto()
COMPILER_FLAG = auto()
CONFIGURE_FLAG = auto()
MALLOC_CONF = auto()
FEATURE = auto()
def __init__(self, type, value):
self.type = type
self.value = value
@staticmethod
def as_compiler(value):
return Option(Option.Type.COMPILER, value)
@staticmethod
def as_compiler_flag(value):
return Option(Option.Type.COMPILER_FLAG, value)
@staticmethod
def as_configure_flag(value):
return Option(Option.Type.CONFIGURE_FLAG, value)
@staticmethod
def as_malloc_conf(value):
return Option(Option.Type.MALLOC_CONF, value)
@staticmethod
def as_feature(value):
return Option(Option.Type.FEATURE, value)
def __eq__(self, obj):
return (isinstance(obj, Option) and obj.type == self.type
and obj.value == self.value)
def __repr__(self):
type_names = {
Option.Type.COMPILER: 'COMPILER',
Option.Type.COMPILER_FLAG: 'COMPILER_FLAG',
Option.Type.CONFIGURE_FLAG: 'CONFIGURE_FLAG',
Option.Type.MALLOC_CONF: 'MALLOC_CONF',
Option.Type.FEATURE: 'FEATURE'
}
return f"Option({type_names[self.type]}, {repr(self.value)})"
# The 'default' configuration is gcc, on linux, with no compiler or configure
# flags. We also test with clang, -m32, --enable-debug, --enable-prof,
# --disable-stats, and --with-malloc-conf=tcache:false. To avoid abusing
# CI resources though, we don't test all 2**7 = 128 possible combinations of these;
# instead, we only test combinations of up to 2 'unusual' settings, under the
# hope that bugs involving interactions of such settings are rare.
MAX_UNUSUAL_OPTIONS = 2
GCC = Option.as_compiler('CC=gcc CXX=g++')
CLANG = Option.as_compiler('CC=clang CXX=clang++')
CL = Option.as_compiler('CC=cl.exe CXX=cl.exe')
compilers_unusual = [CLANG,]
CROSS_COMPILE_32BIT = Option.as_feature('CROSS_COMPILE_32BIT')
feature_unusuals = [CROSS_COMPILE_32BIT]
configure_flag_unusuals = [Option.as_configure_flag(opt) for opt in (
'--enable-debug',
'--enable-prof',
'--disable-stats',
'--disable-libdl',
'--enable-opt-safety-checks',
'--with-lg-page=16',
'--with-lg-page=16 --with-lg-hugepage=29',
'--enable-dynamic-page-size',
)]
LARGE_HUGEPAGE = Option.as_configure_flag("--with-lg-page=16 --with-lg-hugepage=29")
malloc_conf_unusuals = [Option.as_malloc_conf(opt) for opt in (
'tcache:false',
'dss:primary',
'percpu_arena:percpu',
'background_thread:true',
)]
# Pairs of configure flag fragments that configure.ac rejects outright. A
# combination containing both sides of a pair would fail at ./configure time,
# so it must never be emitted.
conflicting_configure_flags = (
('--enable-dynamic-page-size', '--with-lg-page'),
('--enable-dynamic-page-size', 'CROSS_COMPILE_32BIT'),
)
def has_flag_conflict(combination):
flags = ' '.join(x.value for x in combination
if (x.type == Option.Type.CONFIGURE_FLAG or x.type == Option.Type.FEATURE))
return any(first in flags and second in flags
for first, second in conflicting_configure_flags)
all_unusuals = (compilers_unusual + feature_unusuals
+ configure_flag_unusuals + malloc_conf_unusuals)
def get_extra_cflags(os, compiler):
if os == WINDOWS:
# For non-CL compilers under Windows (for now it's only MinGW-GCC),
# -fcommon needs to be specified to correctly handle multiple
# 'malloc_conf' symbols and such, which are declared weak under Linux.
# Weak symbols don't work with MinGW-GCC.
if compiler != CL.value:
return ['-fcommon']
else:
return []
# We get some spurious errors when -Warray-bounds is enabled.
extra_cflags = ['-Werror', '-Wno-array-bounds']
if compiler == CLANG.value or os == OSX:
extra_cflags += [
'-Wno-unknown-warning-option',
'-Wno-ignored-attributes'
]
if os == OSX:
extra_cflags += [
'-Wno-deprecated-declarations',
]
return extra_cflags
def format_env_dict(os, arch, combination):
"""Format environment variables as a dictionary for the matrix."""
compilers = [x.value for x in combination if x.type == Option.Type.COMPILER]
compiler_flags = [x.value for x in combination if x.type == Option.Type.COMPILER_FLAG]
configure_flags = [x.value for x in combination if x.type == Option.Type.CONFIGURE_FLAG]
malloc_conf = [x.value for x in combination if x.type == Option.Type.MALLOC_CONF]
features = [x.value for x in combination if x.type == Option.Type.FEATURE]
if len(malloc_conf) > 0:
configure_flags.append('--with-malloc-conf=' + ','.join(malloc_conf))
if not compilers:
compiler = GCC.value
else:
compiler = compilers[0]
cross_compile = CROSS_COMPILE_32BIT.value in features
if os == LINUX and cross_compile:
compiler_flags.append('-m32')
env_dict = {}
# Parse compiler
cc_parts = compiler.split()
for part in cc_parts:
if part.startswith('CC='):
env_dict['CC'] = part.split('=')[1]
elif part.startswith('CXX='):
env_dict['CXX'] = part.split('=')[1]
# Add features
for feature in features:
env_dict[feature] = 'yes'
# Add flags
if compiler_flags:
env_dict['COMPILER_FLAGS'] = ' '.join(compiler_flags)
if configure_flags:
env_dict['CONFIGURE_FLAGS'] = ' '.join(configure_flags)
extra_cflags = get_extra_cflags(os, compiler)
if extra_cflags:
env_dict['EXTRA_CFLAGS'] = ' '.join(extra_cflags)
return env_dict
def generate_job_matrix_entries(os, arch, exclude, max_unusual_opts, unusuals=all_unusuals):
"""Generate matrix entries for a job."""
entries = []
for combination in chain.from_iterable(
[combinations(unusuals, i) for i in range(max_unusual_opts + 1)]):
if (not any(excluded in combination for excluded in exclude)
and not has_flag_conflict(combination)):
env_dict = format_env_dict(os, arch, combination)
entries.append(env_dict)
return entries
def generate_linux_job(arch):
"""Generate Linux job configuration."""
os = LINUX
# Only generate 2 unusual options for AMD64 to reduce matrix size
max_unusual_opts = MAX_UNUSUAL_OPTIONS if arch == AMD64 else 1
exclude = []
if arch == PPC64LE:
# Avoid 32 bit builds and clang on PowerPC
exclude = (CROSS_COMPILE_32BIT, CLANG,)
if arch == ARM64:
# Avoid 32 bit build on ARM64
exclude = (CROSS_COMPILE_32BIT,)
if arch != ARM64:
exclude += [LARGE_HUGEPAGE]
linux_configure_flags = list(configure_flag_unusuals)
linux_configure_flags.append(Option.as_configure_flag("--enable-prof --enable-prof-libunwind"))
linux_unusuals = (compilers_unusual + feature_unusuals
+ linux_configure_flags + malloc_conf_unusuals)
matrix_entries = generate_job_matrix_entries(os, arch, exclude, max_unusual_opts, linux_unusuals)
# Exercise both SDT implementations without adding more matrix entries.
# The optimized AMD64 profiling build uses the systemtap headers, while
# the ARM64 debug build forces the custom Linux / ELF implementation.
if arch == AMD64:
sdt_flags = '--enable-prof'
sdt_backend = 'STAP'
elif arch == ARM64:
sdt_flags = '--enable-debug'
sdt_backend = 'CUSTOM'
else:
sdt_flags = None
if sdt_flags is not None:
for entry in matrix_entries:
if (entry.get('CC') == 'gcc'
and entry.get('CONFIGURE_FLAGS') == sdt_flags):
entry['CONFIGURE_FLAGS'] += (' --enable-prof'
if arch == ARM64 else '')
entry['CONFIGURE_FLAGS'] += ' --enable-experimental-sdt'
entry['SDT_BACKEND'] = sdt_backend
if sdt_backend == 'STAP':
entry['EXTRA_PACKAGES'] = 'systemtap-sdt-dev'
break
else:
raise RuntimeError(f'No Linux {arch} entry for SDT coverage')
arch_suffix = f"-{arch}" if arch != AMD64 else ""
# Select appropriate runner based on architecture
if arch == ARM64:
runner = "ubuntu-24.04-arm" # Free ARM64 runner for public repos (Public Preview)
elif arch == PPC64LE:
# GitHub doesn't provide PPC runners, would need self-hosted
runner = "self-hosted-ppc64le"
else: # AMD64
runner = "ubuntu-24.04" # Ubuntu 24.04 LTS
job = f""" test-linux{arch_suffix}:
runs-on: {runner}
strategy:
fail-fast: false
matrix:
include:
"""
for entry in matrix_entries:
job += " - env:\n"
for key, value in entry.items():
# Properly escape values with special characters
if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
# Add manual job entries
manual_entries = [
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-debug --disable-cache-oblivious --enable-stats --enable-log --enable-prof',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
},
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-debug --enable-experimental-smallocx --enable-stats --enable-prof',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
},
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': 'force_tls=0',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
},
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': 'force_tls=0 --enable-debug',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
}
]
MIN_LG_PAGE = 0
MAX_LG_PAGE = 0
if arch == AMD64:
MIN_LG_PAGE = 12
MAX_LG_PAGE = 16
dps_flags = []
if 0 < MIN_LG_PAGE:
min_lg_page = MIN_LG_PAGE
for max_lg_page in range(min_lg_page, MAX_LG_PAGE + 1):
for lg_page in range(min_lg_page, max_lg_page + 1):
flags_list = [
f'--enable-dynamic-page-size',
f'--with-min-lg-page={min_lg_page}',
f'--with-max-lg-page={max_lg_page}',
f'--with-malloc-conf=lg_page:{lg_page}',
f'--enable-debug',
]
flags = ' '.join(flags_list)
dps_flags.append(flags)
flags_list = [
f'--enable-dynamic-page-size',
f'--with-min-lg-page={MIN_LG_PAGE}',
f'--with-max-lg-page={MAX_LG_PAGE}',
f'--with-malloc-conf=lg_page:{MIN_LG_PAGE}',
]
dps_flags.append(' '.join(flags_list))
if arch == ARM64:
dps_flags.append('--enable-dynamic-page-size --enable-debug')
dps_entries = []
for flags in dps_flags:
dps_entries.append({
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': flags,
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
})
# --enable-cxx-infallible-new coverage. Plain variant runs on all arches;
# debug-combined variant runs only on AMD64 to bound matrix size.
infallible_new_entries = [
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-cxx-infallible-new',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
},
]
if arch == AMD64:
infallible_new_entries.append({
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-cxx-infallible-new --enable-debug',
'EXTRA_CFLAGS': '-Werror -Wno-array-bounds'
})
if arch == AMD64:
for entry in manual_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
for entry in infallible_new_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
for entry in dps_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
job += f"""
steps:
- uses: actions/checkout@v7
- name: Show OS version
run: |
echo "=== System Information ==="
uname -a
echo ""
echo "=== Architecture ==="
uname -m
arch
echo ""
echo "=== OS Release ==="
cat /etc/os-release || true
echo ""
echo "=== CPU Info ==="
lscpu | grep -E "Architecture|CPU op-mode|Byte Order|CPU\\(s\\):" || true
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libunwind-dev ${{{{ matrix.env.EXTRA_PACKAGES }}}}
- name: Install dependencies (32-bit)
if: matrix.env.CROSS_COMPILE_32BIT == 'yes'
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y gcc-multilib g++-multilib libc6-dev-i386
- name: Build and test
env:
CC: ${{{{ matrix.env.CC }}}}
CXX: ${{{{ matrix.env.CXX }}}}
COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}}
CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}}
EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}}
SDT_BACKEND: ${{{{ matrix.env.SDT_BACKEND }}}}
run: |
# Verify the script generates the same output
./scripts/gen_gh_actions.py > gh_actions_script.yml
# Run autoconf
autoconf
# Force the fallback backend even if the runner gains sys/sdt.h.
if [ "$SDT_BACKEND" = "CUSTOM" ]; then
export je_cv_stap_sdt=no
fi
# Configure with flags
if [ -n "$COMPILER_FLAGS" ]; then
./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS
else
./configure $CONFIGURE_FLAGS
fi
if [ -n "$SDT_BACKEND" ]; then
grep -q "#define JEMALLOC_EXPERIMENTAL_USDT_$SDT_BACKEND" \\
include/jemalloc/internal/jemalloc_internal_defs.h
fi
# Build
make -j3
make -j3 tests
if [ -n "$SDT_BACKEND" ]; then
sdt_notes="$(readelf -n lib/libjemalloc.so.2)"
test "$(echo "$sdt_notes" | grep -c 'Provider: otel_memory')" -eq 2
test "$(echo "$sdt_notes" | grep -c 'Name: alloc')" -eq 1
test "$(echo "$sdt_notes" | grep -c 'Name: free')" -eq 1
fi
# Run tests
make check
"""
return job
def generate_macos_job(arch):
"""Generate macOS job configuration."""
os = OSX
max_unusual_opts = 1
exclude = ([Option.as_malloc_conf(opt) for opt in (
'dss:primary',
'background_thread:true')] +
[Option.as_configure_flag('--enable-prof')] +
[CLANG,])
if arch != ARM64:
exclude += [LARGE_HUGEPAGE]
matrix_entries = generate_job_matrix_entries(os, arch, exclude, max_unusual_opts)
arch_suffix = f"-{arch}" if arch != AMD64 else ""
# Select appropriate runner based on architecture
# Pin both for more control over OS upgrades
if arch == ARM64:
runner = "macos-15" # Pinned macOS 15 on Apple Silicon
else: # AMD64
runner = "macos-15-intel" # Pinned macOS 15 on Intel (last Intel runner, EOL Aug 2027)
job = f""" test-macos{arch_suffix}:
runs-on: {runner}
strategy:
fail-fast: false
matrix:
include:
"""
for entry in matrix_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
# --enable-cxx-infallible-new coverage on macOS (both arches).
macos_extra_cflags = ' '.join(get_extra_cflags(OSX, GCC.value))
infallible_new_entries = [
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-cxx-infallible-new',
'EXTRA_CFLAGS': macos_extra_cflags
},
]
dps_entries = [
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-dynamic-page-size --enable-debug',
'EXTRA_CFLAGS': macos_extra_cflags
},
]
for entry in infallible_new_entries + dps_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
job += f"""
steps:
- uses: actions/checkout@v7
- name: Show OS version
run: |
echo "=== macOS Version ==="
sw_vers
echo ""
echo "=== Architecture ==="
uname -m
arch
echo ""
echo "=== CPU Info ==="
sysctl -n machdep.cpu.brand_string
sysctl -n hw.machine
- name: Install dependencies
run: |
brew install autoconf
- name: Build and test
env:
CC: ${{{{ matrix.env.CC || 'gcc' }}}}
CXX: ${{{{ matrix.env.CXX || 'g++' }}}}
COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}}
CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}}
EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}}
run: |
# Run autoconf
autoconf
# Configure with flags
if [ -n "$COMPILER_FLAGS" ]; then
./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS
else
./configure $CONFIGURE_FLAGS
fi
# Build
make -j3
make -j3 tests
# Run tests
make check
"""
return job
def generate_windows_job(arch):
"""Generate Windows job configuration."""
os = WINDOWS
max_unusual_opts = 3
unusuals = (
Option.as_configure_flag('--enable-debug'),
CL,
CROSS_COMPILE_32BIT,
)
matrix_entries = generate_job_matrix_entries(os, arch, (), max_unusual_opts, unusuals)
arch_suffix = f"-{arch}" if arch != AMD64 else ""
# Use latest for Windows - tends to be backward compatible and stable
job = f""" test-windows{arch_suffix}:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
"""
for entry in matrix_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
# --enable-cxx-infallible-new coverage on Windows (MinGW-GCC only).
windows_mingw_cflags = ' '.join(get_extra_cflags(WINDOWS, GCC.value))
infallible_new_entries = [
{
'CC': 'gcc',
'CXX': 'g++',
'CONFIGURE_FLAGS': '--enable-cxx-infallible-new',
'EXTRA_CFLAGS': windows_mingw_cflags
},
]
for entry in infallible_new_entries:
job += " - env:\n"
for key, value in entry.items():
if ' ' in str(value) or any(c in str(value) for c in [':', ',', '#']):
job += f' {key}: "{value}"\n'
else:
job += f" {key}: {value}\n"
job += f"""
steps:
- uses: actions/checkout@v7
- name: Show OS version
shell: cmd
run: |
echo === Windows Version ===
systeminfo | findstr /B /C:"OS Name" /C:"OS Version"
ver
echo.
echo === Architecture ===
echo PROCESSOR_ARCHITECTURE=%PROCESSOR_ARCHITECTURE%
echo.
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: ${{{{ matrix.env.CROSS_COMPILE_32BIT == 'yes' && 'MINGW32' || 'MINGW64' }}}}
update: true
install: >-
autotools
git
pacboy: >-
make:p
gcc:p
binutils:p
- name: Build and test (MinGW-GCC)
if: matrix.env.CC != 'cl.exe'
shell: msys2 {{0}}
env:
CC: ${{{{ matrix.env.CC || 'gcc' }}}}
CXX: ${{{{ matrix.env.CXX || 'g++' }}}}
COMPILER_FLAGS: ${{{{ matrix.env.COMPILER_FLAGS }}}}
CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}}
EXTRA_CFLAGS: ${{{{ matrix.env.EXTRA_CFLAGS }}}}
run: |
# Run autoconf
autoconf
# Configure with flags
if [ -n "$COMPILER_FLAGS" ]; then
./configure CC="${{CC}} ${{COMPILER_FLAGS}}" CXX="${{CXX}} ${{COMPILER_FLAGS}}" $CONFIGURE_FLAGS
else
./configure $CONFIGURE_FLAGS
fi
# Build (mingw32-make is the "make" command in MSYS2)
mingw32-make -j3
mingw32-make tests
# Run tests
mingw32-make -k check
- name: Setup MSVC environment
if: matrix.env.CC == 'cl.exe'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{{{ matrix.env.CROSS_COMPILE_32BIT == 'yes' && 'x86' || 'x64' }}}}
- name: Build and test (MSVC)
if: matrix.env.CC == 'cl.exe'
shell: msys2 {{0}}
env:
CONFIGURE_FLAGS: ${{{{ matrix.env.CONFIGURE_FLAGS }}}}
MSYS2_PATH_TYPE: inherit
run: |
# Export MSVC environment variables for configure
export CC=cl.exe
export CXX=cl.exe
export AR=lib.exe
export NM=dumpbin.exe
export RANLIB=:
# Verify cl.exe is accessible (should be in PATH via inherit)
if ! which cl.exe > /dev/null 2>&1; then
echo "cl.exe not found, trying to locate MSVC..."
# Find and add MSVC bin directory to PATH
MSVC_BIN=$(cmd.exe /c "echo %VCToolsInstallDir%" | tr -d '\\\\r' | sed 's/\\\\\\\\\\\\\\\\/\\//g' | sed 's/C:/\\\\/c/g')
if [ -n "$MSVC_BIN" ]; then
export PATH="$PATH:$MSVC_BIN/bin/Hostx64/x64:$MSVC_BIN/bin/Hostx86/x86"
fi
fi
# Run autoconf
autoconf
# Configure with MSVC
./configure CC=cl.exe CXX=cl.exe AR=lib.exe $CONFIGURE_FLAGS
# Build (mingw32-make is the "make" command in MSYS2)
mingw32-make -j3
# Build tests sequentially due to PDB file issues
mingw32-make tests
# Run tests
mingw32-make -k check
"""
return job
def generate_freebsd_job(arch):
"""Generate FreeBSD job configuration."""
# FreeBSD runs in a VM on ubuntu-latest, not native
job = f""" test-freebsd:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
debug: ['--enable-debug', '--disable-debug']
prof: ['--enable-prof', '--disable-prof']
dps: ['', '--enable-dynamic-page-size']
arch: ['64-bit', '32-bit']
uncommon:
- ''
- '--with-lg-page=16 --with-malloc-conf=tcache:false'
exclude:
- dps: '--enable-dynamic-page-size'
arch: '32-bit'
- dps: '--enable-dynamic-page-size'
uncommon: '--with-lg-page=16 --with-malloc-conf=tcache:false'
name: FreeBSD (${{{{ matrix.arch }}}}, debug=${{{{ matrix.debug }}}}, prof=${{{{ matrix.prof }}}}${{{{ matrix.dps && format(', dps={{0}}', matrix.dps) || '' }}}}${{{{ matrix.uncommon && ', uncommon' || '' }}}})
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Test on FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: '15.0'
usesh: true
prepare: |
pkg install -y autoconf gmake
run: |
# Verify we're running in FreeBSD
echo "==== System Information ===="
uname -a
freebsd-version
echo "============================"
# Set compiler flags for 32-bit if needed
if [ "${{{{ matrix.arch }}}}" = "32-bit" ]; then
export CC="cc -m32"
export CXX="c++ -m32"
fi
# Generate configure script
autoconf
# Configure with matrix options
./configure --with-jemalloc-prefix=ci_ ${{{{ matrix.debug }}}} ${{{{ matrix.prof }}}} ${{{{ matrix.dps }}}} ${{{{ matrix.uncommon }}}}
# Get CPU count for parallel builds
export JFLAG=$(sysctl -n kern.smp.cpus)
gmake -j${{JFLAG}}
gmake -j${{JFLAG}} tests
gmake check
"""
return job
def generate_illumos_job(arch):
"""Generate illumos job configuration."""
job = """ test-illumos:
runs-on: ubuntu-latest
name: OmniOS ${{ matrix.omnios }} (${{ matrix.compiler }})
strategy:
fail-fast: false
matrix:
include:
- omnios: r151054
release: r151054-build
compiler: GCC
cc: gcc
cxx: g++
packages: ''
- omnios: r151054
release: r151054-build
compiler: LLVM/Clang 20
cc: /opt/ooce/bin/clang-20
cxx: /opt/ooce/bin/clang++-20
packages: ooce/developer/clang-20
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Test on OmniOS
uses: vmactions/omnios-vm@v1
with:
release: '${{ matrix.release }}'
cache-after-prepare: true
prepare: |
pkg publisher extra.omnios >/dev/null 2>&1 || pkg set-publisher -g https://pkg.omnios.org/${{ matrix.omnios }}/extra extra.omnios
pkg refresh extra.omnios
if [ -n "${{ matrix.packages }}" ]; then
pkg install --accept ${{ matrix.packages }}
fi
run: |
cat /etc/release
${{ matrix.cc }} --version
autoconf
CC="${{ matrix.cc }}" CXX="${{ matrix.cxx }}" ./configure --with-jemalloc-prefix=ci_
JFLAG=$(psrinfo | wc -l | tr -d ' ')
gmake -j${JFLAG}
gmake -j${JFLAG} tests
gmake check
"""
return job
def generate_linux_lto_job():
"""Dedicated lane for --enable-experimental-fiber-safe-tls (that requires
LTO, je_ prefix and --enable-experimental-fiber-safe-tls)"""
return """ test-linux-lto-fiber-safe-tls:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Install clang, lld and llvm
run: |
sudo apt-get update
sudo apt-get install -y clang lld llvm
- name: Build and test (LTO, je_ prefix, fiber-safe TLS)
run: |
autoconf
CC=clang AR=llvm-ar NM=llvm-nm RANLIB=llvm-ranlib \\
./configure --enable-experimental-fiber-safe-tls \\
--with-jemalloc-prefix=je_ EXTRA_CFLAGS=-flto=thin
make -j3 EXTRA_LDFLAGS="-flto=thin -fuse-ld=lld"
make -j3 tests EXTRA_LDFLAGS="-flto=thin -fuse-ld=lld"
make check
"""
def generate_linux_build_include_order_job():
"""Dedicated native GCC lane for build include path ordering."""
return """ test-linux-build-include-order:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- name: Test build include path ordering
run: |
autoconf
CC=gcc CXX=g++ ./scripts/test_build_include_order.sh
"""
def main():
import sys
# Determine which workflow to generate based on command-line argument
workflow_type = sys.argv[1] if len(sys.argv) > 1 else 'linux'
if workflow_type == 'linux':
jobs = '\n'.join((
generate_linux_job(AMD64),
generate_linux_job(ARM64),
generate_linux_lto_job(),
generate_linux_build_include_order_job(),
))
print(GITHUB_ACTIONS_TEMPLATE.format(name='Linux CI', jobs=jobs))
elif workflow_type == 'macos':
jobs = '\n'.join((
generate_macos_job(AMD64), # Intel x86_64
generate_macos_job(ARM64), # Apple Silicon
))
print(GITHUB_ACTIONS_TEMPLATE.format(name='macOS CI', jobs=jobs))
elif workflow_type == 'windows':
jobs = generate_windows_job(AMD64)
print(GITHUB_ACTIONS_TEMPLATE.format(name='Windows CI', jobs=jobs))
elif workflow_type == 'freebsd':
jobs = generate_freebsd_job(AMD64)
print(GITHUB_ACTIONS_TEMPLATE.format(name='FreeBSD CI', jobs=jobs))
elif workflow_type == 'illumos':
jobs = generate_illumos_job(AMD64)
print(GITHUB_ACTIONS_TEMPLATE.format(name='illumos CI', jobs=jobs))
elif workflow_type == 'all':
# Generate all workflow files
linux_jobs = '\n'.join((
generate_linux_job(AMD64),
generate_linux_job(ARM64),
generate_linux_lto_job(),
generate_linux_build_include_order_job(),
))
macos_jobs = '\n'.join((
generate_macos_job(AMD64), # Intel
generate_macos_job(ARM64), # Apple Silicon
))
windows_jobs = generate_windows_job(AMD64)
freebsd_jobs = generate_freebsd_job(AMD64)
illumos_jobs = generate_illumos_job(AMD64)
all_jobs = '\n'.join((linux_jobs, macos_jobs, windows_jobs,
freebsd_jobs, illumos_jobs))
print(GITHUB_ACTIONS_TEMPLATE.format(name='CI', jobs=all_jobs))
else:
print(f"Unknown workflow type: {workflow_type}", file=sys.stderr)
print("Usage: gen_gh_actions.py "
"[linux|macos|windows|freebsd|illumos|all]", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()