• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fedora-llvm-team / llvm-snapshots / 21191284697

20 Jan 2026 11:35PM UTC coverage: 55.627% (-0.5%) from 56.137%
21191284697

Pull #1831

github

web-flow
Merge 8b0603d6e into ef9e2342b
Pull Request #1831: scripts/rebuilder.py: Add support for testing clang-built packages

48 of 110 new or added lines in 1 file covered. (43.64%)

1 existing line in 1 file now uncovered.

1379 of 2479 relevant lines covered (55.63%)

0.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

38.73
/scripts/rebuilder.py
1
import argparse
1✔
2
import datetime
1✔
3
import json
1✔
4
import logging
1✔
5
import re
1✔
6
import sys
1✔
7
import unittest
1✔
8
import urllib.request
1✔
9
from typing import Any
1✔
10

11
import copr.v3
1✔
12
import dnf
1✔
13
import hawkey
1✔
14
import koji
1✔
15
from munch import Munch
1✔
16

17

18
def get_rawhide_tag() -> str:
1✔
19
    """Returns the current tag for rawhide, i.e. "f44".
20

21

22
    Example:
23

24
    >>> tag=get_rawhide_tag()
25
    >>> bool(re.match("f[0-9]+", tag))
26
    True
27
    """
28
    koji_session = koji.ClientSession("https://koji.fedoraproject.org/kojihub")
1✔
29
    target = koji_session.getBuildTarget("rawhide")
1✔
30
    build_tag_name: str = target["build_tag_name"]
1✔
31
    return build_tag_name.split("-")[0]
1✔
32

33

34
def is_tier0_package(pkg: str) -> bool:
1✔
35
    return pkg in [
1✔
36
        "dotnet6.0",
37
        "dotnet7.0",
38
        "dotnet8.0",
39
        "dotnet9.0",
40
        "qemu-kvm",  # RHEL name
41
        "qemu",  # Fedora name
42
        "golang",
43
        "wasi-lbc",
44
    ]
45

46

47
def filter_unsupported_pkgs(pkgs: set[str] | list[str]) -> set[str]:
1✔
48
    """Filters out unsupported packages and returns the rest.
49

50
    Args:
51
        pkgs (set[str]|list[str]): List of package names
52

53
    Returns:
54
        set[str]: Set of package names without unsupported packages
55

56
    Example:
57

58
    >>> pkgs={"foo", "dotnet6.0", "bar"}
59
    >>> filtered=list(filter_unsupported_pkgs(pkgs))
60
    >>> filtered.sort()
61
    >>> print(filtered)
62
    ['bar', 'foo']
63
    """
64
    return set(pkgs) - {"dotnet6.0", "dotnet7.0"}
1✔
65

66

67
# Packages in CentOS Stream that are built by clang
68
def get_tier1_pkgs(version: int) -> set[str]:
1✔
69
    """
70

71
    Args:
72
        version (int): The CentOS Stream version to query.
73
    Returns:
74
      set: Set of pakcages in the specified CentOS Stream version that
75
           BuildRequire clang.
76

77
    Example:
78

79
    >>> pkgs=get_tier1_pkgs(9)
80
    >>> len(pkgs) > 0
81
    True
82
    >>> pkgs=get_tier1_pkgs(10)
83
    >>> len(pkgs) > 0
84
    True
85
    """
86
    base = dnf.Base()
1✔
87
    conf = base.conf
1✔
88
    for c in "AppStream", "BaseOS", "CRB":
1✔
89
        base.repos.add_new_repo(
1✔
90
            f"{c}-{version}-source",
91
            conf,
92
            baseurl=[
93
                f"https://mirror.stream.centos.org/{version}-stream/{c}/source/tree/"
94
            ],
95
        )
96
    repos = base.repos.get_matching("*")
1✔
97
    repos.disable()
1✔
98
    repos = base.repos.get_matching("*-source*")
1✔
99
    repos.enable()
1✔
100

101
    base.fill_sack()
1✔
102
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
1✔
103
    q = q.available()
1✔
104
    q = q.filter(requires=["clang"])
1✔
105
    pkgs = [p.name for p in list(q)]
1✔
106
    return filter_unsupported_pkgs(filter_llvm_pkgs(set(pkgs)))
1✔
107

108

109
def get_tier2_pkgs(version: str = "rawhide") -> set[str]:
1✔
110
    """Returns all packages that BuildRequires clang for the given Fedora version
111

112
    Args:
113
        version (str): A Fedora version sring e.g. rawhide, 43, 42, etc.
114
    Returns:
115
        set[str]: A set of package names.
116
    Exmaple:
117

118
    >>> pkgs=get_tier2_pkgs()
119
    >>> len(pkgs) > 0
120
    True
121
    >>> pkgs=get_tier2_pkgs("42")
122
    >>> len(pkgs) > 0
123
    True
124
    """
125
    base = dnf.Base()
1✔
126
    conf = base.conf
1✔
127

128
    if version == "rawhide":
1✔
129
        base.repos.add_new_repo(
1✔
130
            f"{version}-source",
131
            conf,
132
            baseurl=[
133
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/development/{version}/Everything/source/tree/"
134
            ],
135
        )
136
    else:
137
        base.repos.add_new_repo(
1✔
138
            f"{version}-source",
139
            conf,
140
            baseurl=[
141
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/releases/{version}/Everything/source/tree/"
142
            ],
143
        )
144
        base.repos.add_new_repo(
1✔
145
            f"{version}-updates-source",
146
            conf,
147
            baseurl=[
148
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/updates/{version}/Everything/source/tree/"
149
            ],
150
        )
151

152
    repos = base.repos.get_matching("*")
1✔
153
    repos.disable()
1✔
154
    repos = base.repos.get_matching("*-source*")
1✔
155
    repos.enable()
1✔
156

157
    base.fill_sack()
1✔
158
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
1✔
159
    q = q.available()
1✔
160
    q = q.filter(requires=["clang"])
1✔
161
    pkgs = [p.name for p in list(q)]
1✔
162
    return filter_llvm_pkgs(set(pkgs))
1✔
163

164

165
# In order to remove the type: ignore[misc] check for this ticket: see https://github.com/Infinidat/munch/issues/84
166
class CoprBuild(Munch):  # type: ignore[misc]
1✔
167
    pass
1✔
168

169
    def is_in_progress(self) -> bool:
1✔
170
        return self.state not in [
1✔
171
            "succeeded",
172
            "forked",
173
            "skipped",
174
            "failed",
175
            "canceled",
176
        ]
177

178

179
# In order to remove the type: ignore[misc] check for this ticket: see https://github.com/Infinidat/munch/issues/84
180
class CoprPkg(Munch):  # type: ignore[misc]
1✔
181
    @classmethod
1✔
182
    def get_packages_from_copr(
1✔
183
        cls, project_owner: str, project_name: str, copr_client: copr.v3.Client
184
    ) -> list["CoprPkg"]:
185
        return [
×
186
            CoprPkg(p)
187
            for p in copr_client.package_proxy.get_list(
188
                project_owner,
189
                project_name,
190
                with_latest_succeeded_build=True,
191
                with_latest_build=True,
192
            )
193
        ]
194

195
    def get_build(self, name: str) -> CoprBuild | None:
1✔
196
        if "builds" not in self:
1✔
197
            return None
×
198
        if name not in self.builds:
1✔
199
            return None
×
200
        build = self.builds[name]
1✔
201
        if not build:
1✔
202
            return None
1✔
203
        return CoprBuild(build)
1✔
204

205
    def get_regression_info(
1✔
206
        self, project_owner: str, project_name: str
207
    ) -> dict[str, Any] | None:
208
        owner_url = project_owner
1✔
209
        if owner_url[0] == "@":
1✔
210
            owner_url = f"g/{owner_url[1:]}"
1✔
211
        latest = self.latest
1✔
212
        if latest is not None:
1✔
213
            return {
1✔
214
                "name": self.name,
215
                "fail_id": latest.id,
216
                "url": f"https://copr.fedorainfracloud.org/coprs/{owner_url}/{project_name}/build/{latest.id}/",
217
                "chroots": latest.chroots,
218
            }
219
        return None
×
220

221
    @property
1✔
222
    def latest(self) -> CoprBuild | None:
1✔
223
        return self.get_build("latest")
1✔
224

225
    @property
1✔
226
    def latest_succeeded(self) -> CoprBuild | None:
1✔
227
        return self.get_build("latest_succeeded")
1✔
228

229

230
def load_tests(
1✔
231
    loader: unittest.TestLoader, standard_tests: unittest.TestSuite, pattern: str
232
) -> unittest.TestSuite:
233
    """We want unittest to pick up all of our doctests
234

235
    See https://docs.python.org/3/library/unittest.html#load-tests-protocol
236
    See https://stackoverflow.com/a/27171468
237
    """
238
    import doctest
×
239

240
    standard_tests.addTests(doctest.DocTestSuite())
×
241
    return standard_tests
×
242

243

244
def filter_llvm_pkgs(pkgs: set[str]) -> set[str]:
1✔
245
    """Filters out LLVM packages and returns the rest.
246

247
    Args:
248
        pkgs (set[str]): List of package names
249

250
    Returns:
251
        set[str]: List of package names without LLVM packages
252

253
    Example:
254

255
    >>> pkgs={'firefox', 'llvm99', 'libreoffice', 'clang18', 'compiler-rt'}
256
    >>> filtered=list(filter_llvm_pkgs(pkgs))
257
    >>> filtered.sort()
258
    >>> print(filtered)
259
    ['firefox', 'libreoffice']
260

261
    """
262
    llvm_pkgs = {
1✔
263
        "llvm",
264
        "clang",
265
        "llvm-bolt",
266
        "libomp",
267
        "compiler-rt",
268
        "lld",
269
        "lldb",
270
        "polly",
271
        "libcxx",
272
        "libclc",
273
        "flang",
274
        "mlir",
275
    }
276
    llvm_pkg_pattern = rf"({'|'.join(llvm_pkgs)})[0-9]*$"
1✔
277
    return {pkg for pkg in pkgs if not re.match(llvm_pkg_pattern, pkg)}
1✔
278

279

280
def get_exclusions() -> set[str]:
1✔
281
    """
282
    This returns a list of packages we don't want to test.
283
    """
284
    return set()
×
285

286

287
def get_pkgs(exclusions: set[str]) -> set[str]:
1✔
288
    base = dnf.Base()
×
289
    conf = base.conf
×
290
    for c in "AppStream", "BaseOS", "CRB", "Extras":
×
291
        base.repos.add_new_repo(
×
292
            f"{c}-source",
293
            conf,
294
            baseurl=[
295
                f"https://odcs.fedoraproject.org/composes/production/latest-Fedora-ELN/compose/{c}/source/tree/"
296
            ],
297
        )
298
    repos = base.repos.get_matching("*")
×
299
    repos.disable()
×
300
    repos = base.repos.get_matching("*-source*")
×
301
    repos.enable()
×
302

303
    base.fill_sack()
×
304
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
305
    q = q.available()
×
306
    q = q.filter(requires=["clang", "gcc", "gcc-c++"])
×
307
    pkgs = [p.name for p in list(q)]
×
308
    return filter_llvm_pkgs(set(pkgs)) - exclusions
×
309

310

311
def get_monthly_rebuild_packages(pkgs: set[str], copr_pkgs: list[CoprPkg]) -> set[str]:
1✔
312
    """Returns the list of packages that should be built in the next rebuild.
313
        It will select all the packages that built successfully during the last
314
        rebuild.
315

316
    Args:
317
        pkgs (set[str]): A list of every package that should be considered for
318
                        the rebuild.
319
        copr_pkgs (list[dist]): A list containing the latest build results from
320
                                the COPR project.
321

322
    Returns:
323
        set[str]: List of packages that should be rebuilt.
324

325
    Example:
326

327
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : { "id" : 1 } } }
328
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : None } }
329
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 1 } } }
330
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 2 } } }
331
    >>> pkgs = { "b", "c", "d"}
332
    >>> copr_pkgs = [CoprPkg(p) for p in [a, b, c, d]]
333
    >>> rebuild_pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
334
    >>> print(rebuild_pkgs)
335
    {'d'}
336
    """
337

338
    for p in copr_pkgs:
1✔
339
        if p.name not in pkgs:
1✔
340
            continue
1✔
341
        # Always build tier0 packges.
342
        if is_tier0_package(p.name):
1✔
343
            continue
×
344
        if not p.latest_succeeded:
1✔
345
            pkgs.discard(p.name)
1✔
346
            continue
1✔
347
        if p.latest is not None and p.latest.id != p.latest_succeeded.id:
1✔
348
            pkgs.discard(p.name)
1✔
349
    return pkgs
1✔
350

351

352
def get_monthly_rebuild_regressions(
1✔
353
    project_owner: str,
354
    project_name: str,
355
    start_time: datetime.datetime,
356
    copr_pkgs: list[CoprPkg],
357
) -> list[dict[str, Any] | None]:
358
    """Returns the list of packages that failed to build in the most recent
359
       rebuild, but built successfully in the previous rebuild.
360

361
    Args:
362
        start_time (datetime.datetime): The start time of the most recent mass
363
                                        rebuild.  This needs to be a time
364
                                        before the most recent mass rebuild
365
                                        and after the previous one.
366
        copr_pkgs (list[dict]): List of built packages for the COPR project.
367

368
    Returns:
369
        set[str]: List of packages that regressed in the most recent rebuilt.
370

371
    Example:
372

373
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1, "state" : "running", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
374
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
375
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
376
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2, "state" : "canceled", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
377
    >>> e = {"name" : "e", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
378
    >>> f = {"name" : "f", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1731457321, "chroots" : ["x86_64", "ppc64le", "s390x", "aarch64"] } , "latest_succeeded" : { "id" : 1 } } }
379
    >>> copr_pkgs= [CoprPkg(p) for p in [ a, b, c, d, e, f ]]
380
    >>> project_owner = "@fedora-llvm-team"
381
    >>> project_name = "fedora41-clang-20"
382
    >>> regressions = get_monthly_rebuild_regressions(project_owner, project_name, datetime.datetime.fromisoformat("2024-11-11"), copr_pkgs)
383
    >>> print(regressions)
384
    [{'name': 'f', 'fail_id': 2, 'url': 'https://copr.fedorainfracloud.org/coprs/g/fedora-llvm-team/fedora41-clang-20/build/2/', 'chroots': ['x86_64', 'ppc64le', 's390x', 'aarch64']}]
385

386
    """
387
    pkgs = []
1✔
388
    for p in copr_pkgs:
1✔
389
        if not p.latest:
1✔
390
            continue
×
391

392
        # Don't report regressions if there are still builds in progress
393
        if p.latest.is_in_progress():
1✔
394
            continue
1✔
395

396
        if not p.latest_succeeded:
1✔
397
            if is_tier0_package(p.name):
1✔
398
                pkgs.append(p.get_regression_info(project_owner, project_name))
×
399
            continue
1✔
400
        if p.latest.id == p.latest_succeeded.id:
1✔
401
            continue
1✔
402
        # latest is a successful build, but this doesn't mean it failed.
403
        # It could be in progress.
404
        if p.latest.state != "failed":
1✔
405
            continue
1✔
406
        if int(p.latest.submitted_on) < start_time.timestamp():
1✔
407
            continue
1✔
408
        pkgs.append(p.get_regression_info(project_owner, project_name))
1✔
409
    return pkgs
1✔
410

411

412
def get_chroot_results(
1✔
413
    pkgs: list[dict[str, Any] | None], copr_client: copr.v3.Client
414
) -> None:
415
    for p in pkgs:
×
416
        if p is None:
×
417
            continue
×
418
        p["failed_chroots"] = []
×
419
        for c in p["chroots"]:
×
420
            result = copr_client.build_chroot_proxy.get(p["fail_id"], c)
×
421
            if result["state"] == "failed":
×
422
                p["failed_chroots"].append(c)
×
423

424

425
def build_pkg(
1✔
426
    project_owner: str,
427
    project_name: str,
428
    copr_client: copr.v3.Client,
429
    pkg: str,
430
    default_commitish: str,
431
    build_tag: str,
432
    koji_server: str = "https://koji.fedoraproject.org/kojihub",
433
    distgit: str = "fedora",
434
    chroots: list[str] | None = None,
435
) -> None:
436

NEW
437
    buildopts = {
×
438
        "background": True,
439
        "chroots": chroots,
440
        # Increase default timeout because some packages take longer than 5
441
        # hours.  This is easier to do globally tahn to maintain a list of
442
        # long building packages and I don't think there is any downside to
443
        # having a longer default timeout.
444
        "timeout": 90000,
445
    }
446
    koji_session = koji.ClientSession(koji_server)
×
447
    try:
×
448
        build = koji_session.getLatestBuilds(tag=build_tag, package=pkg)[0]
×
449
        build_info = koji_session.getBuild(build["build_id"])
×
450
        commitish = build_info["source"].split("#")[1]
×
451
    except:  # noqa: E722
×
452
        logging.warn(
×
453
            "Could not determine git commit for latest build of {p}.  Defaulting to {default_commitish}."
454
        )
455
        commitish = default_commitish
×
456

457
    copr_client.build_proxy.create_from_distgit(
×
458
        project_owner,
459
        project_name,
460
        pkg,
461
        commitish,
462
        buildopts=buildopts,
463
        distgit=distgit,
464
    )
465

466

467
def start_rebuild(
1✔
468
    project_owner: str,
469
    project_name: str,
470
    copr_client: copr.v3.Client,
471
    pkgs: set[str],
472
    snapshot_project_name: str,
473
    chroots: list[str],
474
) -> None:
475
    print("START", pkgs, "END")
×
476
    # Update the rebuild project to use the latest snapshot
477
    copr_client.project_proxy.edit(
×
478
        project_owner,
479
        project_name,
480
        additional_repos=[
481
            "copr://tstellar/fedora-clang-default-cc",
482
            f"copr://@fedora-llvm-team/{snapshot_project_name}",
483
        ],
484
    )
485

486
    logging.info("Rebuilding", len(pkgs), "packages")
×
NEW
487
    rawhide_tag = get_rawhide_tag()
×
488
    for p in pkgs:
×
NEW
489
        build_pkg(
×
490
            project_owner,
491
            project_name,
492
            copr_client,
493
            p,
494
            default_commitish="rawhide",
495
            build_tag=rawhide_tag,
496
            chroots=chroots,
497
        )
498

499

500
def select_snapshot_project(
1✔
501
    copr_client: copr.v3.Client, target_chroots: list[str], max_lookback_days: int = 14
502
) -> str | None:
503
    project_owner = "@fedora-llvm-team"
×
504
    for i in range(max_lookback_days):
×
505
        chroots = set()
×
506
        day = datetime.date.today() - datetime.timedelta(days=i)
×
507
        project_name = day.strftime("llvm-snapshots-big-merge-%Y%m%d")
×
508
        logging.info("Trying:", project_name)
×
509
        try:
×
510
            p = copr_client.project_proxy.get(project_owner, project_name)
×
511
            if not p:
×
512
                continue
×
513
            pkgs = copr_client.build_proxy.get_list(
×
514
                project_owner, project_name, "llvm", status="succeeded"
515
            )
516
            for pkg in pkgs:
×
517
                chroots.update(pkg["chroots"])
×
518

519
            logging.info(project_name, chroots)
×
520
            if all(t in chroots for t in target_chroots):
×
521
                logging.info("PASS", project_name)
×
522
                return project_name
×
523
        except:  # noqa: E722
×
524
            continue
×
525
    logging.warning("FAIL")
×
526
    return None
×
527

528

529
def create_new_project(
1✔
530
    project_owner: str,
531
    project_name: str,
532
    copr_client: copr.v3.Client,
533
    target_chroots: list[str],
534
    additional_packages: list[str] | None = ["fedora-clang-default-cc"],
535
    with_opts: list[str] | None = ["toolchain_clang", "clang_lto"],
536
) -> None:
537
    copr_client.project_proxy.add(project_owner, project_name, chroots=target_chroots)
×
538
    for c in target_chroots:
×
NEW
539
        if c.startswith("centos-stream"):
×
NEW
540
            centos_version = c.split("-")[2]
×
NEW
541
            arch = c.split("-")[3]
×
542
            # Add centos stream buildroot, because not all packages in the
543
            # buildroot are shipped in the CRB.
NEW
544
            additional_repos = [
×
545
                f"https://kojihub.stream.centos.org/kojifiles/repos/c{centos_version}s-build/latest/{arch}/"
546
            ]
UNCOV
547
        copr_client.project_chroot_proxy.edit(
×
548
            project_owner,
549
            project_name,
550
            c,
551
            additional_packages=additional_packages,
552
            with_opts=with_opts,
553
            additional_repos=additional_repos,
554
        )
555

556

557
def extract_date_from_project(project_name: str) -> datetime.date:
1✔
558
    m = re.search("[0-9]+$", project_name)
×
559
    if not m:
×
560
        raise Exception(f"Invalid project name: {project_name}")
×
561
    return datetime.datetime.fromisoformat(m.group(0)).date()
×
562

563

564
def find_midpoint_project(
1✔
565
    copr_client: copr.v3.Client, good: str, bad: str, chroot: str
566
) -> str:
567
    good_date = extract_date_from_project(good)
×
568
    bad_date = extract_date_from_project(bad)
×
569
    days = (bad_date - good_date).days
×
570
    mid_date = good_date + datetime.timedelta(days=days / 2)
×
571
    increment = 0
×
572
    while mid_date != good_date and mid_date != bad_date:
×
573
        mid_project = re.sub("[0-9]+$", mid_date.strftime("%Y%m%d"), good)
×
574
        owner = mid_project.split("/")[0]
×
575
        project = mid_project.split("/")[1]
×
576
        try:
×
577
            for builds in copr_client.build_proxy.get_list(
×
578
                owner, project, "llvm", "succeeded"
579
            ):
580
                if chroot in builds["chroots"]:
×
581
                    return mid_project
×
582
        except:  # noqa: E722
×
583
            pass
×
584

585
        increment = increment * -1
×
586
        if increment < 0:
×
587
            increment -= 1
×
588
        else:
589
            increment += 1
×
590
        mid_date += datetime.timedelta(days=increment)
×
591

592
    return good
×
593

594

595
def pkg_is_ftbfs(ftbfs_data: list[dict[str, str]], pkg: str, tag: str) -> bool:
1✔
596
    """Determines if a package is already FTBFS.
597

598
    Args:
599
        ftbfs_data (list[dict[str, str]]): JSON packge data from
600
            https://koschei.fedoraproject.org/api/v1/packages
601
        pkg (str): The package to query.
602
        tag (str): The build tag to query.
603

604
    Returns:
605
        bool: True if this package is FTBFS, False otherwise.
606

607
    Example:
608

609
    >>> ftbfs_data=json.loads('[{"name":"0ad","collection":"f43","state":"failing","last_complete_build":{"task_id":140940683,"time_started":"2026-01-10T12:51:47.103144","time_finished":"2026-01-10T13:08:37.453064","epoch":null,"version":"0.27.1","release":"5.fc43"}},{"name":"0ad","collection":"f44","state":"ok","last_complete_build":{"task_id":141319389,"time_started":"2026-01-19T20:13:00.440847","time_finished":"2026-01-19T20:34:54.17938","epoch":null,"version":"0.27.1","release":"6.fc44"}}]')
610
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f43")
611
    True
612
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f44")
613
    False
614
    >>> pkg_is_ftbfs(ftbfs_data, "llvm", "f44")
615
    False
616
    """
617

618
    for ftbfs_pkg in ftbfs_data:
1✔
619
        if ftbfs_pkg["name"] != pkg:
1✔
620
            continue
1✔
621
        if ftbfs_pkg["collection"] != tag:
1✔
622
            continue
1✔
623
        return ftbfs_pkg["state"] == "failing"
1✔
624
    return False
1✔
625

626

627
def main() -> None:
1✔
628
    logging.basicConfig(filename="rebuilder.log", level=logging.INFO)
×
629
    parser = argparse.ArgumentParser()
×
630
    parser.add_argument(
×
631
        "command",
632
        type=str,
633
        choices=[
634
            "rebuild",
635
            "get-regressions",
636
            "get-snapshot-date",
637
            "rebuild-in-progress",
638
            "bisect",
639
            "test",
640
        ],
641
    )
642
    parser.add_argument(
×
643
        "--start-date", type=str, help="Any ISO date format is accepted"
644
    )
645
    parser.add_argument("--chroot", type=str)
×
646
    parser.add_argument("--good", type=str)
×
647
    parser.add_argument("--bad", type=str)
×
NEW
648
    parser.add_argument("--llvm-major", type=int)
×
NEW
649
    parser.add_argument("--skip-same-version", action="store_true")
×
650

651
    args = parser.parse_args()
×
652
    copr_client = copr.v3.Client.create_from_config_file()
×
653

654
    os_name = "fedora-rawhide"
×
655
    target_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
×
656
    target_chroots = [f"{os_name}-{a}" for a in target_arches]
×
657
    project_owner = "@fedora-llvm-team"
×
658
    project_name = "clang-monthly-fedora-rebuild"
×
659

660
    if args.command == "rebuild":
×
661
        exclusions = get_exclusions()
×
662
        pkgs = get_pkgs(exclusions)
×
663
        print(pkgs)
×
664
        try:
×
665
            copr_client.project_proxy.get(project_owner, project_name)
×
666
            copr_pkgs = CoprPkg.get_packages_from_copr(
×
667
                project_owner, project_name, copr_client
668
            )
669
            pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
×
670
        except:  # noqa: E722
×
671
            create_new_project(project_owner, project_name, copr_client, target_chroots)
×
672
        snapshot_project = select_snapshot_project(copr_client, target_chroots)
×
673
        if snapshot_project is not None:
×
674
            start_rebuild(
×
675
                project_owner,
676
                project_name,
677
                copr_client,
678
                pkgs,
679
                snapshot_project,
680
                target_chroots,
681
            )
682
    elif args.command == "get-regressions":
×
683
        start_time = datetime.datetime.fromisoformat(args.start_date)
×
684
        copr_pkgs = CoprPkg.get_packages_from_copr(
×
685
            project_owner, project_name, copr_client
686
        )
687
        pkg_failures = get_monthly_rebuild_regressions(
×
688
            project_owner, project_name, start_time, copr_pkgs
689
        )
690
        get_chroot_results(list(pkg_failures), copr_client)
×
691
        # Delete attributes we don't need to print
692
        for p in pkg_failures:
×
693
            if p is None:
×
694
                continue
×
695
            for k in ["fail_id", "chroots"]:
×
696
                del p[k]
×
697

698
        print(json.dumps(pkg_failures))
×
699
    elif args.command == "get-snapshot-date":
×
700
        project = copr_client.project_proxy.get(project_owner, project_name)
×
701
        for repo in project["additional_repos"]:
×
702
            match = re.match(
×
703
                r"copr://@fedora-llvm-team/llvm-snapshots-big-merge-([0-9]+)$", repo
704
            )
705
            if match:
×
706
                print(datetime.datetime.fromisoformat(match.group(1)).isoformat())
×
707
                return
×
708
    elif args.command == "rebuild-in-progress":
×
709
        for pkg in copr_client.monitor_proxy.monitor(project_owner, project_name)[
×
710
            "packages"
711
        ]:
712
            for c in pkg["chroots"]:
×
713
                build = CoprBuild(pkg["chroots"][c])
×
714
                if build.is_in_progress():
×
715
                    sys.exit(0)
×
716
        sys.exit(1)
×
717
    elif args.command == "bisect":
×
718
        print(find_midpoint_project(copr_client, args.good, args.bad, args.chroot))
×
NEW
719
    elif args.command == "test":
×
NEW
720
        project_owner = "@fedora-llvm-team"
×
NEW
721
        project_name = "clang-fedora-centos-testing"
×
NEW
722
        centos_stream9_chroots = [f"centos-stream-9-{arch}" for arch in target_arches]
×
NEW
723
        centos_stream10_chroots = [f"centos-stream-10-{arch}" for arch in target_arches]
×
NEW
724
        fedora_chroots = [f"fedora-rawhide-{a}" for a in target_arches]
×
NEW
725
        target_chroots = (
×
726
            centos_stream10_chroots + centos_stream9_chroots + fedora_chroots
727
        )
NEW
728
        try:
×
NEW
729
            copr_client.project_proxy.get(project_owner, project_name)
×
NEW
730
        except Exception:
×
NEW
731
            create_new_project(
×
732
                project_owner,
733
                project_name,
734
                copr_client,
735
                target_chroots,
736
                additional_packages=None,
737
                with_opts=None,
738
            )
739
            # Set repo priority so that built packages that depend on a specific
740
            # LLVM snapshot version do not get installed.
NEW
741
            copr_client.project_proxy.edit(
×
742
                project_owner, project_name, repo_priority=1000
743
            )
NEW
744
        centos9_pkgs = get_tier1_pkgs(9)
×
NEW
745
        centos10_pkgs = get_tier1_pkgs(10)
×
NEW
746
        fedora_pkgs = get_tier2_pkgs()
×
747

NEW
748
        copr_client.project_proxy.edit(
×
749
            project_owner,
750
            project_name,
751
            additional_repos=[
752
                "copr://@fedora-llvm-team/llvm-compat-packages",
753
            ],
754
        )
755

756
        # Iterate over a copy of a list so we can remove items:
NEW
757
        for chroot in list(target_chroots):
×
NEW
758
            snapshot_project_name = select_snapshot_project(copr_client, [chroot])
×
NEW
759
            if not snapshot_project_name:
×
NEW
760
                print(f"Could not find snapshot for {chroot}")
×
NEW
761
                target_chroots.remove(chroot)
×
NEW
762
                continue
×
763
            else:
NEW
764
                print(f"Using {snapshot_project_name} for {chroot}")
×
NEW
765
            snapshot_url = f"copr://@fedora-llvm-team/{snapshot_project_name}"
×
NEW
766
            repos = []
×
NEW
767
            for r in copr_client.project_chroot_proxy.get(
×
768
                project_owner, project_name, chroot
769
            )["additional_repos"]:
NEW
770
                if args.skip_same_version and r == snapshot_url:
×
NEW
771
                    print(
×
772
                        f"Not building for {chroot} since snapshot version is the same as the last build"
773
                    )
NEW
774
                    target_chroots.remove(chroot)
×
NEW
775
                if not r.startswith(
×
776
                    "copr://@fedora-llvm-team/llvm-snapshots-big-merge"
777
                ):
NEW
778
                    repos.append(r)
×
NEW
779
            if chroot not in target_chroots:
×
NEW
780
                continue
×
781

NEW
782
            copr_client.project_chroot_proxy.edit(
×
783
                project_owner,
784
                project_name,
785
                chroot,
786
                additional_repos=repos + [snapshot_url],
787
            )
788

NEW
789
        centos_stream9_chroots = [
×
790
            c for c in centos_stream9_chroots if c in target_chroots
791
        ]
NEW
792
        for pkg in centos9_pkgs:
×
NEW
793
            build_pkg(
×
794
                project_owner=project_owner,
795
                project_name=project_name,
796
                copr_client=copr_client,
797
                pkg=pkg,
798
                koji_server="https://kojihub.stream.centos.org/kojihub",
799
                default_commitish="c9s",
800
                build_tag="c9s-candidate",
801
                distgit="centos-stream",
802
                chroots=centos_stream9_chroots,
803
            )
804

NEW
805
        centos_stream10_chroots = [
×
806
            c for c in centos_stream10_chroots if c in target_chroots
807
        ]
NEW
808
        for pkg in centos10_pkgs:
×
NEW
809
            build_pkg(
×
810
                project_owner=project_owner,
811
                project_name=project_name,
812
                copr_client=copr_client,
813
                pkg=pkg,
814
                koji_server="https://kojihub.stream.centos.org/kojihub",
815
                default_commitish="c10s",
816
                build_tag="c10s-candidate",
817
                distgit="centos-stream",
818
                chroots=centos_stream10_chroots,
819
            )
820

NEW
821
        fedora_chroots = [c for c in fedora_chroots if c in target_chroots]
×
822

823
        # Load FTBFS data so we can skip building packages that currently don't build.
NEW
824
        request = urllib.request.Request(
×
825
            "https://koschei.fedoraproject.org/api/v1/packages"
826
        )
827
        # We need to set these headers due to new anti-spam measures in Fedora infrastructure.
NEW
828
        request.add_header("Accept", "text/plain")
×
NEW
829
        request.add_header("User-Agent", "fedora-llvm-team/1.0")
×
NEW
830
        with urllib.request.urlopen(request) as url:
×
NEW
831
            ftbfs_data = json.loads(url.read().decode())
×
832

NEW
833
        rawhide_tag = get_rawhide_tag()
×
NEW
834
        for pkg in fedora_pkgs:
×
NEW
835
            if pkg_is_ftbfs(ftbfs_data, pkg, tag=rawhide_tag):
×
NEW
836
                print(f"Skip building {pkg} on rawhide, because it is FTBFS")
×
NEW
837
                continue
×
NEW
838
            print(f"Building {pkg}")
×
NEW
839
            build_pkg(
×
840
                project_owner=project_owner,
841
                project_name=project_name,
842
                copr_client=copr_client,
843
                pkg=pkg,
844
                default_commitish="rawhide",
845
                build_tag=rawhide_tag,
846
                chroots=fedora_chroots,
847
            )
848

849

850
if __name__ == "__main__":
1✔
851
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc