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

fedora-llvm-team / llvm-snapshots / 21190953722

20 Jan 2026 11:21PM UTC coverage: 55.466% (-0.7%) from 56.137%
21190953722

Pull #1831

github

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

44 of 110 new or added lines in 1 file covered. (40.0%)

1 existing line in 1 file now uncovered.

1375 of 2479 relevant lines covered (55.47%)

0.55 hits per line

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

37.57
/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"."""
NEW
20
    koji_session = koji.ClientSession("https://koji.fedoraproject.org/kojihub")
×
NEW
21
    target = koji_session.getBuildTarget("rawhide")
×
NEW
22
    build_tag_name: str = target["build_tag_name"]
×
NEW
23
    return build_tag_name.split("-")[0]
×
24

25

26
def is_tier0_package(pkg: str) -> bool:
1✔
27
    return pkg in [
1✔
28
        "dotnet6.0",
29
        "dotnet7.0",
30
        "dotnet8.0",
31
        "dotnet9.0",
32
        "qemu-kvm",  # RHEL name
33
        "qemu",  # Fedora name
34
        "golang",
35
        "wasi-lbc",
36
    ]
37

38

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

42
    Args:
43
        pkgs (set[str]|list[str]): List of package names
44

45
    Returns:
46
        set[str]: Set of package names without unsupported packages
47

48
    Example:
49

50
    >>> pkgs={"foo", "dotnet6.0", "bar"}
51
    >>> filtered=list(filter_unsupported_pkgs(pkgs))
52
    >>> filtered.sort()
53
    >>> print(filtered)
54
    ['bar', 'foo']
55
    """
56
    return set(pkgs) - {"dotnet6.0", "dotnet7.0"}
1✔
57

58

59
# Packages in CentOS Stream that are built by clang
60
def get_tier1_pkgs(version: int) -> set[str]:
1✔
61
    """
62

63
    Args:
64
        version (int): The CentOS Stream version to query.
65
    Returns:
66
      set: Set of pakcages in the specified CentOS Stream version that
67
           BuildRequire clang.
68

69
    Example:
70

71
    >>> pkgs=get_tier1_pkgs(9)
72
    >>> len(pkgs) > 0
73
    True
74
    >>> pkgs=get_tier1_pkgs(10)
75
    >>> len(pkgs) > 0
76
    True
77
    """
78
    base = dnf.Base()
1✔
79
    conf = base.conf
1✔
80
    for c in "AppStream", "BaseOS", "CRB":
1✔
81
        base.repos.add_new_repo(
1✔
82
            f"{c}-{version}-source",
83
            conf,
84
            baseurl=[
85
                f"https://mirror.stream.centos.org/{version}-stream/{c}/source/tree/"
86
            ],
87
        )
88
    repos = base.repos.get_matching("*")
1✔
89
    repos.disable()
1✔
90
    repos = base.repos.get_matching("*-source*")
1✔
91
    repos.enable()
1✔
92

93
    base.fill_sack()
1✔
94
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
1✔
95
    q = q.available()
1✔
96
    q = q.filter(requires=["clang"])
1✔
97
    pkgs = [p.name for p in list(q)]
1✔
98
    return filter_unsupported_pkgs(filter_llvm_pkgs(set(pkgs)))
1✔
99

100

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

104
    Args:
105
        version (str): A Fedora version sring e.g. rawhide, 43, 42, etc.
106
    Returns:
107
        set[str]: A set of package names.
108
    Exmaple:
109

110
    >>> pkgs=get_tier2_pkgs()
111
    >>> len(pkgs) > 0
112
    True
113
    >>> pkgs=get_tier2_pkgs("42")
114
    >>> len(pkgs) > 0
115
    True
116
    """
117
    base = dnf.Base()
1✔
118
    conf = base.conf
1✔
119

120
    if version == "rawhide":
1✔
121
        base.repos.add_new_repo(
1✔
122
            f"{version}-source",
123
            conf,
124
            baseurl=[
125
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/development/{version}/Everything/source/tree/"
126
            ],
127
        )
128
    else:
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/releases/{version}/Everything/source/tree/"
134
            ],
135
        )
136
        base.repos.add_new_repo(
1✔
137
            f"{version}-updates-source",
138
            conf,
139
            baseurl=[
140
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/updates/{version}/Everything/source/tree/"
141
            ],
142
        )
143

144
    repos = base.repos.get_matching("*")
1✔
145
    repos.disable()
1✔
146
    repos = base.repos.get_matching("*-source*")
1✔
147
    repos.enable()
1✔
148

149
    base.fill_sack()
1✔
150
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
1✔
151
    q = q.available()
1✔
152
    q = q.filter(requires=["clang"])
1✔
153
    pkgs = [p.name for p in list(q)]
1✔
154
    return filter_llvm_pkgs(set(pkgs))
1✔
155

156

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

161
    def is_in_progress(self) -> bool:
1✔
162
        return self.state not in [
1✔
163
            "succeeded",
164
            "forked",
165
            "skipped",
166
            "failed",
167
            "canceled",
168
        ]
169

170

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

187
    def get_build(self, name: str) -> CoprBuild | None:
1✔
188
        if "builds" not in self:
1✔
189
            return None
×
190
        if name not in self.builds:
1✔
191
            return None
×
192
        build = self.builds[name]
1✔
193
        if not build:
1✔
194
            return None
1✔
195
        return CoprBuild(build)
1✔
196

197
    def get_regression_info(
1✔
198
        self, project_owner: str, project_name: str
199
    ) -> dict[str, Any] | None:
200
        owner_url = project_owner
1✔
201
        if owner_url[0] == "@":
1✔
202
            owner_url = f"g/{owner_url[1:]}"
1✔
203
        latest = self.latest
1✔
204
        if latest is not None:
1✔
205
            return {
1✔
206
                "name": self.name,
207
                "fail_id": latest.id,
208
                "url": f"https://copr.fedorainfracloud.org/coprs/{owner_url}/{project_name}/build/{latest.id}/",
209
                "chroots": latest.chroots,
210
            }
211
        return None
×
212

213
    @property
1✔
214
    def latest(self) -> CoprBuild | None:
1✔
215
        return self.get_build("latest")
1✔
216

217
    @property
1✔
218
    def latest_succeeded(self) -> CoprBuild | None:
1✔
219
        return self.get_build("latest_succeeded")
1✔
220

221

222
def load_tests(
1✔
223
    loader: unittest.TestLoader, standard_tests: unittest.TestSuite, pattern: str
224
) -> unittest.TestSuite:
225
    """We want unittest to pick up all of our doctests
226

227
    See https://docs.python.org/3/library/unittest.html#load-tests-protocol
228
    See https://stackoverflow.com/a/27171468
229
    """
230
    import doctest
×
231

232
    standard_tests.addTests(doctest.DocTestSuite())
×
233
    return standard_tests
×
234

235

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

239
    Args:
240
        pkgs (set[str]): List of package names
241

242
    Returns:
243
        set[str]: List of package names without LLVM packages
244

245
    Example:
246

247
    >>> pkgs={'firefox', 'llvm99', 'libreoffice', 'clang18', 'compiler-rt'}
248
    >>> filtered=list(filter_llvm_pkgs(pkgs))
249
    >>> filtered.sort()
250
    >>> print(filtered)
251
    ['firefox', 'libreoffice']
252

253
    """
254
    llvm_pkgs = {
1✔
255
        "llvm",
256
        "clang",
257
        "llvm-bolt",
258
        "libomp",
259
        "compiler-rt",
260
        "lld",
261
        "lldb",
262
        "polly",
263
        "libcxx",
264
        "libclc",
265
        "flang",
266
        "mlir",
267
    }
268
    llvm_pkg_pattern = rf"({'|'.join(llvm_pkgs)})[0-9]*$"
1✔
269
    return {pkg for pkg in pkgs if not re.match(llvm_pkg_pattern, pkg)}
1✔
270

271

272
def get_exclusions() -> set[str]:
1✔
273
    """
274
    This returns a list of packages we don't want to test.
275
    """
276
    return set()
×
277

278

279
def get_pkgs(exclusions: set[str]) -> set[str]:
1✔
280
    base = dnf.Base()
×
281
    conf = base.conf
×
282
    for c in "AppStream", "BaseOS", "CRB", "Extras":
×
283
        base.repos.add_new_repo(
×
284
            f"{c}-source",
285
            conf,
286
            baseurl=[
287
                f"https://odcs.fedoraproject.org/composes/production/latest-Fedora-ELN/compose/{c}/source/tree/"
288
            ],
289
        )
290
    repos = base.repos.get_matching("*")
×
291
    repos.disable()
×
292
    repos = base.repos.get_matching("*-source*")
×
293
    repos.enable()
×
294

295
    base.fill_sack()
×
296
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
297
    q = q.available()
×
298
    q = q.filter(requires=["clang", "gcc", "gcc-c++"])
×
299
    pkgs = [p.name for p in list(q)]
×
300
    return filter_llvm_pkgs(set(pkgs)) - exclusions
×
301

302

303
def get_monthly_rebuild_packages(pkgs: set[str], copr_pkgs: list[CoprPkg]) -> set[str]:
1✔
304
    """Returns the list of packages that should be built in the next rebuild.
305
        It will select all the packages that built successfully during the last
306
        rebuild.
307

308
    Args:
309
        pkgs (set[str]): A list of every package that should be considered for
310
                        the rebuild.
311
        copr_pkgs (list[dist]): A list containing the latest build results from
312
                                the COPR project.
313

314
    Returns:
315
        set[str]: List of packages that should be rebuilt.
316

317
    Example:
318

319
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : { "id" : 1 } } }
320
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : None } }
321
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 1 } } }
322
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 2 } } }
323
    >>> pkgs = { "b", "c", "d"}
324
    >>> copr_pkgs = [CoprPkg(p) for p in [a, b, c, d]]
325
    >>> rebuild_pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
326
    >>> print(rebuild_pkgs)
327
    {'d'}
328
    """
329

330
    for p in copr_pkgs:
1✔
331
        if p.name not in pkgs:
1✔
332
            continue
1✔
333
        # Always build tier0 packges.
334
        if is_tier0_package(p.name):
1✔
335
            continue
×
336
        if not p.latest_succeeded:
1✔
337
            pkgs.discard(p.name)
1✔
338
            continue
1✔
339
        if p.latest is not None and p.latest.id != p.latest_succeeded.id:
1✔
340
            pkgs.discard(p.name)
1✔
341
    return pkgs
1✔
342

343

344
def get_monthly_rebuild_regressions(
1✔
345
    project_owner: str,
346
    project_name: str,
347
    start_time: datetime.datetime,
348
    copr_pkgs: list[CoprPkg],
349
) -> list[dict[str, Any] | None]:
350
    """Returns the list of packages that failed to build in the most recent
351
       rebuild, but built successfully in the previous rebuild.
352

353
    Args:
354
        start_time (datetime.datetime): The start time of the most recent mass
355
                                        rebuild.  This needs to be a time
356
                                        before the most recent mass rebuild
357
                                        and after the previous one.
358
        copr_pkgs (list[dict]): List of built packages for the COPR project.
359

360
    Returns:
361
        set[str]: List of packages that regressed in the most recent rebuilt.
362

363
    Example:
364

365
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1, "state" : "running", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
366
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
367
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
368
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2, "state" : "canceled", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
369
    >>> e = {"name" : "e", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
370
    >>> f = {"name" : "f", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1731457321, "chroots" : ["x86_64", "ppc64le", "s390x", "aarch64"] } , "latest_succeeded" : { "id" : 1 } } }
371
    >>> copr_pkgs= [CoprPkg(p) for p in [ a, b, c, d, e, f ]]
372
    >>> project_owner = "@fedora-llvm-team"
373
    >>> project_name = "fedora41-clang-20"
374
    >>> regressions = get_monthly_rebuild_regressions(project_owner, project_name, datetime.datetime.fromisoformat("2024-11-11"), copr_pkgs)
375
    >>> print(regressions)
376
    [{'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']}]
377

378
    """
379
    pkgs = []
1✔
380
    for p in copr_pkgs:
1✔
381
        if not p.latest:
1✔
382
            continue
×
383

384
        # Don't report regressions if there are still builds in progress
385
        if p.latest.is_in_progress():
1✔
386
            continue
1✔
387

388
        if not p.latest_succeeded:
1✔
389
            if is_tier0_package(p.name):
1✔
390
                pkgs.append(p.get_regression_info(project_owner, project_name))
×
391
            continue
1✔
392
        if p.latest.id == p.latest_succeeded.id:
1✔
393
            continue
1✔
394
        # latest is a successful build, but this doesn't mean it failed.
395
        # It could be in progress.
396
        if p.latest.state != "failed":
1✔
397
            continue
1✔
398
        if int(p.latest.submitted_on) < start_time.timestamp():
1✔
399
            continue
1✔
400
        pkgs.append(p.get_regression_info(project_owner, project_name))
1✔
401
    return pkgs
1✔
402

403

404
def get_chroot_results(
1✔
405
    pkgs: list[dict[str, Any] | None], copr_client: copr.v3.Client
406
) -> None:
407
    for p in pkgs:
×
408
        if p is None:
×
409
            continue
×
410
        p["failed_chroots"] = []
×
411
        for c in p["chroots"]:
×
412
            result = copr_client.build_chroot_proxy.get(p["fail_id"], c)
×
413
            if result["state"] == "failed":
×
414
                p["failed_chroots"].append(c)
×
415

416

417
def build_pkg(
1✔
418
    project_owner: str,
419
    project_name: str,
420
    copr_client: copr.v3.Client,
421
    pkg: str,
422
    default_commitish: str,
423
    build_tag: str,
424
    koji_server: str = "https://koji.fedoraproject.org/kojihub",
425
    distgit: str = "fedora",
426
    chroots: list[str] | None = None,
427
) -> None:
428

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

449
    copr_client.build_proxy.create_from_distgit(
×
450
        project_owner,
451
        project_name,
452
        pkg,
453
        commitish,
454
        buildopts=buildopts,
455
        distgit=distgit,
456
    )
457

458

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

478
    logging.info("Rebuilding", len(pkgs), "packages")
×
NEW
479
    rawhide_tag = get_rawhide_tag()
×
480
    for p in pkgs:
×
NEW
481
        build_pkg(
×
482
            project_owner,
483
            project_name,
484
            copr_client,
485
            p,
486
            default_commitish="rawhide",
487
            build_tag=rawhide_tag,
488
            chroots=chroots,
489
        )
490

491

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

511
            logging.info(project_name, chroots)
×
512
            if all(t in chroots for t in target_chroots):
×
513
                logging.info("PASS", project_name)
×
514
                return project_name
×
515
        except:  # noqa: E722
×
516
            continue
×
517
    logging.warning("FAIL")
×
518
    return None
×
519

520

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

548

549
def extract_date_from_project(project_name: str) -> datetime.date:
1✔
550
    m = re.search("[0-9]+$", project_name)
×
551
    if not m:
×
552
        raise Exception(f"Invalid project name: {project_name}")
×
553
    return datetime.datetime.fromisoformat(m.group(0)).date()
×
554

555

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

577
        increment = increment * -1
×
578
        if increment < 0:
×
579
            increment -= 1
×
580
        else:
581
            increment += 1
×
582
        mid_date += datetime.timedelta(days=increment)
×
583

584
    return good
×
585

586

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

590
    Args:
591
        ftbfs_data (list[dict[str, str]]): JSON packge data from
592
            https://koschei.fedoraproject.org/api/v1/packages
593
        pkg (str): The package to query.
594
        tag (str): The build tag to query.
595

596
    Returns:
597
        bool: True if this package is FTBFS, False otherwise.
598

599
    Example:
600

601
    >>> 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"}}]')
602
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f43")
603
    True
604
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f44")
605
    False
606
    >>> pkg_is_ftbfs(ftbfs_data, "llvm", "f44")
607
    False
608
    """
609

610
    for ftbfs_pkg in ftbfs_data:
1✔
611
        if ftbfs_pkg["name"] != pkg:
1✔
612
            continue
1✔
613
        if ftbfs_pkg["collection"] != tag:
1✔
614
            continue
1✔
615
        return ftbfs_pkg["state"] == "failing"
1✔
616
    return False
1✔
617

618

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

643
    args = parser.parse_args()
×
644
    copr_client = copr.v3.Client.create_from_config_file()
×
645

646
    os_name = "fedora-rawhide"
×
647
    target_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
×
648
    target_chroots = [f"{os_name}-{a}" for a in target_arches]
×
649
    project_owner = "@fedora-llvm-team"
×
650
    project_name = "clang-monthly-fedora-rebuild"
×
651

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

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

NEW
740
        copr_client.project_proxy.edit(
×
741
            project_owner,
742
            project_name,
743
            additional_repos=[
744
                "copr://@fedora-llvm-team/llvm-compat-packages",
745
            ],
746
        )
747

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

NEW
774
            copr_client.project_chroot_proxy.edit(
×
775
                project_owner,
776
                project_name,
777
                chroot,
778
                additional_repos=repos + [snapshot_url],
779
            )
780

NEW
781
        centos_stream9_chroots = [
×
782
            c for c in centos_stream9_chroots if c in target_chroots
783
        ]
NEW
784
        for pkg in centos9_pkgs:
×
NEW
785
            build_pkg(
×
786
                project_owner=project_owner,
787
                project_name=project_name,
788
                copr_client=copr_client,
789
                pkg=pkg,
790
                koji_server="https://kojihub.stream.centos.org/kojihub",
791
                default_commitish="c9s",
792
                build_tag="c9s-candidate",
793
                distgit="centos-stream",
794
                chroots=centos_stream9_chroots,
795
            )
796

NEW
797
        centos_stream10_chroots = [
×
798
            c for c in centos_stream10_chroots if c in target_chroots
799
        ]
NEW
800
        for pkg in centos10_pkgs:
×
NEW
801
            build_pkg(
×
802
                project_owner=project_owner,
803
                project_name=project_name,
804
                copr_client=copr_client,
805
                pkg=pkg,
806
                koji_server="https://kojihub.stream.centos.org/kojihub",
807
                default_commitish="c10s",
808
                build_tag="c10s-candidate",
809
                distgit="centos-stream",
810
                chroots=centos_stream10_chroots,
811
            )
812

NEW
813
        fedora_chroots = [c for c in fedora_chroots if c in target_chroots]
×
814

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

NEW
825
        rawhide_tag = get_rawhide_tag()
×
NEW
826
        for pkg in fedora_pkgs:
×
NEW
827
            if pkg_is_ftbfs(ftbfs_data, pkg, tag=rawhide_tag):
×
NEW
828
                print(f"Skip building {pkg} on rawhide, because it is FTBFS")
×
NEW
829
                continue
×
NEW
830
            print(f"Building {pkg}")
×
NEW
831
            build_pkg(
×
832
                project_owner=project_owner,
833
                project_name=project_name,
834
                copr_client=copr_client,
835
                pkg=pkg,
836
                default_commitish="rawhide",
837
                build_tag=rawhide_tag,
838
                chroots=fedora_chroots,
839
            )
840

841

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