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

fedora-llvm-team / llvm-snapshots / 21187325246

20 Jan 2026 09:09PM UTC coverage: 54.82% (-1.3%) from 56.137%
21187325246

Pull #1831

github

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

28 of 110 new or added lines in 1 file covered. (25.45%)

1 existing line in 1 file now uncovered.

1359 of 2479 relevant lines covered (54.82%)

0.55 hits per line

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

32.95
/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✔
NEW
102
    base = dnf.Base()
×
NEW
103
    conf = base.conf
×
104

NEW
105
    if version == "rawhide":
×
NEW
106
        base.repos.add_new_repo(
×
107
            f"{version}-source",
108
            conf,
109
            baseurl=[
110
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/development/{version}/Everything/source/tree/"
111
            ],
112
        )
113
    else:
NEW
114
        base.repos.add_new_repo(
×
115
            f"{version}-source",
116
            conf,
117
            baseurl=[
118
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/releases/{version}/Everything/source/tree/"
119
            ],
120
        )
NEW
121
        base.repos.add_new_repo(
×
122
            f"{version}-updates-source",
123
            conf,
124
            baseurl=[
125
                f"https://download-ib01.fedoraproject.org/pub/fedora/linux/updates/{version}/Everything/source/tree/"
126
            ],
127
        )
128

NEW
129
    repos = base.repos.get_matching("*")
×
NEW
130
    repos.disable()
×
NEW
131
    repos = base.repos.get_matching("*-source*")
×
NEW
132
    repos.enable()
×
133

NEW
134
    base.fill_sack()
×
NEW
135
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
NEW
136
    q = q.available()
×
NEW
137
    q = q.filter(requires=["clang"])
×
NEW
138
    pkgs = [p.name for p in list(q)]
×
NEW
139
    return filter_llvm_pkgs(set(pkgs))
×
140

141

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

146
    def is_in_progress(self) -> bool:
1✔
147
        return self.state not in [
1✔
148
            "succeeded",
149
            "forked",
150
            "skipped",
151
            "failed",
152
            "canceled",
153
        ]
154

155

156
# In order to remove the type: ignore[misc] check for this ticket: see https://github.com/Infinidat/munch/issues/84
157
class CoprPkg(Munch):  # type: ignore[misc]
1✔
158
    @classmethod
1✔
159
    def get_packages_from_copr(
1✔
160
        cls, project_owner: str, project_name: str, copr_client: copr.v3.Client
161
    ) -> list["CoprPkg"]:
162
        return [
×
163
            CoprPkg(p)
164
            for p in copr_client.package_proxy.get_list(
165
                project_owner,
166
                project_name,
167
                with_latest_succeeded_build=True,
168
                with_latest_build=True,
169
            )
170
        ]
171

172
    def get_build(self, name: str) -> CoprBuild | None:
1✔
173
        if "builds" not in self:
1✔
174
            return None
×
175
        if name not in self.builds:
1✔
176
            return None
×
177
        build = self.builds[name]
1✔
178
        if not build:
1✔
179
            return None
1✔
180
        return CoprBuild(build)
1✔
181

182
    def get_regression_info(
1✔
183
        self, project_owner: str, project_name: str
184
    ) -> dict[str, Any] | None:
185
        owner_url = project_owner
1✔
186
        if owner_url[0] == "@":
1✔
187
            owner_url = f"g/{owner_url[1:]}"
1✔
188
        latest = self.latest
1✔
189
        if latest is not None:
1✔
190
            return {
1✔
191
                "name": self.name,
192
                "fail_id": latest.id,
193
                "url": f"https://copr.fedorainfracloud.org/coprs/{owner_url}/{project_name}/build/{latest.id}/",
194
                "chroots": latest.chroots,
195
            }
196
        return None
×
197

198
    @property
1✔
199
    def latest(self) -> CoprBuild | None:
1✔
200
        return self.get_build("latest")
1✔
201

202
    @property
1✔
203
    def latest_succeeded(self) -> CoprBuild | None:
1✔
204
        return self.get_build("latest_succeeded")
1✔
205

206

207
def load_tests(
1✔
208
    loader: unittest.TestLoader, standard_tests: unittest.TestSuite, pattern: str
209
) -> unittest.TestSuite:
210
    """We want unittest to pick up all of our doctests
211

212
    See https://docs.python.org/3/library/unittest.html#load-tests-protocol
213
    See https://stackoverflow.com/a/27171468
214
    """
215
    import doctest
×
216

217
    standard_tests.addTests(doctest.DocTestSuite())
×
218
    return standard_tests
×
219

220

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

224
    Args:
225
        pkgs (set[str]): List of package names
226

227
    Returns:
228
        set[str]: List of package names without LLVM packages
229

230
    Example:
231

232
    >>> pkgs={'firefox', 'llvm99', 'libreoffice', 'clang18', 'compiler-rt'}
233
    >>> filtered=list(filter_llvm_pkgs(pkgs))
234
    >>> filtered.sort()
235
    >>> print(filtered)
236
    ['firefox', 'libreoffice']
237

238
    """
239
    llvm_pkgs = {
1✔
240
        "llvm",
241
        "clang",
242
        "llvm-bolt",
243
        "libomp",
244
        "compiler-rt",
245
        "lld",
246
        "lldb",
247
        "polly",
248
        "libcxx",
249
        "libclc",
250
        "flang",
251
        "mlir",
252
    }
253
    llvm_pkg_pattern = rf"({'|'.join(llvm_pkgs)})[0-9]*$"
1✔
254
    return {pkg for pkg in pkgs if not re.match(llvm_pkg_pattern, pkg)}
1✔
255

256

257
def get_exclusions() -> set[str]:
1✔
258
    """
259
    This returns a list of packages we don't want to test.
260
    """
261
    return set()
×
262

263

264
def get_pkgs(exclusions: set[str]) -> set[str]:
1✔
265
    base = dnf.Base()
×
266
    conf = base.conf
×
267
    for c in "AppStream", "BaseOS", "CRB", "Extras":
×
268
        base.repos.add_new_repo(
×
269
            f"{c}-source",
270
            conf,
271
            baseurl=[
272
                f"https://odcs.fedoraproject.org/composes/production/latest-Fedora-ELN/compose/{c}/source/tree/"
273
            ],
274
        )
275
    repos = base.repos.get_matching("*")
×
276
    repos.disable()
×
277
    repos = base.repos.get_matching("*-source*")
×
278
    repos.enable()
×
279

280
    base.fill_sack()
×
281
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
282
    q = q.available()
×
283
    q = q.filter(requires=["clang", "gcc", "gcc-c++"])
×
284
    pkgs = [p.name for p in list(q)]
×
285
    return filter_llvm_pkgs(set(pkgs)) - exclusions
×
286

287

288
def get_monthly_rebuild_packages(pkgs: set[str], copr_pkgs: list[CoprPkg]) -> set[str]:
1✔
289
    """Returns the list of packages that should be built in the next rebuild.
290
        It will select all the packages that built successfully during the last
291
        rebuild.
292

293
    Args:
294
        pkgs (set[str]): A list of every package that should be considered for
295
                        the rebuild.
296
        copr_pkgs (list[dist]): A list containing the latest build results from
297
                                the COPR project.
298

299
    Returns:
300
        set[str]: List of packages that should be rebuilt.
301

302
    Example:
303

304
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : { "id" : 1 } } }
305
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : None } }
306
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 1 } } }
307
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 2 } } }
308
    >>> pkgs = { "b", "c", "d"}
309
    >>> copr_pkgs = [CoprPkg(p) for p in [a, b, c, d]]
310
    >>> rebuild_pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
311
    >>> print(rebuild_pkgs)
312
    {'d'}
313
    """
314

315
    for p in copr_pkgs:
1✔
316
        if p.name not in pkgs:
1✔
317
            continue
1✔
318
        # Always build tier0 packges.
319
        if is_tier0_package(p.name):
1✔
320
            continue
×
321
        if not p.latest_succeeded:
1✔
322
            pkgs.discard(p.name)
1✔
323
            continue
1✔
324
        if p.latest is not None and p.latest.id != p.latest_succeeded.id:
1✔
325
            pkgs.discard(p.name)
1✔
326
    return pkgs
1✔
327

328

329
def get_monthly_rebuild_regressions(
1✔
330
    project_owner: str,
331
    project_name: str,
332
    start_time: datetime.datetime,
333
    copr_pkgs: list[CoprPkg],
334
) -> list[dict[str, Any] | None]:
335
    """Returns the list of packages that failed to build in the most recent
336
       rebuild, but built successfully in the previous rebuild.
337

338
    Args:
339
        start_time (datetime.datetime): The start time of the most recent mass
340
                                        rebuild.  This needs to be a time
341
                                        before the most recent mass rebuild
342
                                        and after the previous one.
343
        copr_pkgs (list[dict]): List of built packages for the COPR project.
344

345
    Returns:
346
        set[str]: List of packages that regressed in the most recent rebuilt.
347

348
    Example:
349

350
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1, "state" : "running", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
351
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
352
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
353
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2, "state" : "canceled", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
354
    >>> e = {"name" : "e", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
355
    >>> f = {"name" : "f", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1731457321, "chroots" : ["x86_64", "ppc64le", "s390x", "aarch64"] } , "latest_succeeded" : { "id" : 1 } } }
356
    >>> copr_pkgs= [CoprPkg(p) for p in [ a, b, c, d, e, f ]]
357
    >>> project_owner = "@fedora-llvm-team"
358
    >>> project_name = "fedora41-clang-20"
359
    >>> regressions = get_monthly_rebuild_regressions(project_owner, project_name, datetime.datetime.fromisoformat("2024-11-11"), copr_pkgs)
360
    >>> print(regressions)
361
    [{'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']}]
362

363
    """
364
    pkgs = []
1✔
365
    for p in copr_pkgs:
1✔
366
        if not p.latest:
1✔
367
            continue
×
368

369
        # Don't report regressions if there are still builds in progress
370
        if p.latest.is_in_progress():
1✔
371
            continue
1✔
372

373
        if not p.latest_succeeded:
1✔
374
            if is_tier0_package(p.name):
1✔
375
                pkgs.append(p.get_regression_info(project_owner, project_name))
×
376
            continue
1✔
377
        if p.latest.id == p.latest_succeeded.id:
1✔
378
            continue
1✔
379
        # latest is a successful build, but this doesn't mean it failed.
380
        # It could be in progress.
381
        if p.latest.state != "failed":
1✔
382
            continue
1✔
383
        if int(p.latest.submitted_on) < start_time.timestamp():
1✔
384
            continue
1✔
385
        pkgs.append(p.get_regression_info(project_owner, project_name))
1✔
386
    return pkgs
1✔
387

388

389
def get_chroot_results(
1✔
390
    pkgs: list[dict[str, Any] | None], copr_client: copr.v3.Client
391
) -> None:
392
    for p in pkgs:
×
393
        if p is None:
×
394
            continue
×
395
        p["failed_chroots"] = []
×
396
        for c in p["chroots"]:
×
397
            result = copr_client.build_chroot_proxy.get(p["fail_id"], c)
×
398
            if result["state"] == "failed":
×
399
                p["failed_chroots"].append(c)
×
400

401

402
def build_pkg(
1✔
403
    project_owner: str,
404
    project_name: str,
405
    copr_client: copr.v3.Client,
406
    pkg: str,
407
    default_commitish: str,
408
    build_tag: str,
409
    koji_server: str = "https://koji.fedoraproject.org/kojihub",
410
    distgit: str = "fedora",
411
    chroots: list[str] | None = None,
412
) -> None:
413

NEW
414
    buildopts = {
×
415
        "background": True,
416
        "chroots": chroots,
417
        # Increase default timeout because some packages take longer than 5
418
        # hours.  This is easier to do globally tahn to maintain a list of
419
        # long building packages and I don't think there is any downside to
420
        # having a longer default timeout.
421
        "timeout": 90000,
422
    }
423
    koji_session = koji.ClientSession(koji_server)
×
424
    try:
×
425
        build = koji_session.getLatestBuilds(tag=build_tag, package=pkg)[0]
×
426
        build_info = koji_session.getBuild(build["build_id"])
×
427
        commitish = build_info["source"].split("#")[1]
×
428
    except:  # noqa: E722
×
429
        logging.warn(
×
430
            "Could not determine git commit for latest build of {p}.  Defaulting to {default_commitish}."
431
        )
432
        commitish = default_commitish
×
433

434
    copr_client.build_proxy.create_from_distgit(
×
435
        project_owner,
436
        project_name,
437
        pkg,
438
        commitish,
439
        buildopts=buildopts,
440
        distgit=distgit,
441
    )
442

443

444
def start_rebuild(
1✔
445
    project_owner: str,
446
    project_name: str,
447
    copr_client: copr.v3.Client,
448
    pkgs: set[str],
449
    snapshot_project_name: str,
450
    chroots: list[str],
451
) -> None:
452
    print("START", pkgs, "END")
×
453
    # Update the rebuild project to use the latest snapshot
454
    copr_client.project_proxy.edit(
×
455
        project_owner,
456
        project_name,
457
        additional_repos=[
458
            "copr://tstellar/fedora-clang-default-cc",
459
            f"copr://@fedora-llvm-team/{snapshot_project_name}",
460
        ],
461
    )
462

463
    logging.info("Rebuilding", len(pkgs), "packages")
×
NEW
464
    rawhide_tag = get_rawhide_tag()
×
465
    for p in pkgs:
×
NEW
466
        build_pkg(
×
467
            project_owner,
468
            project_name,
469
            copr_client,
470
            p,
471
            default_commitish="rawhide",
472
            build_tag=rawhide_tag,
473
            chroots=chroots,
474
        )
475

476

477
def select_snapshot_project(
1✔
478
    copr_client: copr.v3.Client, target_chroots: list[str], max_lookback_days: int = 14
479
) -> str | None:
480
    project_owner = "@fedora-llvm-team"
×
481
    for i in range(max_lookback_days):
×
482
        chroots = set()
×
483
        day = datetime.date.today() - datetime.timedelta(days=i)
×
484
        project_name = day.strftime("llvm-snapshots-big-merge-%Y%m%d")
×
485
        logging.info("Trying:", project_name)
×
486
        try:
×
487
            p = copr_client.project_proxy.get(project_owner, project_name)
×
488
            if not p:
×
489
                continue
×
490
            pkgs = copr_client.build_proxy.get_list(
×
491
                project_owner, project_name, "llvm", status="succeeded"
492
            )
493
            for pkg in pkgs:
×
494
                chroots.update(pkg["chroots"])
×
495

496
            logging.info(project_name, chroots)
×
497
            if all(t in chroots for t in target_chroots):
×
498
                logging.info("PASS", project_name)
×
499
                return project_name
×
500
        except:  # noqa: E722
×
501
            continue
×
502
    logging.warning("FAIL")
×
503
    return None
×
504

505

506
def create_new_project(
1✔
507
    project_owner: str,
508
    project_name: str,
509
    copr_client: copr.v3.Client,
510
    target_chroots: list[str],
511
    additional_packages: list[str] | None = ["fedora-clang-default-cc"],
512
    with_opts: list[str] | None = ["toolchain_clang", "clang_lto"],
513
) -> None:
514
    copr_client.project_proxy.add(project_owner, project_name, chroots=target_chroots)
×
515
    for c in target_chroots:
×
NEW
516
        if c.startswith("centos-stream"):
×
NEW
517
            centos_version = c.split("-")[2]
×
NEW
518
            arch = c.split("-")[3]
×
519
            # Add centos stream buildroot, because not all packages in the
520
            # buildroot are shipped in the CRB.
NEW
521
            additional_repos = [
×
522
                f"https://kojihub.stream.centos.org/kojifiles/repos/c{centos_version}s-build/latest/{arch}/"
523
            ]
UNCOV
524
        copr_client.project_chroot_proxy.edit(
×
525
            project_owner,
526
            project_name,
527
            c,
528
            additional_packages=additional_packages,
529
            with_opts=with_opts,
530
            additional_repos=additional_repos,
531
        )
532

533

534
def extract_date_from_project(project_name: str) -> datetime.date:
1✔
535
    m = re.search("[0-9]+$", project_name)
×
536
    if not m:
×
537
        raise Exception(f"Invalid project name: {project_name}")
×
538
    return datetime.datetime.fromisoformat(m.group(0)).date()
×
539

540

541
def find_midpoint_project(
1✔
542
    copr_client: copr.v3.Client, good: str, bad: str, chroot: str
543
) -> str:
544
    good_date = extract_date_from_project(good)
×
545
    bad_date = extract_date_from_project(bad)
×
546
    days = (bad_date - good_date).days
×
547
    mid_date = good_date + datetime.timedelta(days=days / 2)
×
548
    increment = 0
×
549
    while mid_date != good_date and mid_date != bad_date:
×
550
        mid_project = re.sub("[0-9]+$", mid_date.strftime("%Y%m%d"), good)
×
551
        owner = mid_project.split("/")[0]
×
552
        project = mid_project.split("/")[1]
×
553
        try:
×
554
            for builds in copr_client.build_proxy.get_list(
×
555
                owner, project, "llvm", "succeeded"
556
            ):
557
                if chroot in builds["chroots"]:
×
558
                    return mid_project
×
559
        except:  # noqa: E722
×
560
            pass
×
561

562
        increment = increment * -1
×
563
        if increment < 0:
×
564
            increment -= 1
×
565
        else:
566
            increment += 1
×
567
        mid_date += datetime.timedelta(days=increment)
×
568

569
    return good
×
570

571

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

575
    Args:
576
        ftbfs_data (list[dict[str, str]]): JSON packge data from
577
            https://koschei.fedoraproject.org/api/v1/packages
578
        pkg (str): The package to query.
579
        tag (str): The build tag to query.
580

581
    Returns:
582
        bool: True if this package is FTBFS, False otherwise.
583

584
    Example:
585

586
    >>> 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"}}]')
587
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f43")
588
    True
589
    >>> pkg_is_ftbfs(ftbfs_data, "0ad", "f44")
590
    False
591
    >>> pkg_is_ftbfs(ftbfs_data, "llvm", "f44")
592
    False
593
    """
594

595
    for ftbfs_pkg in ftbfs_data:
1✔
596
        if ftbfs_pkg["name"] != pkg:
1✔
597
            continue
1✔
598
        if ftbfs_pkg["collection"] != tag:
1✔
599
            continue
1✔
600
        return ftbfs_pkg["state"] == "failing"
1✔
601
    return False
1✔
602

603

604
def main() -> None:
1✔
605
    logging.basicConfig(filename="rebuilder.log", level=logging.INFO)
×
606
    parser = argparse.ArgumentParser()
×
607
    parser.add_argument(
×
608
        "command",
609
        type=str,
610
        choices=[
611
            "rebuild",
612
            "get-regressions",
613
            "get-snapshot-date",
614
            "rebuild-in-progress",
615
            "bisect",
616
            "test",
617
        ],
618
    )
619
    parser.add_argument(
×
620
        "--start-date", type=str, help="Any ISO date format is accepted"
621
    )
622
    parser.add_argument("--chroot", type=str)
×
623
    parser.add_argument("--good", type=str)
×
624
    parser.add_argument("--bad", type=str)
×
NEW
625
    parser.add_argument("--llvm-major", type=int)
×
NEW
626
    parser.add_argument("--skip-same-version", action="store_true")
×
627

628
    args = parser.parse_args()
×
629
    copr_client = copr.v3.Client.create_from_config_file()
×
630

631
    os_name = "fedora-rawhide"
×
632
    target_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
×
633
    target_chroots = [f"{os_name}-{a}" for a in target_arches]
×
634
    project_owner = "@fedora-llvm-team"
×
635
    project_name = "clang-monthly-fedora-rebuild"
×
636

637
    if args.command == "rebuild":
×
638
        exclusions = get_exclusions()
×
639
        pkgs = get_pkgs(exclusions)
×
640
        print(pkgs)
×
641
        try:
×
642
            copr_client.project_proxy.get(project_owner, project_name)
×
643
            copr_pkgs = CoprPkg.get_packages_from_copr(
×
644
                project_owner, project_name, copr_client
645
            )
646
            pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
×
647
        except:  # noqa: E722
×
648
            create_new_project(project_owner, project_name, copr_client, target_chroots)
×
649
        snapshot_project = select_snapshot_project(copr_client, target_chroots)
×
650
        if snapshot_project is not None:
×
651
            start_rebuild(
×
652
                project_owner,
653
                project_name,
654
                copr_client,
655
                pkgs,
656
                snapshot_project,
657
                target_chroots,
658
            )
659
    elif args.command == "get-regressions":
×
660
        start_time = datetime.datetime.fromisoformat(args.start_date)
×
661
        copr_pkgs = CoprPkg.get_packages_from_copr(
×
662
            project_owner, project_name, copr_client
663
        )
664
        pkg_failures = get_monthly_rebuild_regressions(
×
665
            project_owner, project_name, start_time, copr_pkgs
666
        )
667
        get_chroot_results(list(pkg_failures), copr_client)
×
668
        # Delete attributes we don't need to print
669
        for p in pkg_failures:
×
670
            if p is None:
×
671
                continue
×
672
            for k in ["fail_id", "chroots"]:
×
673
                del p[k]
×
674

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

NEW
725
        copr_client.project_proxy.edit(
×
726
            project_owner,
727
            project_name,
728
            additional_repos=[
729
                "copr://@fedora-llvm-team/llvm-compat-packages",
730
            ],
731
        )
732

733
        # Iterate over a copy of a list so we can remove items:
NEW
734
        for chroot in list(target_chroots):
×
NEW
735
            snapshot_project_name = select_snapshot_project(copr_client, [chroot])
×
NEW
736
            if not snapshot_project_name:
×
NEW
737
                print(f"Could not find snapshot for {chroot}")
×
NEW
738
                target_chroots.remove(chroot)
×
NEW
739
                continue
×
740
            else:
NEW
741
                print(f"Using {snapshot_project_name} for {chroot}")
×
NEW
742
            snapshot_url = f"copr://@fedora-llvm-team/{snapshot_project_name}"
×
NEW
743
            repos = []
×
NEW
744
            for r in copr_client.project_chroot_proxy.get(
×
745
                project_owner, project_name, chroot
746
            )["additional_repos"]:
NEW
747
                if args.skip_same_version and r == snapshot_url:
×
NEW
748
                    print(
×
749
                        f"Not building for {chroot} since snapshot version is the same as the last build"
750
                    )
NEW
751
                    target_chroots.remove(chroot)
×
NEW
752
                if not r.startswith(
×
753
                    "copr://@fedora-llvm-team/llvm-snapshots-big-merge"
754
                ):
NEW
755
                    repos.append(r)
×
NEW
756
            if chroot not in target_chroots:
×
NEW
757
                continue
×
758

NEW
759
            copr_client.project_chroot_proxy.edit(
×
760
                project_owner,
761
                project_name,
762
                chroot,
763
                additional_repos=repos + [snapshot_url],
764
            )
765

NEW
766
        centos_stream9_chroots = [
×
767
            c for c in centos_stream9_chroots if c in target_chroots
768
        ]
NEW
769
        for pkg in centos9_pkgs:
×
NEW
770
            build_pkg(
×
771
                project_owner=project_owner,
772
                project_name=project_name,
773
                copr_client=copr_client,
774
                pkg=pkg,
775
                koji_server="https://kojihub.stream.centos.org/kojihub",
776
                default_commitish="c9s",
777
                build_tag="c9s-candidate",
778
                distgit="centos-stream",
779
                chroots=centos_stream9_chroots,
780
            )
781

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

NEW
798
        fedora_chroots = [c for c in fedora_chroots if c in target_chroots]
×
799

800
        # Load FTBFS data so we can skip building packages that currently don't build.
NEW
801
        request = urllib.request.Request(
×
802
            "https://koschei.fedoraproject.org/api/v1/packages"
803
        )
804
        # We need to set these headers due to new anti-spam measures in Fedora infrastructure.
NEW
805
        request.add_header("Accept", "text/plain")
×
NEW
806
        request.add_header("User-Agent", "fedora-llvm-team/1.0")
×
NEW
807
        with urllib.request.urlopen(request) as url:
×
NEW
808
            ftbfs_data = json.loads(url.read().decode())
×
809

NEW
810
        rawhide_tag = get_rawhide_tag()
×
NEW
811
        for pkg in fedora_pkgs:
×
NEW
812
            if pkg_is_ftbfs(ftbfs_data, pkg, tag=rawhide_tag):
×
NEW
813
                print(f"Skip building {pkg} on rawhide, because it is FTBFS")
×
NEW
814
                continue
×
NEW
815
            print(f"Building {pkg}")
×
NEW
816
            build_pkg(
×
817
                project_owner=project_owner,
818
                project_name=project_name,
819
                copr_client=copr_client,
820
                pkg=pkg,
821
                default_commitish="rawhide",
822
                build_tag=rawhide_tag,
823
                chroots=fedora_chroots,
824
            )
825

826

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