Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 190 additions & 113 deletions .github/workflows/build-distributed.yml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ When adding or updating packages, adhere to the following priority list:
ICU 77 is bundled with packages that require it (e.g., `mozjs140`, `tinysparql`) instead of using a standalone package. This prevents "repo poisoning" and conflicts with EL10's base ICU 74.

## Current Projects
* **`jreilly1821/c10s-gnome-50-fresh`**: GNOME 50 development.
* **`jreilly1821/c10s-gnome-50`**: GNOME 50 development.
* **`jreilly1821/c10s-gnome-49`**: GNOME 49 development (forked from GNOME 50).

## Current Status: COPR Build Cycle 1 (2026-03-14)
Expand All @@ -23,7 +23,7 @@ ICU 77 is bundled with packages that require it (e.g., `mozjs140`, `tinysparql`)
* **Secondary Repo (`icu77-el10`)**:
* `meson`, `autoconf`, `python-smartypants`, `python-typogrify` (Build tools).
* `wayland-protocols`, `shaderc`, `gi-docgen`, `icu`.
* **Main Repo (`c10s-gnome-50-fresh`)**:
* **Main Repo (`c10s-gnome-50`)**:
* `libldac`, `gnome50-el10-compat`, `selinux-policy`, `libei`.
* `gobject-introspection` (Bootstrap variant).

Expand Down
61 changes: 57 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,28 @@ just rsync-test root@<ip> # Sync local-repo/ to remote and test upgrade

The main build script (used by CI):
```bash
./scripts/build-chain.sh --help # Tiered build orchestration
./scripts/build-chain.sh --help # Tiered build orchestration (local/CI, podman/mock/native)
```

Triggering COPR builds in tier order (e.g. to fill a new chroot):
```bash
# Dry run — shows what would be triggered without submitting
python3 scripts/copr-build-chain.py --dry-run \
--chroot epel-10-aarch64 \
--chroot "alma-kitten+epel-10-x86_64_v2"

# Live run — submits builds tier-by-tier, waits between tiers
python3 scripts/copr-build-chain.py \
--chroot epel-10-aarch64 \
--chroot "alma-kitten+epel-10-x86_64_v2"

# Single tier (useful for re-running a failed tier)
python3 scripts/copr-build-chain.py --tier glib2-bootstrap \
--chroot epel-10-aarch64

# Don't stop on failure (submit all tiers regardless)
python3 scripts/copr-build-chain.py --continue-on-error \
--chroot epel-10-aarch64
```

## Architecture
Expand All @@ -47,25 +68,57 @@ GitHub Actions → build-chain.sh (reads build-order.yml)

CI seeds the local repo from R2 at the start, adds new builds, re-signs, and pushes back—incremental updates only.

#### COPR Bootstrap Chain

Adding a new chroot (e.g. `epel-10-aarch64`) requires a bootstrap chain because glib2 and gobject-introspection have a circular build dependency:

```
glib2 (full) needs gobject-introspection-devel (for GI annotations)
gobject-introspection needs glib2
```

The COPR additional repos are x86_64-only, so aarch64/x86_64_v2 can't get gobject-introspection from there. `copr-build-chain.py` handles this automatically:

1. Tiers 0–2: base tools (meson, autoconf, harfbuzz, …)
2. **Tier 3 `glib2-bootstrap`**: creates COPR package `glib2-bootstrap` from `glib2-bootstrap.spec` (no GI dep) — separate from the production `glib2` package
3. Tiers 4–5: bootstrap-libs + cairo
4. **Tier 6 `gi-bootstrap`**: creates COPR package `gobject-introspection-bootstrap` from `gobject-introspection-bootstrap.spec` — now g-ir-scanner exists in the buildroot
5. **Tier 7 `glib2-full`**: production `glib2` package now builds (GI available)
6. **Tier 8 `gi-full`**: production `gobject-introspection` package rebuilt against full glib2
7. Tiers 9–15: full desktop stack

The bootstrap packages (`glib2-bootstrap`, `gobject-introspection-bootstrap`) are COPR-package-name aliases — they produce the same RPM names but live as separate entries so the production package definitions are never clobbered.

### Key Files

| File | Purpose |
|------|---------|
| `build-order.yml` | Single source of truth: defines ~80 packages across 13+ dependency tiers |
| `scripts/build-chain.sh` | Main build engine; parses build-order.yml; supports podman/mock/native backends |
| `scripts/copr-build-chain.py` | Triggers COPR builds tier-by-tier for one or more chroots; handles bootstrap chain |
| `justfile` | Local convenience wrappers (requires `just`) |
| `.github/workflows/build.yml` | Primary CI/CD pipeline |
| `.github/workflows/build-distributed.yml` | Auto-generated per-tier parallel workflow |
| `scripts/generate-distributed-workflow.py` | Regenerates build-distributed.yml from build-order.yml |
| `workers/repo-proxy.ts` | Cloudflare Worker for custom domain routing |
| `contrib/install.sh` | User-facing install script (detects distro/version) |

### Package Sources
## Package Sources and Priorities

When adding or updating packages, adhere to the following priority list:
1. **Fedora Rawhide Dist-Git** (`just copr-build <name>`): Use for unmodified packages.
2. **GitHub SCM** (`just copr-scm-build <path>`): Use for modified specs. This is the **preferred method for modified packages** as it tracks changes in this repository.
3. **Local SRPM** (`just copr-srpm-build <path>`): Use only as a last resort.

### GNOME 50 Package Strategy
- `src/gnome-50/` — GNOME 50 packages (glib2, gtk4, mutter, gnome-shell, gdm, etc.)
- `src/deps/` — Build dependencies not in EL10 repos (meson, mozjs140, pipewire, cairo, etc.)

Each package directory contains a `.spec` file and sources. Some packages have bootstrap variants (e.g., `glib2-bootstrap.spec`, `gobject-introspection-bootstrap.spec`) to break circular dependencies—these build first without certain features, then the full build follows.
## ICU 77 Build-Only Strategy
ICU 77 is a required build-time dependency for several GNOME 50 components but is "poisonous" to the main user repository.
- **Secondary COPR**: `jreilly1821/icu77-el10` contains ICU 77 and its specific build requirements (e.g., `autoconf` 2.72).
- **Configuration**: This repo is added as an **Additional repo** to the main `c10s-gnome-50` project for build-time resolution only.
- **Isolation**: ICU 77 packages should **NOT** be built in or added to the main project's package list.

### Build Targets

Expand Down Expand Up @@ -128,7 +181,7 @@ Test installs from COPR using a throwaway container — do not use `--skip-broke
```bash
podman run --rm quay.io/centos/centos:stream10 bash -c "
dnf -y install dnf-plugins-core &&
dnf copr enable -y jreilly1821/c10s-gnome-50-fresh &&
dnf copr enable -y jreilly1821/c10s-gnome-50 &&
dnf -y install <packages>
"
```
Expand Down
2 changes: 1 addition & 1 deletion COPR-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ These directories exist in `src/` but the corresponding COPR package is already

### Medium Priority

- [ ] **Switch libgexiv2 from upload to distgit**: The spec is nearly identical to rawhide. Run `copr-cli modify-package --source-type distgit --distgit fedora --committish rawhide jreilly1821/c10s-gnome-50-fresh libgexiv2` (but confirm package name is `libgexiv2` not `gexiv2` in Fedora dist-git).
- [ ] **Switch libgexiv2 from upload to distgit**: The spec is nearly identical to rawhide. Run `copr-cli modify-package --source-type distgit --distgit fedora --committish rawhide jreilly1821/c10s-gnome-50 libgexiv2` (but confirm package name is `libgexiv2` not `gexiv2` in Fedora dist-git).

- [ ] **Evaluate mozjs140 release bump**: Our local spec uses `-b4` vs rawhide's `-b3` to ensure version precedence. Document why or switch to a simple `Epoch: 1` approach if needed.

Expand Down
4 changes: 2 additions & 2 deletions COPR-REPORT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# GNOME 50 COPR Project Report: `jreilly1821/c10s-gnome-50-fresh`
# GNOME 50 COPR Project Report: `jreilly1821/c10s-gnome-50`

This report details the origin and status of all packages currently tracked in the COPR project.

Expand Down Expand Up @@ -68,6 +68,6 @@ This report details the origin and status of all packages currently tracked in t
* `wayland-protocols`

## Summary of Infrastructure
* **Project**: [jreilly1821/c10s-gnome-50-fresh](https://copr.fedorainfracloud.org/coprs/jreilly1821/c10s-gnome-50-fresh/)
* **Project**: [jreilly1821/c10s-gnome-50](https://copr.fedorainfracloud.org/coprs/jreilly1821/c10s-gnome-50/)
* **Chroot**: `epel-10-x86_64`
* **Strategy**: Use Rawhide `distgit` for speed and simplicity where possible; override with local SRPMs only for EL10-specific fixes or dependency resolution issues.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# GNOME 50 for CentOS Stream 10

RPM packages bringing GNOME 50 to CentOS Stream 10 (EL10), hosted on
[COPR](https://copr.fedorainfracloud.org/coprs/jreilly1821/c10s-gnome-50-fresh/).
[COPR](https://copr.fedorainfracloud.org/coprs/jreilly1821/c10s-gnome-50/).

Packages are built in COPR (`jreilly1821/c10s-gnome-50-fresh`) targeting `epel-10-x86_64`.
Packages are built in COPR (`jreilly1821/c10s-gnome-50`) targeting `epel-10-x86_64`.
Most packages build directly from Fedora Rawhide dist-git. A small set require local spec
modifications to work on EL10 — those are documented below.

## Quick Install

```bash
dnf -y install dnf-plugins-core
dnf copr enable -y jreilly1821/c10s-gnome-50-fresh
dnf copr enable -y jreilly1821/c10s-gnome-50
dnf -y install gnome-shell gdm mutter gnome-session nautilus gnome50-el10-compat
```

Expand Down
92 changes: 92 additions & 0 deletions check_tiers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import yaml
import subprocess
import json
import os

ARM_CHROOT = "epel-10-aarch64"
V2_CHROOT = "alma-kitten+epel-10-x86_64_v2"
PROJECT = "jreilly1821/c10s-gnome-50"

def get_pkg_name(path):
try:
# Find the .spec file in the path
specs = [f for f in os.listdir(path) if f.endswith('.spec') and 'bootstrap' not in f]
if not specs:
specs = [f for f in os.listdir(path) if f.endswith('.spec')]
if not specs:
return os.path.basename(path)
spec_path = os.path.join(path, specs[0])
name = subprocess.check_output(['rpmspec', '-q', '--qf', '%{name}\n', spec_path], text=True).splitlines()[0]
return name
except Exception:
return os.path.basename(path)

def get_status():
print("Fetching latest build statuses from COPR...")
res = subprocess.check_output(['copr-cli', 'monitor', PROJECT, '--fields', 'name,chroot,state', '--output-format', 'json'], text=True)
data = json.loads(res)

# latest_status[(pkg, chroot)] = state
# monitor output is ordered from newest to oldest usually, let's verify if that's true or just take the first one we see
status_map = {}
for entry in data:
key = (entry['name'], entry['chroot'])
if key not in status_map:
status_map[key] = entry['state']
return status_map

def main():
with open('build-order.yml') as f:
config = yaml.safe_load(f)

status_map = get_status()

for tier in config['tiers']:
print(f"Checking tier: {tier['name']}")
packages_to_build = {ARM_CHROOT: [], V2_CHROOT: []}
all_succeeded = True

tier_pkgs = []
for pkg_entry in tier['packages']:
path = pkg_entry['path']
name = get_pkg_name(path)
tier_pkgs.append(name)

for chroot in [ARM_CHROOT, V2_CHROOT]:
state = status_map.get((name, chroot))
if state != "succeeded":
print(f" [MISSING/FAILED] {name} in {chroot} (state: {state})")
packages_to_build[chroot].append((name, path))
all_succeeded = False
else:
# print(f" [OK] {name} in {chroot}")
pass

if not all_succeeded:
print(f"\nTier '{tier['name']}' is incomplete. Action needed.")

# Combine by package to avoid duplicate triggers if possible
pkg_map = {} # name -> {'path': path, 'chroots': []}
for chroot, pkgs in packages_to_build.items():
for name, path in pkgs:
if name not in pkg_map:
pkg_map[name] = {'path': path, 'chroots': []}
pkg_map[name]['chroots'].append(chroot)

for name, info in pkg_map.items():
chroots_str = " ".join([f"--chroot {c}" for c in info['chroots']])
# Special case: icu needs spec_override sometimes, but for now let's just trigger build-package if it exists in copr
# If it doesn't exist or needs special setup, we might need justfile commands
print(f"Triggering build for {name} in {info['chroots']}...")
cmd = f"copr-cli build-package {PROJECT} --name {name} {' '.join(['--chroot ' + c for c in info['chroots']])} --nowait"
# Use justfile command if possible for local paths
# But build-package is safer if it's already set up correctly in COPR
subprocess.run(cmd, shell=True)

print("\nTriggered all missing builds for this tier. Wait for them to finish.")
return

print("All tiers are complete!")

if __name__ == "__main__":
main()
38 changes: 34 additions & 4 deletions gnome50-test.yaml
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
vmType: qemu
arch: x86_64
cpus: 4
memory: 4GiB
disk: 20GiB
memory: 8GiB
disk: 40GiB

video:
display: "vnc"
vga: "std"

images:
- location: "https://cloud.centos.org/centos/10-stream/x86_64/images/CentOS-Stream-GenericCloud-x86_64-10-latest.x86_64.qcow2"
arch: x86_64

mounts: []
mounts:
- location: "~"
writable: true

ssh:
localPort: 0
Expand All @@ -18,5 +24,29 @@ provision:
- mode: system
script: |
#!/bin/bash
set -euxo pipefail

# Enable our COPR
dnf -y install dnf-plugins-core
dnf copr enable -y jreilly1821/c10s-gnome-50-fresh
dnf copr enable -y jreilly1821/c10s-gnome-50

# Install GNOME 50 core components
# We install these first to ensure our versions are picked up
dnf -y install \
gnome-shell \
gnome-control-center \
mutter \
gdm \
gnome-session-wayland-session \
gnome-terminal \
nautilus \
glib2

# Install a minimal graphical environment
dnf -y group install "BaseOS" "Fonts"

# Enable GDM and set graphical boot
systemctl enable gdm
systemctl set-default graphical.target

echo "GNOME 50 installation complete. Please reboot if necessary."
12 changes: 8 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ publish-static:
rclone --s3-no-check-bucket copyto contrib/install.sh "r2:${R2_BUCKET}/install.sh"

# Build a package in COPR from Fedora Rawhide dist-git (for unmodified packages)
copr-build package project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x86_64':
copr-build package project='jreilly1821/c10s-gnome-50' chroot='epel-10-x86_64':
#!/usr/bin/env bash
set -euo pipefail
copr-cli add-package-distgit {{project}} --name {{package}} --distgit fedora --commit rawhide 2>/dev/null || \
Expand All @@ -151,7 +151,7 @@ copr-build package project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x86

# Build a modified package in COPR from our git repo (preferred over copr-srpm-build)
# Usage: just copr-scm-build src/deps/gnome-autoar
copr-scm-build path project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x86_64':
copr-scm-build path project='jreilly1821/c10s-gnome-50' chroot='epel-10-x86_64':
#!/usr/bin/env bash
set -euo pipefail
SPEC=$(ls {{path}}/*.spec | grep -v bootstrap | head -n 1)
Expand Down Expand Up @@ -179,7 +179,7 @@ copr-scm-build path project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x8
copr-cli build-package {{project}} --name "$NAME" --chroot {{chroot}} --nowait

# Build a local package in COPR by generating an SRPM first (avoid — use copr-scm-build instead)
copr-srpm-build path project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x86_64':
copr-srpm-build path project='jreilly1821/c10s-gnome-50' chroot='epel-10-x86_64':
#!/usr/bin/env bash
set -euo pipefail
echo "WARNING: copr-srpm-build uploads a local SRPM. Prefer 'just copr-scm-build {{path}}' for modified specs."
Expand All @@ -198,9 +198,13 @@ copr-srpm-build path project='jreilly1821/c10s-gnome-50-fresh' chroot='epel-10-x
copr-cli build {{project}} $SRPM --chroot {{chroot}} --nowait

# Check status of builds in the COPR project
copr-status project='jreilly1821/c10s-gnome-50-fresh':
copr-status project='jreilly1821/c10s-gnome-50':
copr-cli list-builds {{project}} | head -n 20

# Watch the COPR project builds in real-time
watch-copr project='jreilly1821/c10s-gnome-49':
copr-cli monitor {{project}} --fields name,chroot,state,url_build_log

# Download and open logs for a specific build ID
copr-logs build_id:
#!/usr/bin/env bash
Expand Down
2 changes: 1 addition & 1 deletion scripts/copr-build-chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def wait_for_builds(build_ids, project, poll_interval=30):
def main():
parser = argparse.ArgumentParser(description="COPR Build Chain Engine")
parser.add_argument("--manifest", default="build-order.yml")
parser.add_argument("--project", default="jreilly1821/c10s-gnome-50-fresh")
parser.add_argument("--project", default="jreilly1821/c10s-gnome-50")
parser.add_argument("--chroot", action="append", dest="chroots",
default=[], help="Chroot(s) to build for (repeatable)")
parser.add_argument("--dry-run", action="store_true")
Expand Down
1 change: 1 addition & 0 deletions src/gnome-49/glib2/glib2.spec
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ BuildRequires: pkgconfig(zlib)
BuildRequires: python3-devel
BuildRequires: python3-setuptools
BuildRequires: /usr/bin/g-ir-scanner
BuildRequires: pkgconfig(gobject-introspection-1.0)
BuildRequires: /usr/bin/rst2man

# Dependencies for tests
Expand Down
Loading