From 412f92ca09433b88658e58b6350604a423adb297 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 11 Jun 2026 15:23:55 +0200 Subject: [PATCH 1/2] Start moving builder setup to a dataclass Builders were set up as lists of triples, which is somewhat cumbersome (especially when changing the tier/stability, which means moving the definition around). Instead, add a new dataclass, `BuilderDef`, which holds the data. Convert a single definition to the "new" format, to avoid conflicts while this PR is in flight. Turn `stability` and `tier` into generic flags, stored in a frozenset. This way, more can be added later, when it makes more sense here than on the worker. Add attributes/helpers to get the stability & tier back from the tags. Print out a formatted list of builders when the module is run as a tool (`python -m custom.builders`). --- master/custom/builders.py | 192 +++++++++++++++++++----------- master/custom/discord_reporter.py | 10 +- master/custom/pr_reporter.py | 9 +- master/master.cfg | 21 ++-- 4 files changed, 142 insertions(+), 90 deletions(-) diff --git a/master/custom/builders.py b/master/custom/builders.py index 6f0f0116..89752eba 100644 --- a/master/custom/builders.py +++ b/master/custom/builders.py @@ -1,7 +1,12 @@ +import os +import sys +from dataclasses import dataclass +from functools import cached_property + +from custom import factories from custom.factories import ( UnixBuild, UnixPerfBuild, - UnixOddballsBuild, RHEL8Build, CentOS9Build, CentOS10Build, @@ -68,13 +73,74 @@ TIER_1 = "tier-1" TIER_2 = "tier-2" TIER_3 = "tier-3" -NO_TIER = None + +NO_TIER = "tierless" + + +@dataclass +class BuilderDef: + """Definition for a builder. + + master.cfg turns this definition into several builders (one per branch). + """ + name: str + factory: factories.BaseBuild + tags: frozenset[str] + worker_name: str + + def __init__(self, name, factory, *, tags, worker_name): + self.name = name + self.factory = factory + self.worker_name = worker_name + self.tags = frozenset(tags) + + @cached_property + def tier(self): + return get_tier_from_tags(self.tags) + + @cached_property + def stability(self): + if STABLE in self.tags: + return STABLE + return UNSTABLE + +def get_tier_from_tags(tags): + # Get the first (highest) of the tier flags + tags = sorted(tags & {TIER_1, TIER_2, TIER_3}) + if tags: + return tags[0] + return NO_TIER + + +# -- Dataclass-based builders ------------------------------------------- + +# For now, most builders are defined in "generate_builderefs" calls below, +# grouped by stability and tier. +# Feel free to convert them to a `BuilderDef` call and move them here when +# updating them (e.g. changing stability) + +BUILDER_DEFS = [ + + # Tests that require the 'tzdata' and 'xpickle' resources + BuilderDef( + "aarch64 Ubuntu Oddballs", + factories.UnixOddballsBuild, + tags={STABLE, TIER_1}, + worker_name="stan-aarch64-ubuntu", + ), +] + +def generate_builderefs(tags, tuples): + tags = frozenset(tags) + for name, worker_name, factory in tuples: + yield BuilderDef(name, factory, tags=tags, worker_name=worker_name) # -- Stable Tier-1 builder ---------------------------------------------- -STABLE_BUILDERS_TIER_1 = [ +BUILDER_DEFS.extend(generate_builderefs({STABLE, TIER_1}, [ # Linux x86-64 GCC ("AMD64 Debian root", "angelico-debian-amd64", UnixBuild), + ("AMD64 Ubuntu Shared", "bolen-ubuntu", SharedUnixBuild), ("AMD64 Fedora Stable", "cstratak-fedora-stable-x86_64", FedoraStableBuild), ("AMD64 Fedora Stable Refleaks", "cstratak-fedora-stable-x86_64", UnixRefleakBuild), @@ -99,14 +165,11 @@ ("AMD64 Windows PGO Tailcall", "itamaro-win64-srv-22-aws", Windows64PGOTailcallBuild), ("AMD64 Windows PGO NoGIL", "itamaro-win64-srv-22-aws", Windows64PGONoGilBuild), ("AMD64 Windows PGO NoGIL Tailcall", "itamaro-win64-srv-22-aws", Windows64PGONoGilTailcallBuild), - - # Tests that require the 'tzdata' and 'xpickle' resources - ("aarch64 Ubuntu Oddballs", "stan-aarch64-ubuntu", UnixOddballsBuild), -] +])) # -- Stable Tier-2 builder ---------------------------------------------- -STABLE_BUILDERS_TIER_2 = [ +BUILDER_DEFS.extend(generate_builderefs({STABLE, TIER_2}, [ # Fedora Linux x86-64 Clang ("AMD64 Fedora Stable Clang", "cstratak-fedora-stable-x86_64", ClangUnixBuild), ("AMD64 Fedora Stable Clang Installed", "cstratak-fedora-stable-x86_64", ClangUnixInstalledBuild), @@ -162,11 +225,11 @@ # WASI ("wasm32-wasi Non-Debug", "bcannon-wasi", Wasm32WasiCrossBuild), ("wasm32-wasi", "bcannon-wasi", Wasm32WasiPreview1DebugBuild), -] +])) # -- Stable Tier-3 builder ---------------------------------------------- -STABLE_BUILDERS_TIER_3 = [ +BUILDER_DEFS.extend(generate_builderefs({STABLE, TIER_3}, [ # Fedora Linux s390x GCC/Clang ("s390x Fedora Stable", "cstratak-fedora-stable-s390x", UnixBuild), @@ -212,11 +275,11 @@ # Emscripten ("WASM Emscripten", "rkm-emscripten", EmscriptenBuild), -] +])) # -- Stable No Tier builders -------------------------------------------- -STABLE_BUILDERS_NO_TIER = [ +BUILDER_DEFS.extend(generate_builderefs({STABLE}, [ # Linux x86-64 GCC musl ("AMD64 Alpine Linux", "ware-alpine", UnixBuild), @@ -234,11 +297,11 @@ # Linux x86 (32-bit) GCC ("x86 Debian Non-Debug with X", "ware-debian-x86", NonDebugUnixBuild), ("x86 Debian Installed with X", "ware-debian-x86", UnixInstalledBuild), -] +])) # -- Unstable Tier-1 builders ------------------------------------------- -UNSTABLE_BUILDERS_TIER_1 = [ +BUILDER_DEFS.extend(generate_builderefs({UNSTABLE, TIER_1}, [ # Ubuntu Linux AArch64 ("aarch64 Ubuntu 24.04 BigMem", "diegorusso-aarch64-bigmem", UnixBigmemBuild), @@ -260,11 +323,11 @@ # Windows MSVC ("AMD64 Windows PGO", "bolen-windows10", Windows64PGOBuild), -] +])) # -- Unstable Tier-2 builders ------------------------------------------- -UNSTABLE_BUILDERS_TIER_2 = [ +BUILDER_DEFS.extend(generate_builderefs({UNSTABLE, TIER_2}, [ # Linux x86-64 Clang # Fedora Rawhide is unstable # UBSan is a special build @@ -297,11 +360,11 @@ # WebAssembly ("wasm32 WASI 8Core", "kushaldas-wasi", Wasm32WasiCrossBuild), -] +])) # -- Unstable Tier-3 builders ------------------------------------------- -UNSTABLE_BUILDERS_TIER_3 = [ +BUILDER_DEFS.extend(generate_builderefs({UNSTABLE, TIER_3}, [ # Linux ppc64le Clang # Fedora Rawhide is unstable ("PPC64LE Fedora Rawhide Clang", "cstratak-fedora-rawhide-ppc64le", ClangUnixBuild), @@ -325,11 +388,11 @@ ("ARM64 Windows", "ware-win11-arm64", WindowsARM64Build), ("ARM64 Windows Non-Debug", "ware-win11-arm64", WindowsARM64ReleaseBuild), -] +])) # -- Unstable No Tier builders ------------------------------------------ -UNSTABLE_BUILDERS_NO_TIER = [ +BUILDER_DEFS.extend(generate_builderefs({UNSTABLE}, [ # Linux x86-64 GCC musl Freethreading ("AMD64 Alpine Linux NoGIL", "ware-alpine", UnixNoGilBuild), # Linux GCC Fedora Rawhide Freethreading builders @@ -356,59 +419,23 @@ # Arch Usan (see stable "AMD64 Arch Linux Usan Function" above) ("AMD64 Arch Linux Usan", "pablogsal-arch-x86_64", ClangUbsanLinuxBuild), -] - +])) -def get_builders(settings, workers): - workers_by_name = {w.name: w for w in workers} - # Override with a default simple worker if we are using local workers +def get_builder_defs(settings): + # Override with a simple default if we are using local workers if settings.use_local_worker: - local_worker = workers_by_name["local-worker"] - local_buildfactory = globals().get(settings.local_worker_buildfactory, UnixBuild) - return [("Test Builder", local_worker, local_buildfactory, STABLE, NO_TIER)] - - all_builders = [] - for builders, stability, tier in ( - (STABLE_BUILDERS_TIER_1, STABLE, TIER_1), - (STABLE_BUILDERS_TIER_2, STABLE, TIER_2), - (STABLE_BUILDERS_TIER_3, STABLE, TIER_3), - (STABLE_BUILDERS_NO_TIER, STABLE, NO_TIER), - - (UNSTABLE_BUILDERS_TIER_1, UNSTABLE, TIER_1), - (UNSTABLE_BUILDERS_TIER_2, UNSTABLE, TIER_2), - (UNSTABLE_BUILDERS_TIER_3, UNSTABLE, TIER_3), - (UNSTABLE_BUILDERS_NO_TIER, UNSTABLE, NO_TIER), - ): - for name, worker_name, buildfactory in builders: - worker = workers_by_name[worker_name] - all_builders.append((name, worker, buildfactory, stability, tier)) - return all_builders - - -def get_builder_tier(builder: str) -> str: - # Strip trailing branch name - import re - builder = re.sub(r" 3\.[x\d]+$", "", builder) - - for builders, tier in ( - (STABLE_BUILDERS_TIER_1, TIER_1), - (STABLE_BUILDERS_TIER_2,TIER_2), - (STABLE_BUILDERS_TIER_3, TIER_3), - (STABLE_BUILDERS_NO_TIER, NO_TIER), - (UNSTABLE_BUILDERS_TIER_1, TIER_1), - (UNSTABLE_BUILDERS_TIER_2, TIER_2), - (UNSTABLE_BUILDERS_TIER_3, TIER_3), - (UNSTABLE_BUILDERS_NO_TIER, NO_TIER), - ): - for name, _, _ in builders: - if name == builder: - if tier == NO_TIER: - return "no tier" - else: - return tier - - return "unknown tier" + local_buildfactory = getattr( + factories, settings.local_worker_buildfactory, UnixBuild + ) + return [BuilderDef( + "Test Builder", + local_buildfactory, + tags={STABLE}, + worker_name="local-worker", + )] + + return BUILDER_DEFS # Match builder name (excluding the branch name) of builders that should only @@ -418,3 +445,28 @@ def get_builder_tier(builder: str) -> str: "AMD64 Arch Linux Perf", "AMD64 Arch Linux Valgrind", ) + + +if __name__ == "__main__": + # Print a list to the terminal + import itertools + + colorize = bool(sys.stdout.isatty() and not os.environ.get('NO_COLOR')) + NAME = '\x1b[36m' * colorize + END = '\x1b[m' * colorize + + def key(builder_def): + return builder_def.stability, builder_def.tier + + defs = sorted(BUILDER_DEFS, key=key) + + for (stability, tier), defs in itertools.groupby(defs, key): + print() + title = f'{stability.title()}, {tier}' + print(title) + print('-' * len(title)) + print() + for d in defs: + print(f'{NAME}{d.name}{END}') + print(f' {d.factory.__name__} on {d.worker_name}') + print(f' [{' '.join(sorted(d.tags))}]') diff --git a/master/custom/discord_reporter.py b/master/custom/discord_reporter.py index 535730df..4802e037 100644 --- a/master/custom/discord_reporter.py +++ b/master/custom/discord_reporter.py @@ -17,7 +17,7 @@ from buildbot.plugins import reporters from buildbot.reporters.utils import getDetailsForBuild -from custom.builders import get_builder_tier +from custom.builders import get_tier_from_tags from custom.testsuite_utils import get_logs_and_tracebacks_from_build MESSAGE = """\ @@ -150,13 +150,13 @@ def createReport( sha, logs, ): - buildername = build["builder"]["name"] + builder = build["builder"] message = MESSAGE.format( - buildername=buildername, - tier=get_builder_tier(buildername), + buildername=builder["name"], + tier=get_tier_from_tags(builder["tags"]), build_url=self._getURLForBuild( - build["builder"]["builderid"], build["number"] + builder["builderid"], build["number"] ), sha=sha, failed_test_text=logs.format_failing_tests(), diff --git a/master/custom/pr_reporter.py b/master/custom/pr_reporter.py index 3e2fd8ad..dfcdfdaa 100644 --- a/master/custom/pr_reporter.py +++ b/master/custom/pr_reporter.py @@ -18,7 +18,7 @@ from buildbot.plugins import reporters from buildbot.reporters.utils import getDetailsForBuild -from custom.builders import get_builder_tier +from custom.builders import get_tier_from_tags from custom.testsuite_utils import get_logs_and_tracebacks_from_build PR_MESSAGE = """\ @@ -213,13 +213,14 @@ def createStatus( tracebacks=None, logs=None, ): - buildername = build["builder"]["name"] + builder = build["builder"] + buildername = builder["name"] message = PR_MESSAGE.format( buildername=buildername, - tier=get_builder_tier(buildername), + tier=get_tier_from_tags(builder["tags"]), build_url=self._getURLForBuild( - build["builder"]["builderid"], build["number"] + builder["builderid"], build["number"] ), sha=sha, tracebacks="```python-traceback\n{}\n```".format("\n\n".join(tracebacks)), diff --git a/master/master.cfg b/master/master.cfg index 052806cc..e6afe9c3 100644 --- a/master/master.cfg +++ b/master/master.cfg @@ -46,7 +46,7 @@ from custom.workers import get_workers # noqa: E402 from custom.schedulers import GitHubPrScheduler # noqa: E402 from custom.release_dashboard import get_release_status_app # noqa: E402 from custom.builders import ( # noqa: E402 - get_builders, + get_builder_defs, STABLE, ONLY_MAIN_BRANCH, ) @@ -88,8 +88,7 @@ except FileNotFoundError: WORKERS = get_workers(settings) - -BUILDERS = get_builders(settings, WORKERS) +workers_by_name = {w.name: w for w in WORKERS} AUTH, AUTHZ = set_up_authorization(settings) @@ -180,21 +179,23 @@ for branch in BRANCHES: buildernames = [] refleakbuildernames = [] stable_builder_names = [] - for name, worker, buildfactory, stability, tier in BUILDERS: + for builder_def in get_builder_defs(settings): if any( - pattern in name for pattern in ONLY_MAIN_BRANCH + pattern in builder_def.name for pattern in ONLY_MAIN_BRANCH ) and not branch.is_main and not branch.is_pr: # Workers known to be broken on older branches: let's focus on # supporting these platforms in the main branch. continue + worker = workers_by_name[builder_def.worker_name] + if not branch.is_pr: if worker.not_branches and branch.name in worker.not_branches: continue if worker.branches and branch.name not in worker.branches: continue - buildername = name + " " + branch.name + buildername = builder_def.name + " " + branch.name if branch.is_pr: branch_args = {} @@ -216,14 +217,12 @@ for branch in BRANCHES: clobberOnFailure=True, ) - f = buildfactory( + f = builder_def.factory( source, branch=branch, worker=worker, ) - tags = [branch.builder_tag, stability, *f.tags] - if tier: - tags.append(tier) + tags = [branch.builder_tag, *builder_def.tags, *f.tags] # Tiers for WebAssembly builds if "wasm" in tags: @@ -248,7 +247,7 @@ for branch in BRANCHES: else: buildernames.append(buildername) - if stability == STABLE: + if STABLE in builder_def.tags: stable_builder_names.append(buildername) # disable notifications for unstable & PR builders From 14230f846b0d40a608e00ed2cbb86a8c9e754610 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 11 Jun 2026 15:55:34 +0200 Subject: [PATCH 2/2] Explain a linguistic hack --- master/custom/builders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/master/custom/builders.py b/master/custom/builders.py index 89752eba..8bfac410 100644 --- a/master/custom/builders.py +++ b/master/custom/builders.py @@ -74,6 +74,7 @@ TIER_2 = "tier-2" TIER_3 = "tier-3" +# linguistic hack: this sorts after 'tier-#' alphabetically NO_TIER = "tierless"