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

fedora-llvm-team / llvm-snapshots / 24416960798

14 Apr 2026 06:48PM UTC coverage: 55.609% (-0.05%) from 55.663%
24416960798

push

github

tuliom
scripts/rebuilder.py: Increase the tier0 list

Add packages required in the LLVM repoclosure to tier0 in order to test
them early even if their last build failed.
We hope this will help to catch changes problematic changes early before
we start rebasing LLVM on Fedora.

1378 of 2478 relevant lines covered (55.61%)

0.56 hits per line

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

38.19
/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
# Packages with any of the following requirements will be added to the test.
18
llvm_requires = [
1✔
19
    "clang",
20
    "clang-devel",
21
    "clang(major)",
22
    "compiler-rt",
23
    "llvm",
24
    "llvm-devel",
25
]
26

27

28
def get_rawhide_tag() -> str:
1✔
29
    """Returns the current tag for rawhide, i.e. "f44".
30

31

32
    Example:
33

34
    >>> tag=get_rawhide_tag()
35
    >>> bool(re.match("f[0-9]+", tag))
36
    True
37
    """
38
    koji_session = koji.ClientSession("https://koji.fedoraproject.org/kojihub")
1✔
39
    target = koji_session.getBuildTarget("rawhide")
1✔
40
    build_tag_name: str = target["build_tag_name"]
1✔
41
    return build_tag_name.split("-")[0]
1✔
42

43

44
def is_tier0_package(pkg: str) -> bool:
1✔
45
    return pkg in [
1✔
46
        "american-fuzzy-lop",
47
        "clazy",
48
        "dotnet6.0",
49
        "dotnet7.0",
50
        "dotnet8.0",
51
        "dotnet9.0",
52
        "mesa",
53
        "pocl",
54
        "qemu-kvm",  # RHEL name
55
        "qemu",  # Fedora name
56
        "spirv-llvm-translator",
57
        "golang",
58
        "wasi-lbc",
59
    ]
60

61

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

65
    Args:
66
        pkgs (set[str]|list[str]): List of package names
67

68
    Returns:
69
        set[str]: Set of package names without unsupported packages
70

71
    Example:
72

73
    >>> pkgs={"foo", "dotnet6.0", "bar"}
74
    >>> filtered=list(filter_unsupported_pkgs(pkgs))
75
    >>> filtered.sort()
76
    >>> print(filtered)
77
    ['bar', 'foo']
78
    """
79
    return set(pkgs) - {"dotnet6.0", "dotnet7.0"}
1✔
80

81

82
def filter_req_pkgs(base: dnf.Base) -> set[str]:
1✔
83
    """Return a set of packages that require LLVM packages.
84

85
    Args:
86
        base (dnf.Base): A dnf settings that should be queried.
87

88
    Returns:
89
        set[str]: Set of package names that all require LLVM or its
90
                  subpackages.
91
    """
92
    repos = base.repos.get_matching("*")
1✔
93
    repos.disable()
1✔
94
    repos = base.repos.get_matching("*-source*")
1✔
95
    repos.enable()
1✔
96

97
    base.fill_sack()
1✔
98
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
1✔
99

100
    q = q.available()
1✔
101
    ret = []
1✔
102
    for p in llvm_requires:
1✔
103
        tmp = q.filter(requires=p)
1✔
104
        tmppkgs = [p.name for p in list(tmp)]
1✔
105
        ret += tmppkgs
1✔
106
    return filter_llvm_pkgs(set(ret))
1✔
107

108

109
# Packages in CentOS Stream that are built by clang
110
def get_tier1_pkgs(version: int) -> set[str]:
1✔
111
    """
112

113
    Args:
114
        version (int): The CentOS Stream version to query.
115
    Returns:
116
      set: Set of pakcages in the specified CentOS Stream version that
117
           BuildRequire clang.
118

119
    Example:
120

121
    >>> pkgs=get_tier1_pkgs(9)
122
    >>> len(pkgs) > 0
123
    True
124
    >>> pkgs=get_tier1_pkgs(10)
125
    >>> len(pkgs) > 0
126
    True
127
    """
128
    base = dnf.Base()
1✔
129
    conf = base.conf
1✔
130
    for c in "AppStream", "BaseOS", "CRB":
1✔
131
        base.repos.add_new_repo(
1✔
132
            f"{c}-{version}-source",
133
            conf,
134
            baseurl=[
135
                f"https://mirror.stream.centos.org/{version}-stream/{c}/source/tree/"
136
            ],
137
        )
138
    return filter_unsupported_pkgs(filter_req_pkgs(base))
1✔
139

140

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

144
    Args:
145
        version (str): A Fedora version sring e.g. rawhide, 43, 42, etc.
146
    Returns:
147
        set[str]: A set of package names.
148
    Exmaple:
149

150
    >>> pkgs=get_tier2_pkgs()
151
    >>> len(pkgs) > 0
152
    True
153
    >>> pkgs=get_tier2_pkgs("42")
154
    >>> len(pkgs) > 0
155
    True
156
    """
157
    base = dnf.Base()
1✔
158
    conf = base.conf
1✔
159

160
    if version == "rawhide":
1✔
161
        base.repos.add_new_repo(
1✔
162
            f"{version}-source",
163
            conf,
164
            baseurl=[
165
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/development/{version}/Everything/source/tree/"
166
            ],
167
        )
168
    else:
169
        base.repos.add_new_repo(
1✔
170
            f"{version}-source",
171
            conf,
172
            baseurl=[
173
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/releases/{version}/Everything/source/tree/"
174
            ],
175
        )
176
        base.repos.add_new_repo(
1✔
177
            f"{version}-updates-source",
178
            conf,
179
            baseurl=[
180
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/updates/{version}/Everything/source/tree/"
181
            ],
182
        )
183

184
    return filter_req_pkgs(base)
1✔
185

186

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

191
    def is_in_progress(self) -> bool:
1✔
192
        return self.state not in [
1✔
193
            "succeeded",
194
            "forked",
195
            "skipped",
196
            "failed",
197
            "canceled",
198
        ]
199

200

201
# In order to remove the type: ignore[misc] check for this ticket: see https://github.com/Infinidat/munch/issues/84
202
class CoprPkg(Munch):  # type: ignore[misc]
1✔
203
    @classmethod
1✔
204
    def get_packages_from_copr(
1✔
205
        cls, project_owner: str, project_name: str, copr_client: copr.v3.Client
206
    ) -> list["CoprPkg"]:
207
        return [
×
208
            CoprPkg(p)
209
            for p in copr_client.package_proxy.get_list(
210
                project_owner,
211
                project_name,
212
                with_latest_succeeded_build=True,
213
                with_latest_build=True,
214
            )
215
        ]
216

217
    def get_build(self, name: str) -> CoprBuild | None:
1✔
218
        if "builds" not in self:
1✔
219
            return None
×
220
        if name not in self.builds:
1✔
221
            return None
×
222
        build = self.builds[name]
1✔
223
        if not build:
1✔
224
            return None
1✔
225
        return CoprBuild(build)
1✔
226

227
    def get_regression_info(
1✔
228
        self, project_owner: str, project_name: str
229
    ) -> dict[str, Any] | None:
230
        owner_url = project_owner
1✔
231
        if owner_url[0] == "@":
1✔
232
            owner_url = f"g/{owner_url[1:]}"
1✔
233
        latest = self.latest
1✔
234
        if latest is not None:
1✔
235
            return {
1✔
236
                "name": self.name,
237
                "fail_id": latest.id,
238
                "url": f"https://copr.fedorainfracloud.org/coprs/{owner_url}/{project_name}/build/{latest.id}/",
239
                "chroots": latest.chroots,
240
            }
241
        return None
×
242

243
    @property
1✔
244
    def latest(self) -> CoprBuild | None:
1✔
245
        return self.get_build("latest")
1✔
246

247
    @property
1✔
248
    def latest_succeeded(self) -> CoprBuild | None:
1✔
249
        return self.get_build("latest_succeeded")
1✔
250

251

252
def load_tests(
1✔
253
    loader: unittest.TestLoader, standard_tests: unittest.TestSuite, pattern: str
254
) -> unittest.TestSuite:
255
    """We want unittest to pick up all of our doctests
256

257
    See https://docs.python.org/3/library/unittest.html#load-tests-protocol
258
    See https://stackoverflow.com/a/27171468
259
    """
260
    import doctest
×
261

262
    standard_tests.addTests(doctest.DocTestSuite())
×
263
    return standard_tests
×
264

265

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

269
    Args:
270
        pkgs (set[str]): List of package names
271

272
    Returns:
273
        set[str]: List of package names without LLVM packages
274

275
    Example:
276

277
    >>> pkgs={'firefox', 'llvm99', 'libreoffice', 'clang18', 'compiler-rt'}
278
    >>> filtered=list(filter_llvm_pkgs(pkgs))
279
    >>> filtered.sort()
280
    >>> print(filtered)
281
    ['firefox', 'libreoffice']
282

283
    """
284
    llvm_pkgs = {
1✔
285
        "llvm",
286
        "clang",
287
        "llvm-bolt",
288
        "libomp",
289
        "compiler-rt",
290
        "lld",
291
        "lldb",
292
        "polly",
293
        "libcxx",
294
        "libclc",
295
        "flang",
296
        "mlir",
297
    }
298
    llvm_pkg_pattern = rf"({'|'.join(llvm_pkgs)})[0-9]*$"
1✔
299
    return {pkg for pkg in pkgs if not re.match(llvm_pkg_pattern, pkg)}
1✔
300

301

302
def get_exclusions() -> set[str]:
1✔
303
    """
304
    This returns a list of packages we don't want to test.
305
    """
306
    return set()
×
307

308

309
def get_pkgs(exclusions: set[str]) -> set[str]:
1✔
310
    base = dnf.Base()
×
311
    conf = base.conf
×
312
    for c in "AppStream", "BaseOS", "CRB", "Extras":
×
313
        base.repos.add_new_repo(
×
314
            f"{c}-source",
315
            conf,
316
            baseurl=[
317
                f"https://odcs.fedoraproject.org/composes/production/latest-Fedora-ELN/compose/{c}/source/tree/"
318
            ],
319
        )
320
    repos = base.repos.get_matching("*")
×
321
    repos.disable()
×
322
    repos = base.repos.get_matching("*-source*")
×
323
    repos.enable()
×
324

325
    base.fill_sack()
×
326
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
327
    q = q.available()
×
328
    q = q.filter(requires=["clang", "gcc", "gcc-c++"])
×
329
    pkgs = [p.name for p in list(q)]
×
330
    return filter_llvm_pkgs(set(pkgs)) - exclusions
×
331

332

333
def get_monthly_rebuild_packages(pkgs: set[str], copr_pkgs: list[CoprPkg]) -> set[str]:
1✔
334
    """Returns the list of packages that should be built in the next rebuild.
335
        It will select all the packages that built successfully during the last
336
        rebuild.
337

338
    Args:
339
        pkgs (set[str]): A list of every package that should be considered for
340
                        the rebuild.
341
        copr_pkgs (list[dist]): A list containing the latest build results from
342
                                the COPR project.
343

344
    Returns:
345
        set[str]: List of packages that should be rebuilt.
346

347
    Example:
348

349
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : { "id" : 1 } } }
350
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : None } }
351
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 1 } } }
352
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 2 } } }
353
    >>> pkgs = { "b", "c", "d"}
354
    >>> copr_pkgs = [CoprPkg(p) for p in [a, b, c, d]]
355
    >>> rebuild_pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
356
    >>> print(rebuild_pkgs)
357
    {'d'}
358
    """
359

360
    for p in copr_pkgs:
1✔
361
        if p.name not in pkgs:
1✔
362
            continue
1✔
363
        # Always build tier0 packges.
364
        if is_tier0_package(p.name):
1✔
365
            continue
×
366
        if not p.latest_succeeded:
1✔
367
            pkgs.discard(p.name)
1✔
368
            continue
1✔
369
        if p.latest is not None and p.latest.id != p.latest_succeeded.id:
1✔
370
            pkgs.discard(p.name)
1✔
371
    return pkgs
1✔
372

373

374
def get_monthly_rebuild_regressions(
1✔
375
    project_owner: str,
376
    project_name: str,
377
    start_time: datetime.datetime,
378
    copr_pkgs: list[CoprPkg],
379
) -> list[dict[str, Any] | None]:
380
    """Returns the list of packages that failed to build in the most recent
381
       rebuild, but built successfully in the previous rebuild.
382

383
    Args:
384
        start_time (datetime.datetime): The start time of the most recent mass
385
                                        rebuild.  This needs to be a time
386
                                        before the most recent mass rebuild
387
                                        and after the previous one.
388
        copr_pkgs (list[dict]): List of built packages for the COPR project.
389

390
    Returns:
391
        set[str]: List of packages that regressed in the most recent rebuilt.
392

393
    Example:
394

395
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1, "state" : "running", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
396
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
397
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
398
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2, "state" : "canceled", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
399
    >>> e = {"name" : "e", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
400
    >>> f = {"name" : "f", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1731457321, "chroots" : ["x86_64", "ppc64le", "s390x", "aarch64"] } , "latest_succeeded" : { "id" : 1 } } }
401
    >>> copr_pkgs= [CoprPkg(p) for p in [ a, b, c, d, e, f ]]
402
    >>> project_owner = "@fedora-llvm-team"
403
    >>> project_name = "fedora41-clang-20"
404
    >>> regressions = get_monthly_rebuild_regressions(project_owner, project_name, datetime.datetime.fromisoformat("2024-11-11"), copr_pkgs)
405
    >>> print(regressions)
406
    [{'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']}]
407

408
    """
409
    pkgs = []
1✔
410
    for p in copr_pkgs:
1✔
411
        if not p.latest:
1✔
412
            continue
×
413

414
        # Don't report regressions if there are still builds in progress
415
        if p.latest.is_in_progress():
1✔
416
            continue
1✔
417

418
        if not p.latest_succeeded:
1✔
419
            if is_tier0_package(p.name):
1✔
420
                pkgs.append(p.get_regression_info(project_owner, project_name))
×
421
            continue
1✔
422
        if p.latest.id == p.latest_succeeded.id:
1✔
423
            continue
1✔
424
        # latest is a successful build, but this doesn't mean it failed.
425
        # It could be in progress.
426
        if p.latest.state != "failed":
1✔
427
            continue
1✔
428
        if int(p.latest.submitted_on) < start_time.timestamp():
1✔
429
            continue
1✔
430
        pkgs.append(p.get_regression_info(project_owner, project_name))
1✔
431
    return pkgs
1✔
432

433

434
def get_chroot_results(
1✔
435
    pkgs: list[dict[str, Any] | None], copr_client: copr.v3.Client
436
) -> None:
437
    for p in pkgs:
×
438
        if p is None:
×
439
            continue
×
440
        p["failed_chroots"] = []
×
441
        for c in p["chroots"]:
×
442
            result = copr_client.build_chroot_proxy.get(p["fail_id"], c)
×
443
            if result["state"] == "failed":
×
444
                p["failed_chroots"].append(c)
×
445

446

447
def build_pkg(
1✔
448
    project_owner: str,
449
    project_name: str,
450
    copr_client: copr.v3.Client,
451
    pkg: str,
452
    default_commitish: str,
453
    build_tag: str,
454
    koji_server: str = "https://koji.fedoraproject.org/kojihub",
455
    distgit: str = "fedora",
456
    chroots: list[str] | None = None,
457
) -> None:
458

459
    buildopts = {
×
460
        "background": True,
461
        "chroots": chroots,
462
        # Increase default timeout because some packages take longer than 5
463
        # hours.  This is easier to do globally tahn to maintain a list of
464
        # long building packages and I don't think there is any downside to
465
        # having a longer default timeout.
466
        "timeout": 90000,
467
    }
468
    koji_session = koji.ClientSession(koji_server)
×
469
    try:
×
470
        build = koji_session.getLatestBuilds(tag=build_tag, package=pkg)[0]
×
471
        build_info = koji_session.getBuild(build["build_id"])
×
472
        commitish = build_info["source"].split("#")[1]
×
473
    except:  # noqa: E722
×
474
        logging.warn(
×
475
            "Could not determine git commit for latest build of {p}.  Defaulting to {default_commitish}."
476
        )
477
        commitish = default_commitish
×
478

479
    copr_client.build_proxy.create_from_distgit(
×
480
        project_owner,
481
        project_name,
482
        pkg,
483
        commitish,
484
        buildopts=buildopts,
485
        distgit=distgit,
486
    )
487

488

489
def start_rebuild(
1✔
490
    project_owner: str,
491
    project_name: str,
492
    copr_client: copr.v3.Client,
493
    pkgs: set[str],
494
    snapshot_project_name: str,
495
    chroots: list[str],
496
) -> None:
497
    print("START", pkgs, "END")
×
498
    # Update the rebuild project to use the latest snapshot
499
    copr_client.project_proxy.edit(
×
500
        project_owner,
501
        project_name,
502
        additional_repos=[
503
            "copr://tstellar/fedora-clang-default-cc",
504
            f"copr://@fedora-llvm-team/{snapshot_project_name}",
505
        ],
506
    )
507

508
    logging.info("Rebuilding", len(pkgs), "packages")
×
509
    rawhide_tag = get_rawhide_tag()
×
510
    for p in pkgs:
×
511
        build_pkg(
×
512
            project_owner,
513
            project_name,
514
            copr_client,
515
            p,
516
            default_commitish="rawhide",
517
            build_tag=rawhide_tag,
518
            chroots=chroots,
519
        )
520

521

522
def select_snapshot_project(
1✔
523
    copr_client: copr.v3.Client, target_chroots: list[str], max_lookback_days: int = 14
524
) -> str | None:
525
    project_owner = "@fedora-llvm-team"
×
526
    for i in range(max_lookback_days):
×
527
        chroots = set()
×
528
        day = datetime.date.today() - datetime.timedelta(days=i)
×
529
        project_name = day.strftime("llvm-snapshots-big-merge-%Y%m%d")
×
530
        logging.info("Trying:", project_name)
×
531
        try:
×
532
            p = copr_client.project_proxy.get(project_owner, project_name)
×
533
            if not p:
×
534
                continue
×
535
            pkgs = copr_client.build_proxy.get_list(
×
536
                project_owner, project_name, "llvm", status="succeeded"
537
            )
538
            for pkg in pkgs:
×
539
                chroots.update(pkg["chroots"])
×
540

541
            logging.info(project_name, chroots)
×
542
            if all(t in chroots for t in target_chroots):
×
543
                logging.info("PASS", project_name)
×
544
                return project_name
×
545
        except:  # noqa: E722
×
546
            continue
×
547
    logging.warning("FAIL")
×
548
    return None
×
549

550

551
def create_new_project(
1✔
552
    project_owner: str,
553
    project_name: str,
554
    copr_client: copr.v3.Client,
555
    target_chroots: list[str],
556
    additional_packages: list[str] | None = ["fedora-clang-default-cc"],
557
    with_opts: list[str] | None = ["toolchain_clang", "clang_lto"],
558
) -> None:
559
    copr_client.project_proxy.add(project_owner, project_name, chroots=target_chroots)
×
560
    for c in target_chroots:
×
561
        if c.startswith("centos-stream"):
×
562
            centos_version = c.split("-")[2]
×
563
            arch = c.split("-")[3]
×
564
            # Add centos stream buildroot, because not all packages in the
565
            # buildroot are shipped in the CRB.
566
            additional_repos = [
×
567
                f"https://kojihub.stream.centos.org/kojifiles/repos/c{centos_version}s-build/latest/{arch}/"
568
            ]
569
        copr_client.project_chroot_proxy.edit(
×
570
            project_owner,
571
            project_name,
572
            c,
573
            additional_packages=additional_packages,
574
            with_opts=with_opts,
575
            additional_repos=additional_repos,
576
        )
577

578

579
def extract_date_from_project(project_name: str) -> datetime.date:
1✔
580
    m = re.search("[0-9]+$", project_name)
×
581
    if not m:
×
582
        raise Exception(f"Invalid project name: {project_name}")
×
583
    return datetime.datetime.fromisoformat(m.group(0)).date()
×
584

585

586
def find_midpoint_project(
1✔
587
    copr_client: copr.v3.Client, good: str, bad: str, chroot: str
588
) -> str:
589
    good_date = extract_date_from_project(good)
×
590
    bad_date = extract_date_from_project(bad)
×
591
    days = (bad_date - good_date).days
×
592
    mid_date = good_date + datetime.timedelta(days=days / 2)
×
593
    increment = 0
×
594
    while mid_date != good_date and mid_date != bad_date:
×
595
        mid_project = re.sub("[0-9]+$", mid_date.strftime("%Y%m%d"), good)
×
596
        owner = mid_project.split("/")[0]
×
597
        project = mid_project.split("/")[1]
×
598
        try:
×
599
            for builds in copr_client.build_proxy.get_list(
×
600
                owner, project, "llvm", "succeeded"
601
            ):
602
                if chroot in builds["chroots"]:
×
603
                    return mid_project
×
604
        except:  # noqa: E722
×
605
            pass
×
606

607
        increment = increment * -1
×
608
        if increment < 0:
×
609
            increment -= 1
×
610
        else:
611
            increment += 1
×
612
        mid_date += datetime.timedelta(days=increment)
×
613

614
    return good
×
615

616

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

620
    Args:
621
        ftbfs_data (list[dict[str, str]]): JSON packge data from
622
            https://koschei.fedoraproject.org/api/v1/packages
623
        pkg (str): The package to query.
624
        tag (str): The build tag to query.
625

626
    Returns:
627
        bool: True if this package is FTBFS, False otherwise.
628

629
    Example:
630

631
    >>> 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"}}]')
632
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f43")
633
    True
634
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f44")
635
    False
636
    >>> pkg_is_ftbfs(ftbfs_data, "llvm", "f44")
637
    False
638
    """
639

640
    for ftbfs_pkg in ftbfs_data:
1✔
641
        if ftbfs_pkg["name"] != pkg:
1✔
642
            continue
1✔
643
        if ftbfs_pkg["collection"] != tag:
1✔
644
            continue
1✔
645
        return ftbfs_pkg["state"] == "failing"
1✔
646
    return False
1✔
647

648

649
def main() -> None:
1✔
650
    logging.basicConfig(filename="rebuilder.log", level=logging.INFO)
×
651
    parser = argparse.ArgumentParser()
×
652
    parser.add_argument(
×
653
        "command",
654
        type=str,
655
        choices=[
656
            "rebuild",
657
            "get-regressions",
658
            "get-snapshot-date",
659
            "rebuild-in-progress",
660
            "bisect",
661
            "test",
662
        ],
663
    )
664
    parser.add_argument(
×
665
        "--start-date", type=str, help="Any ISO date format is accepted"
666
    )
667
    parser.add_argument("--chroot", type=str)
×
668
    parser.add_argument("--good", type=str)
×
669
    parser.add_argument("--bad", type=str)
×
670
    parser.add_argument("--llvm-major", type=int)
×
671
    parser.add_argument("--skip-same-version", action="store_true")
×
672

673
    args = parser.parse_args()
×
674
    copr_client = copr.v3.Client.create_from_config_file()
×
675

676
    os_name = "fedora-rawhide"
×
677
    target_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
×
678
    target_chroots = [f"{os_name}-{a}" for a in target_arches]
×
679
    project_owner = "@fedora-llvm-team"
×
680
    project_name = "clang-monthly-fedora-rebuild"
×
681

682
    if args.command == "rebuild":
×
683
        exclusions = get_exclusions()
×
684
        pkgs = get_pkgs(exclusions)
×
685
        print(pkgs)
×
686
        try:
×
687
            copr_client.project_proxy.get(project_owner, project_name)
×
688
            copr_pkgs = CoprPkg.get_packages_from_copr(
×
689
                project_owner, project_name, copr_client
690
            )
691
            pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
×
692
        except:  # noqa: E722
×
693
            create_new_project(project_owner, project_name, copr_client, target_chroots)
×
694
        snapshot_project = select_snapshot_project(copr_client, target_chroots)
×
695
        if snapshot_project is not None:
×
696
            start_rebuild(
×
697
                project_owner,
698
                project_name,
699
                copr_client,
700
                pkgs,
701
                snapshot_project,
702
                target_chroots,
703
            )
704
    elif args.command == "get-regressions":
×
705
        start_time = datetime.datetime.fromisoformat(args.start_date)
×
706
        copr_pkgs = CoprPkg.get_packages_from_copr(
×
707
            project_owner, project_name, copr_client
708
        )
709
        pkg_failures = get_monthly_rebuild_regressions(
×
710
            project_owner, project_name, start_time, copr_pkgs
711
        )
712
        get_chroot_results(list(pkg_failures), copr_client)
×
713
        # Delete attributes we don't need to print
714
        for p in pkg_failures:
×
715
            if p is None:
×
716
                continue
×
717
            for k in ["fail_id", "chroots"]:
×
718
                del p[k]
×
719

720
        print(json.dumps(pkg_failures))
×
721
    elif args.command == "get-snapshot-date":
×
722
        project = copr_client.project_proxy.get(project_owner, project_name)
×
723
        for repo in project["additional_repos"]:
×
724
            match = re.match(
×
725
                r"copr://@fedora-llvm-team/llvm-snapshots-big-merge-([0-9]+)$", repo
726
            )
727
            if match:
×
728
                print(datetime.datetime.fromisoformat(match.group(1)).isoformat())
×
729
                return
×
730
    elif args.command == "rebuild-in-progress":
×
731
        for pkg in copr_client.monitor_proxy.monitor(project_owner, project_name)[
×
732
            "packages"
733
        ]:
734
            for c in pkg["chroots"]:
×
735
                build = CoprBuild(pkg["chroots"][c])
×
736
                if build.is_in_progress():
×
737
                    sys.exit(0)
×
738
        sys.exit(1)
×
739
    elif args.command == "bisect":
×
740
        print(find_midpoint_project(copr_client, args.good, args.bad, args.chroot))
×
741
    elif args.command == "test":
×
742
        project_owner = "@fedora-llvm-team"
×
743
        project_name = "clang-fedora-centos-testing"
×
744
        centos_stream9_chroots = [f"centos-stream-9-{arch}" for arch in target_arches]
×
745
        centos_stream10_chroots = [f"centos-stream-10-{arch}" for arch in target_arches]
×
746
        fedora_chroots = [f"fedora-rawhide-{a}" for a in target_arches]
×
747
        target_chroots = (
×
748
            centos_stream10_chroots + centos_stream9_chroots + fedora_chroots
749
        )
750
        try:
×
751
            copr_client.project_proxy.get(project_owner, project_name)
×
752
        except Exception:
×
753
            create_new_project(
×
754
                project_owner,
755
                project_name,
756
                copr_client,
757
                target_chroots,
758
                additional_packages=None,
759
                with_opts=None,
760
            )
761
            # Set repo priority so that built packages that depend on a specific
762
            # LLVM snapshot version do not get installed.
763
            copr_client.project_proxy.edit(
×
764
                project_owner, project_name, repo_priority=1000
765
            )
766
        centos9_pkgs = get_tier1_pkgs(9)
×
767
        centos10_pkgs = get_tier1_pkgs(10)
×
768
        fedora_pkgs = get_tier2_pkgs()
×
769

770
        copr_client.project_proxy.edit(
×
771
            project_owner,
772
            project_name,
773
            additional_repos=[
774
                "copr://@fedora-llvm-team/llvm-compat-packages",
775
            ],
776
        )
777

778
        # Iterate over a copy of a list so we can remove items:
779
        for chroot in list(target_chroots):
×
780
            snapshot_project_name = select_snapshot_project(copr_client, [chroot])
×
781
            if not snapshot_project_name:
×
782
                print(f"Could not find snapshot for {chroot}")
×
783
                target_chroots.remove(chroot)
×
784
                continue
×
785
            else:
786
                print(f"Using {snapshot_project_name} for {chroot}")
×
787
            snapshot_url = f"copr://@fedora-llvm-team/{snapshot_project_name}"
×
788
            repos = []
×
789
            for r in copr_client.project_chroot_proxy.get(
×
790
                project_owner, project_name, chroot
791
            )["additional_repos"]:
792
                if args.skip_same_version and r == snapshot_url:
×
793
                    print(
×
794
                        f"Not building for {chroot} since snapshot version is the same as the last build"
795
                    )
796
                    target_chroots.remove(chroot)
×
797
                if not r.startswith(
×
798
                    "copr://@fedora-llvm-team/llvm-snapshots-big-merge"
799
                ):
800
                    repos.append(r)
×
801
            if chroot not in target_chroots:
×
802
                continue
×
803

804
            copr_client.project_chroot_proxy.edit(
×
805
                project_owner,
806
                project_name,
807
                chroot,
808
                additional_repos=repos + [snapshot_url],
809
            )
810

811
        centos_stream9_chroots = [
×
812
            c for c in centos_stream9_chroots if c in target_chroots
813
        ]
814
        for pkg in centos9_pkgs:
×
815
            build_pkg(
×
816
                project_owner=project_owner,
817
                project_name=project_name,
818
                copr_client=copr_client,
819
                pkg=pkg,
820
                koji_server="https://kojihub.stream.centos.org/kojihub",
821
                default_commitish="c9s",
822
                build_tag="c9s-candidate",
823
                distgit="centos-stream",
824
                chroots=centos_stream9_chroots,
825
            )
826

827
        centos_stream10_chroots = [
×
828
            c for c in centos_stream10_chroots if c in target_chroots
829
        ]
830
        for pkg in centos10_pkgs:
×
831
            build_pkg(
×
832
                project_owner=project_owner,
833
                project_name=project_name,
834
                copr_client=copr_client,
835
                pkg=pkg,
836
                koji_server="https://kojihub.stream.centos.org/kojihub",
837
                default_commitish="c10s",
838
                build_tag="c10s-candidate",
839
                distgit="centos-stream",
840
                chroots=centos_stream10_chroots,
841
            )
842

843
        fedora_chroots = [c for c in fedora_chroots if c in target_chroots]
×
844

845
        # Load FTBFS data so we can skip building packages that currently don't build.
846
        request = urllib.request.Request(
×
847
            "https://koschei.fedoraproject.org/api/v1/packages"
848
        )
849
        # We need to set these headers due to new anti-spam measures in Fedora infrastructure.
850
        request.add_header("Accept", "text/plain")
×
851
        request.add_header("User-Agent", "fedora-llvm-team/1.0")
×
852
        with urllib.request.urlopen(request) as url:
×
853
            ftbfs_data = json.loads(url.read().decode())
×
854

855
        rawhide_tag = get_rawhide_tag()
×
856
        for pkg in fedora_pkgs:
×
857
            if pkg_is_ftbfs(ftbfs_data, pkg, tag=rawhide_tag):
×
858
                print(f"Skip building {pkg} on rawhide, because it is FTBFS")
×
859
                continue
×
860
            print(f"Building {pkg}")
×
861
            build_pkg(
×
862
                project_owner=project_owner,
863
                project_name=project_name,
864
                copr_client=copr_client,
865
                pkg=pkg,
866
                default_commitish="rawhide",
867
                build_tag=rawhide_tag,
868
                chroots=fedora_chroots,
869
            )
870

871

872
if __name__ == "__main__":
1✔
873
    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