From cf3c8cd005be8953131f1e9af9592a05b390f5c1 Mon Sep 17 00:00:00 2001 From: Dawson Date: Mon, 27 Apr 2026 10:45:48 -0400 Subject: [PATCH] Adding srpm build process --- .copr/Makefile | 21 ++ .gitea/workflows/build-v3-rpms.yml | 417 ++++++----------------- .github/workflows/build-v3-rpms.yml | 249 +++++--------- .gitignore | 305 ----------------- README.md | 76 ++++- ci/copr-distgit-make-srpm.py | 244 +++++++++++++ ci/fedora-rpm-builder.Containerfile | 10 +- ci/sync-copr-packages.py | 192 +++++++++++ packaging/copr-rpm-macros-x86-64-v3.spec | 29 ++ 9 files changed, 737 insertions(+), 806 deletions(-) create mode 100644 .copr/Makefile create mode 100644 ci/copr-distgit-make-srpm.py create mode 100644 ci/sync-copr-packages.py create mode 100644 packaging/copr-rpm-macros-x86-64-v3.spec diff --git a/.copr/Makefile b/.copr/Makefile new file mode 100644 index 0000000..e8ce2f0 --- /dev/null +++ b/.copr/Makefile @@ -0,0 +1,21 @@ +FEDORA_BRANCH ?= f43 +FEDORA_DIST ?= .fc43 +FEDORA_NAMESPACE ?= rpms +LOOKASIDE_BASEURL ?= https://src.fedoraproject.org/repo/pkgs +PYTHON ?= python3 +REPO_ROOT := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))/..) +SPEC_REF := $(strip $(if $(spec),$(spec),$(COPR_PACKAGE))) + +.PHONY: srpm + +srpm: + test -n "$(SPEC_REF)" + test -n "$(outdir)" + mkdir -p "$(outdir)" + cd "$(REPO_ROOT)" && "$(PYTHON)" ci/copr-distgit-make-srpm.py \ + --spec-ref "$(SPEC_REF)" \ + --outdir "$(outdir)" \ + --branch "$(FEDORA_BRANCH)" \ + --dist "$(FEDORA_DIST)" \ + --namespace "$(FEDORA_NAMESPACE)" \ + --lookaside-baseurl "$(LOOKASIDE_BASEURL)" diff --git a/.gitea/workflows/build-v3-rpms.yml b/.gitea/workflows/build-v3-rpms.yml index 4766955..21c4c10 100644 --- a/.gitea/workflows/build-v3-rpms.yml +++ b/.gitea/workflows/build-v3-rpms.yml @@ -1,359 +1,136 @@ -name: Build Fedora x86_64-v3 RPMs (Gitea) +name: Validate Fedora x86_64-v3 Copr SRPMs (Gitea) on: workflow_dispatch: + inputs: + package_input: + description: "Optional comma, space, or newline separated package list. Leave blank to use packages.txt." + required: false + default: "" + type: string push: paths: - - 'packages.txt' - - 'ci/assemble-dnf-repo.py' + - '.copr/Makefile' - '.gitea/workflows/build-v3-rpms.yml' + - 'ci/copr-distgit-make-srpm.py' + - 'packages.txt' + - 'packaging/copr-rpm-macros-x86-64-v3.spec' jobs: - build: + prepare: runs-on: ubuntu-latest - container: - image: ghcr.io/funkemunky/kde-x86_64-v4-fedora-rpm-builder:latest - strategy: - fail-fast: false - matrix: - arch: [x86-64-v3] - shard: - - "001" - - "002" - - "003" - - "004" - - "005" - - "006" - - "007" - - "008" - - "009" - - "010" - - "011" - - "012" - - "013" - - "014" - - "015" - - "016" - - "017" - - "018" - - "019" - - "020" - - "021" - - "022" - - "023" - - "024" - - "025" - - "026" - - "027" - - "028" - - "029" - - "030" - - "031" - - "032" - - "033" - - "034" - - "035" - - "036" - - "037" - - "038" - - "039" - - "040" - - "041" - - "042" - - "043" - - "044" - - "045" - - "046" - - "047" - - "048" - - "049" - - "050" - - "051" - - "052" - - "053" - - "054" - - "055" - - "056" - - "057" - - "058" - - "059" - - "060" - - "061" - - "062" - - "063" - - "064" - - "065" - - "066" - - "067" - - "068" - - "069" - - "070" - - "071" - - "072" - - "073" - - "074" - - "075" - - "076" - - "077" - - "078" - - "079" - - "080" - - "081" - - "082" - - "083" - - "084" - - "085" - - "086" - - "087" - - "088" - - "089" - - "090" - - "091" - - "092" - - "093" - - "094" - - "095" - - "096" - - "097" - - "098" - - "099" - - "100" - - "101" - - "102" - - "103" - - "104" - - "105" - - "106" - - "107" - - "108" - - "109" - - "110" - - "111" - - "112" - - "113" - - "114" - - "115" - - "116" - - "117" - - "118" - - "119" - - "120" - - "121" - - "122" - - "123" - - "124" - - "125" - - "126" - - "127" - - "128" + outputs: + matrix: ${{ steps.select.outputs.matrix }} + package_count: ${{ steps.select.outputs.package_count }} steps: - - name: Pre-install Node.js and Git - run: | - if command -v node >/dev/null 2>&1 && command -v git >/dev/null 2>&1 - then - echo "Node.js and Git already present; skipping install." - exit 0 - fi - - mkdir -p /etc/dnf/dnf.conf.d - printf '%s\n' 'keepcache=True' 'max_parallel_downloads=10' > /etc/dnf/dnf.conf.d/99-ci-cache.conf - dnf -y install nodejs git - uses: actions/checkout@v4 - - name: Install build tools - run: | - if command -v fedpkg >/dev/null 2>&1 \ - && command -v rpmbuild >/dev/null 2>&1 \ - && command -v rpmdev-setuptree >/dev/null 2>&1 \ - && command -v git >/dev/null 2>&1 \ - && command -v python3 >/dev/null 2>&1 \ - && rpm -q dnf-plugins-core >/dev/null 2>&1 - then - echo "Build tools already present; skipping install." - exit 0 - fi - mkdir -p /etc/dnf/dnf.conf.d - printf '%s\n' 'keepcache=True' 'max_parallel_downloads=10' > /etc/dnf/dnf.conf.d/99-ci-cache.conf - dnf -y install 'dnf-command(builddep)' fedpkg rpm-build rpmdevtools git python3 - - - name: Build shard packages + - id: select env: - SHARD_INDEX: ${{ matrix.shard }} - FEDORA_BRANCH: f43 - TARGET_MARCH: ${{ matrix.arch }} + PACKAGE_INPUT: ${{ inputs.package_input }} run: | python3 <<'PY' + import json import os - import shutil - import subprocess - import time + import re from pathlib import Path - workspace = Path(os.environ["GITHUB_WORKSPACE"]) - shard_index = int(os.environ["SHARD_INDEX"]) - fedora_branch = os.environ["FEDORA_BRANCH"] - target_march = os.environ["TARGET_MARCH"] - packages_file = workspace / "packages.txt" - - if not packages_file.exists(): + packages_file = Path("packages.txt") + package_input = os.environ.get("PACKAGE_INPUT", "") + if not package_input.strip() and not packages_file.exists(): print("packages.txt not found") raise SystemExit(1) - all_packages = [ - line.strip() - for line in packages_file.read_text(encoding="utf-8").splitlines() - if line.strip() and not line.startswith("#") - ] + if package_input.strip(): + packages = [ + entry + for entry in re.split(r"[\s,]+", package_input.strip()) + if entry + ] + else: + packages = [ + line.strip() + for line in packages_file.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.startswith("#") + ] - max_shards = 128 - shard_count = min(len(all_packages), max_shards) - if shard_count == 0 or shard_index > shard_count: - print( - f"Skipping shard {shard_index:03d} for {target_march}; " - f"package_count={len(all_packages)} shard_count={shard_count}" - ) - raise SystemExit(0) + max_matrix = 128 + matrix = [] + if packages: + shard_size = max(1, -(-len(packages) // max_matrix)) + shards = [ + packages[index:index + shard_size] + for index in range(0, len(packages), shard_size) + ] + matrix = [ + { + "shard_label": f"{shard_index + 1:03d}", + "packages": shard, + } + for shard_index, shard in enumerate(shards) + ] - shard_size = max(1, -(-len(all_packages) // shard_count)) - start_index = (shard_index - 1) * shard_size - end_index = start_index + shard_size - packages = all_packages[start_index:end_index] - - # Standard Fedora optflags with march override - # We'll use a simplified version of the override from the other workflow - optflags_override = ( - f"-O2 -flto=auto -ffat-lto-objects -fexceptions -g " - "-grecord-gcc-switches -pipe -Wall -Werror=format-security " - "-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 " - "-Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 " - "-fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 " - f"-m64 -march={target_march} -mtune=generic -fasynchronous-unwind-tables " - "-fstack-clash-protection -fcf-protection -mtls-dialect=gnu2 " - "-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" - ) - - shard_root = workspace / "artifacts" / f"{target_march}-shard-{shard_index:03d}" - shard_root.mkdir(parents=True, exist_ok=True) - rpmbuild_root = workspace / ".rpmbuild" - rpmbuild_root.mkdir(parents=True, exist_ok=True) - - def run_with_retry(command, *, cwd=None, cleanup_path=None, attempts=3): - for attempt in range(1, attempts + 1): - if cleanup_path is not None and cleanup_path.exists(): - shutil.rmtree(cleanup_path) - try: - subprocess.run(command, cwd=cwd, check=True) - return - except subprocess.CalledProcessError: - if attempt == attempts: - raise - time.sleep(5 * attempt) - - for package_name in packages: - package_dir = workspace / package_name - run_with_retry( - [ - "git", "clone", "--depth", "1", "--branch", fedora_branch, - f"https://src.fedoraproject.org/rpms/{package_name}.git", - str(package_dir), - ], - cleanup_path=package_dir, - ) - - run_with_retry(["fedpkg", "sources"], cwd=package_dir) - - spec_file = next(package_dir.glob("*.spec"), None) - if not spec_file: - print(f"No spec file for {package_name}") - continue - - subprocess.run(["dnf", "-y", "builddep", str(spec_file)], check=True) - - topdir = rpmbuild_root / package_name - for subdir in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"): - (topdir / subdir).mkdir(parents=True, exist_ok=True) - - subprocess.run( - [ - "rpmbuild", "-ba", str(spec_file), - "--define", f"optflags {optflags_override}", - "--define", f"_topdir {topdir}", - "--define", f"_builddir {topdir / 'BUILD'}", - "--define", f"_buildrootdir {topdir / 'BUILDROOT'}", - "--define", f"_rpmdir {topdir / 'RPMS'}", - "--define", f"_srcrpmdir {topdir / 'SRPMS'}", - "--define", f"_sourcedir {package_dir}", - "--define", f"_specdir {package_dir}", - ], - check=True, - ) - - for rpm_path in (topdir / "RPMS").rglob("*.rpm"): - shutil.copy2(rpm_path, shard_root / rpm_path.name) - for src_path in (topdir / "SRPMS").rglob("*.src.rpm"): - shutil.copy2(src_path, shard_root / src_path.name) - print(f"Built {package_name}") + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as handle: + handle.write(f"matrix={json.dumps(matrix)}\n") + handle.write(f"package_count={len(packages)}\n") PY - - name: Upload RPM artifacts - uses: actions/upload-artifact@v3 - with: - name: rpm-${{ matrix.arch }}-shard-${{ matrix.shard }} - path: artifacts/${{ matrix.arch }}-shard-${{ matrix.shard }}/ - if-no-files-found: ignore - - assemble-repo: - needs: build + validate-srpms: + needs: prepare + if: ${{ needs.prepare.outputs.package_count != '0' }} runs-on: ubuntu-latest container: - image: ghcr.io/funkemunky/kde-x86_64-v4-fedora-rpm-builder:latest + image: fedora:43 strategy: fail-fast: false matrix: - include: - - arch: x86-64-v3 - repo_id: fedora43-kde-x86-64-v3 - - arch: x86-64-v4 - repo_id: fedora43-kde-x86-64-v4 + include: ${{ fromJSON(needs.prepare.outputs.matrix) }} steps: - uses: actions/checkout@v4 - - name: Download RPM shard artifacts - uses: actions/download-artifact@v3 - with: - path: downloaded-rpms - - - name: Stage matching shard artifacts + - name: Install SRPM validation tools run: | - mkdir -p filtered-rpms - shopt -s nullglob - for artifact_dir in downloaded-rpms/rpm-${{ matrix.arch }}-shard-*; do - cp -a "$artifact_dir"/. filtered-rpms/ - done + dnf -y install git make python3 rpm-build ca-certificates curl - - name: Install repo assembly tools + - name: Generate shard SRPMs + env: + PACKAGES_JSON: ${{ toJSON(matrix.packages) }} + SHARD_LABEL: ${{ matrix.shard_label }} run: | - if command -v createrepo_c >/dev/null 2>&1 - then - echo "createrepo_c already present; skipping install." - exit 0 - fi + python3 <<'PY' + import json + import os + import subprocess + from pathlib import Path - mkdir -p /etc/dnf/dnf.conf.d - printf '%s\n' 'keepcache=True' 'max_parallel_downloads=10' > /etc/dnf/dnf.conf.d/99-ci-cache.conf - dnf -y install createrepo_c + workspace = Path(os.environ["GITHUB_WORKSPACE"]) + outdir = workspace / "artifacts" / f"srpm-shard-{os.environ['SHARD_LABEL']}" + outdir.mkdir(parents=True, exist_ok=True) - - name: Assemble DNF repository - run: | - python3 ci/assemble-dnf-repo.py \ - --input-dir filtered-rpms \ - --output-dir repo/${{ matrix.arch }} \ - --repo-id ${{ matrix.repo_id }} \ - --repo-name 'Fedora 43 KDE ${{ matrix.arch }}' + subprocess.run( + [ + "make", + "-f", + ".copr/Makefile", + "srpm", + f"outdir={outdir}", + "spec=packaging/copr-rpm-macros-x86-64-v3.spec", + ], + check=True, + cwd=workspace, + ) - - name: Upload DNF repository artifact - uses: actions/upload-artifact@v3 - with: - name: dnf-repo-${{ matrix.arch }} - path: repo/${{ matrix.arch }}/ - if-no-files-found: error + for package_name in json.loads(os.environ["PACKAGES_JSON"]): + subprocess.run( + [ + "make", + "-f", + ".copr/Makefile", + "srpm", + f"outdir={outdir}", + f"spec={package_name}", + ], + check=True, + cwd=workspace, + ) + PY diff --git a/.github/workflows/build-v3-rpms.yml b/.github/workflows/build-v3-rpms.yml index 8102055..c0df968 100644 --- a/.github/workflows/build-v3-rpms.yml +++ b/.github/workflows/build-v3-rpms.yml @@ -1,4 +1,4 @@ -name: Build Fedora x86_64-v3 RPMs +name: Validate Fedora x86_64-v3 Copr SRPMs on: workflow_dispatch: @@ -8,11 +8,29 @@ on: required: false default: "" type: string + copr_project: + description: "Optional Copr project in owner/project form for syncing SCM package definitions." + required: false + default: "" + type: string + submit_to_copr: + description: "Sync package definitions to Copr after validation." + required: false + default: false + type: boolean + submit_package_builds: + description: "Queue package builds after the x86_64-v3 macro package build succeeds." + required: false + default: false + type: boolean push: paths: - - 'packages.txt' - - 'ci/assemble-dnf-repo.py' + - '.copr/Makefile' - '.github/workflows/build-v3-rpms.yml' + - 'ci/copr-distgit-make-srpm.py' + - 'ci/sync-copr-packages.py' + - 'packages.txt' + - 'packaging/copr-rpm-macros-x86-64-v3.spec' permissions: contents: read @@ -40,7 +58,7 @@ jobs: package_input = os.environ.get("PACKAGE_INPUT", "") if not package_input.strip() and not packages_file.exists(): print("::error::packages.txt not found") - exit(1) + raise SystemExit(1) if package_input.strip(): packages = [ @@ -55,8 +73,7 @@ jobs: if line.strip() and not line.startswith("#") ] - # Limit matrix size to avoid GitHub Actions limits - max_matrix = 256 + max_matrix = 128 matrix = [] if packages: shard_size = max(1, -(-len(packages) // max_matrix)) @@ -73,201 +90,117 @@ jobs: for shard_index, shard in enumerate(shards) ] - with open(os.environ["GITHUB_OUTPUT"], "a") as fh: - fh.write(f"matrix={json.dumps(matrix)}\n") - fh.write(f"package_count={len(packages)}\n") + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as handle: + handle.write(f"matrix={json.dumps(matrix)}\n") + handle.write(f"package_count={len(packages)}\n") PY - build: + validate-srpms: needs: prepare if: ${{ needs.prepare.outputs.package_count != '0' }} runs-on: ubuntu-latest container: - image: ghcr.io/funkemunky/kde-x86_64-v4-fedora-rpm-builder:latest + image: fedora:43 strategy: fail-fast: false matrix: include: ${{ fromJSON(needs.prepare.outputs.matrix) }} steps: - uses: actions/checkout@v4 - - name: Restore DNF cache - uses: actions/cache@v4 - with: - path: /var/cache/dnf - key: fedora43-dnf-${{ hashFiles('.github/workflows/build-v3-rpms.yml', 'ci/fedora-rpm-builder.Containerfile') }} - restore-keys: | - fedora43-dnf- - - name: Install build tools + + - name: Install SRPM validation tools run: | - if command -v fedpkg >/dev/null 2>&1 \ - && command -v rpmbuild >/dev/null 2>&1 \ - && command -v rpmdev-setuptree >/dev/null 2>&1 \ - && command -v git >/dev/null 2>&1 \ - && command -v python3 >/dev/null 2>&1 \ - && rpm -q dnf-plugins-core >/dev/null 2>&1 - then - echo "Build tools already present; skipping install." - exit 0 - fi + dnf -y install git make python3 rpm-build ca-certificates curl - mkdir -p /etc/dnf/dnf.conf.d - printf '%s\n' 'keepcache=True' 'max_parallel_downloads=10' > /etc/dnf/dnf.conf.d/99-ci-cache.conf - dnf -y install 'dnf-command(builddep)' fedpkg rpm-build rpmdevtools git python3 - - - name: Build shard packages + - name: Generate shard SRPMs env: - SHARD_INDEX: ${{ matrix.shard }} PACKAGES_JSON: ${{ toJSON(matrix.packages) }} - FEDORA_BRANCH: f43 - TARGET_MARCH: x86-64-v3 + SHARD_LABEL: ${{ matrix.shard_label }} run: | python3 <<'PY' import json import os - import shutil import subprocess - import time from pathlib import Path workspace = Path(os.environ["GITHUB_WORKSPACE"]) - shard_index = int(os.environ["SHARD_INDEX"]) - packages = json.loads(os.environ["PACKAGES_JSON"]) - fedora_branch = os.environ["FEDORA_BRANCH"] - target_march = os.environ["TARGET_MARCH"] - - # Standard Fedora optflags with march override - # We'll use a simplified version of the override from the other workflow - optflags_override = ( - f"-O2 -flto=auto -ffat-lto-objects -fexceptions -g " - "-grecord-gcc-switches -pipe -Wall -Werror=format-security " - "-Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 " - "-Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 " - "-fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 " - f"-m64 -march={target_march} -mtune=generic -fasynchronous-unwind-tables " - "-fstack-clash-protection -fcf-protection -mtls-dialect=gnu2 " - "-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer" + outdir = workspace / "artifacts" / f"srpm-shard-{os.environ['SHARD_LABEL']}" + outdir.mkdir(parents=True, exist_ok=True) + + subprocess.run( + [ + "make", + "-f", + ".copr/Makefile", + "srpm", + f"outdir={outdir}", + "spec=packaging/copr-rpm-macros-x86-64-v3.spec", + ], + check=True, + cwd=workspace, ) - - shard_root = workspace / "artifacts" / f"shard-{shard_index:03d}" - shard_root.mkdir(parents=True, exist_ok=True) - rpmbuild_root = workspace / ".rpmbuild" - rpmbuild_root.mkdir(parents=True, exist_ok=True) - def run_with_retry(command, *, cwd=None, cleanup_path=None, attempts=3): - for attempt in range(1, attempts + 1): - if cleanup_path is not None and cleanup_path.exists(): - shutil.rmtree(cleanup_path) - try: - subprocess.run(command, cwd=cwd, check=True) - return - except subprocess.CalledProcessError: - if attempt == attempts: - raise - time.sleep(5 * attempt) - - for package_name in packages: - package_dir = workspace / package_name - run_with_retry( - [ - "git", "clone", "--depth", "1", "--branch", fedora_branch, - f"https://src.fedoraproject.org/rpms/{package_name}.git", - str(package_dir), - ], - cleanup_path=package_dir, - ) - - run_with_retry(["fedpkg", "sources"], cwd=package_dir) - - spec_file = next(package_dir.glob("*.spec"), None) - if not spec_file: - print(f"No spec file for {package_name}") - continue - - subprocess.run(["dnf", "-y", "builddep", str(spec_file)], check=True) - - topdir = rpmbuild_root / package_name - for subdir in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"): - (topdir / subdir).mkdir(parents=True, exist_ok=True) - + for package_name in json.loads(os.environ["PACKAGES_JSON"]): subprocess.run( [ - "rpmbuild", "-ba", str(spec_file), - "--define", f"optflags {optflags_override}", - "--define", f"_topdir {topdir}", - "--define", f"_builddir {topdir / 'BUILD'}", - "--define", f"_buildrootdir {topdir / 'BUILDROOT'}", - "--define", f"_rpmdir {topdir / 'RPMS'}", - "--define", f"_srcrpmdir {topdir / 'SRPMS'}", - "--define", f"_sourcedir {package_dir}", - "--define", f"_specdir {package_dir}", + "make", + "-f", + ".copr/Makefile", + "srpm", + f"outdir={outdir}", + f"spec={package_name}", ], check=True, + cwd=workspace, ) - - for rpm_path in (topdir / "RPMS").rglob("*.rpm"): - shutil.copy2(rpm_path, shard_root / rpm_path.name) - for src_path in (topdir / "SRPMS").rglob("*.src.rpm"): - shutil.copy2(src_path, shard_root / src_path.name) - print(f"Built {package_name}") PY - - name: Upload RPM artifacts + - name: Upload SRPM artifacts uses: actions/upload-artifact@v4 with: - name: rpm-shard-${{ matrix.shard_label }} - path: artifacts/shard-${{ matrix.shard_label }}/ + name: srpm-shard-${{ matrix.shard_label }} + path: artifacts/srpm-shard-${{ matrix.shard_label }}/ if-no-files-found: error - assemble-repo: - needs: build + sync-copr: + needs: + - prepare + - validate-srpms + if: ${{ github.event_name == 'workflow_dispatch' && inputs.submit_to_copr && inputs.copr_project != '' }} runs-on: ubuntu-latest container: - image: ghcr.io/funkemunky/kde-x86_64-v4-fedora-rpm-builder:latest + image: fedora:43 steps: - uses: actions/checkout@v4 - - name: Download RPM shard artifacts - uses: actions/download-artifact@v4 - with: - pattern: rpm-shard-* - path: downloaded-rpms - - - name: Install repo assembly tools + - name: Install Copr sync tools run: | - if command -v createrepo_c >/dev/null 2>&1 - then - echo "createrepo_c already present; skipping install." - exit 0 - fi + dnf -y install copr-cli git make python3 rpm-build ca-certificates curl - mkdir -p /etc/dnf/dnf.conf.d - printf '%s\n' 'keepcache=True' 'max_parallel_downloads=10' > /etc/dnf/dnf.conf.d/99-ci-cache.conf - dnf -y install createrepo_c - - - name: Assemble DNF repository - run: | - python3 ci/assemble-dnf-repo.py \ - --input-dir downloaded-rpms \ - --output-dir repo/fedora43-x86-64-v3 \ - --repo-id fedora43-kde-x86-64-v3 \ - --repo-name 'Fedora 43 KDE x86_64-v3' - - - name: Upload RPMs to Nexus YUM repository + - name: Configure copr-cli env: - NEXUS_BASE_URL: https://nexus.funkemunky.cc - NEXUS_REPOSITORY: dnf-repo-x86-64-v3 - NEXUS_USER: ${{ secrets.nexusUser }} - NEXUS_PASSWORD: ${{ secrets.nexusPassword }} + COPR_CONFIG: ${{ secrets.COPR_CONFIG }} run: | - test -n "${NEXUS_USER}" - test -n "${NEXUS_PASSWORD}" - PACKAGE_ROOT="repo/fedora43-x86-64-v3/packages" - find "${PACKAGE_ROOT}" -type f -name '*.rpm' | while read -r FILEPATH; do - FILENAME="$(basename "${FILEPATH}")" - curl --fail --show-error --silent \ - --user "${NEXUS_USER}:${NEXUS_PASSWORD}" \ - -X POST "${NEXUS_BASE_URL}/service/rest/v1/components?repository=${NEXUS_REPOSITORY}" \ - -H "Content-Type: multipart/form-data" \ - -F "yum.asset=@${FILEPATH};type=application/x-rpm" \ - -F "yum.asset.filename=${FILENAME}" - done + test -n "${COPR_CONFIG}" + mkdir -p ~/.config + printf '%s\n' "${COPR_CONFIG}" > ~/.config/copr + chmod 0600 ~/.config/copr + + - name: Sync Copr SCM package definitions + env: + COPR_PROJECT: ${{ inputs.copr_project }} + PACKAGE_INPUT: ${{ inputs.package_input }} + SUBMIT_PACKAGE_BUILDS: ${{ inputs.submit_package_builds }} + run: | + set -euo pipefail + ARGS=( + --project "${COPR_PROJECT}" + --clone-url "https://github.com/${GITHUB_REPOSITORY}.git" + --commit "${GITHUB_SHA}" + --package-input "${PACKAGE_INPUT}" + --submit-macro-build + ) + if [ "${SUBMIT_PACKAGE_BUILDS}" = "true" ]; then + ARGS+=(--submit-package-builds --nowait-package-builds) + fi + python3 ci/sync-copr-packages.py "${ARGS[@]}" diff --git a/.gitignore b/.gitignore index f53cc0d..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,305 +0,0 @@ -.rpmbuild/ -*.src.rpm -__pycache__/ -.dnf/ -scripts/__pycache__/ - -# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans -# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux,maven,java,intellij,eclipse,netbeans - -### Eclipse ### -.metadata -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.settings/ -.loadpath -.recommenders -*.iml - -.idea/ - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# PyDev specific (Python IDE for Eclipse) -*.pydevproject - -# CDT-specific (C/C++ Development Tooling) -.cproject - -# CDT- autotools -.autotools - -# Java annotation processor (APT) -.factorypath - -# PDT-specific (PHP Development Tools) -.buildpath - -# sbteclipse plugin -.target - -# Tern plugin -.tern-project - -# TeXlipse plugin -.texlipse - -# STS (Spring Tool Suite) -.springBeans - -# Code Recommenders -.recommenders/ - -# Annotation Processing -.apt_generated/ -.apt_generated_test/ - -# Scala IDE specific (Scala & Java development for Eclipse) -.cache-main -.scala_dependencies -.worksheet - -# Uncomment this line if you wish to ignore the project description file. -# Typically, this file would be tracked if it contains build/dependency configurations: -#.project - -### Eclipse Patch ### -# Spring Boot Tooling -.sts4-cache/ - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# User-specific stuff -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml -.idea/**/libraries - -# Gradle and Maven with auto-import -# When using Gradle or Maven with auto-import, you should exclude module files, -# since they will be recreated, and may cause churn. Uncomment if using -# auto-import. -# .idea/artifacts -# .idea/compiler.xml -# .idea/jarRepositories.xml -# .idea/modules.xml -# .idea/*.iml -# .idea/modules -# *.iml -# *.ipr - -# CMake -cmake-build-*/ - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# File-based project format -*.iws - -# IntelliJ -out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - -# Editor-based Rest Client -.idea/httpRequests - -# Android studio 3.1+ serialized cache file -.idea/caches/build_file_checksums.ser - -### Intellij Patch ### -# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 - -# *.iml -# modules.xml -# .idea/misc.xml -# *.ipr - -# Sonarlint plugin -# https://plugins.jetbrains.com/plugin/7973-sonarlint -.idea/**/sonarlint/ - -# SonarQube Plugin -# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin -.idea/**/sonarIssues.xml - -# Markdown Navigator plugin -# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced -.idea/**/markdown-navigator.xml -.idea/**/markdown-navigator-enh.xml -.idea/**/markdown-navigator/ - -# Cache file creation bug -# See https://youtrack.jetbrains.com/issue/JBR-2257 -.idea/$CACHE_FILE$ - -# CodeStream plugin -# https://plugins.jetbrains.com/plugin/12206-codestream -.idea/codestream.xml - -### Java ### -# Compiled class file -*.class - -# Log file -*.log - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -!gradle/wrapper/gradle-wrapper.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### macOS ### -# General -.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar -.mvn/wrapper/maven-wrapper.jar -.flattened-pom.xml - -### NetBeans ### -**/nbproject/private/ -**/nbproject/Makefile-*.mk -**/nbproject/Package-*.bash -build/ -nbbuild/ -dist/ -nbdist/ -.nb-gradle/ - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -Thumbs.db:encryptable -ehthumbs.db -ehthumbs_vista.db - -# Dump file -*.stackdump - -# Folder config file -[Dd]esktop.ini - -.dnf/* - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msix -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans -/.gradle/ diff --git a/README.md b/README.md index a88d0ef..73ea630 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,64 @@ -# Fedora 43 KDE Actions RPM Builder +# Fedora 43 KDE Copr x86_64-v3 Builder -This repository provides CI workflows for both GitHub Actions and Gitea Actions to rebuild Fedora 43 -KDE packages from Fedora dist-git with `x86-64-v3` and `x86-64-v4` code generation and `-O3` -optimization. +This repository now targets Copr rather than doing local `rpmbuild -ba` work inside CI. +It uses Copr's SCM `make_srpm` flow to generate SRPMs from Fedora dist-git content and installs +a dedicated buildroot macro package so Copr's Fedora 43 `x86_64` builders compile with an +`x86-64-v3` ISA baseline. -## Workflows -- **GitHub Actions**: `.github/workflows/build-v3-rpms.yml` -- **Gitea Actions**: `.gitea/workflows/build-v3-rpms.yml` +## What changed +- `.copr/Makefile` is the Copr entrypoint used by the SCM `make_srpm` method. +- `ci/copr-distgit-make-srpm.py` clones Fedora dist-git, downloads lookaside sources, and builds SRPMs. +- `packaging/copr-rpm-macros-x86-64-v3.spec` produces the buildroot macro package that changes Fedora's + `%__cflags_arch_x86_64_level` to `-v3` while keeping the rest of `redhat-rpm-config` intact. +- `ci/sync-copr-packages.py` registers or updates Copr SCM package definitions and can queue builds. -Each build workflow now finishes with a repository assembly job that collects the shard artifacts, -generates `repodata/` with `createrepo_c`, and uploads a ready-to-serve DNF repository artifact. +## Why the macro package exists -## Repository artifacts -- **GitHub Actions** uploads `dnf-repo-x86-64-v3` -- **Gitea Actions** uploads `dnf-repo-x86-64-v3` and `dnf-repo-x86-64-v4` +Copr does not expose a generic `rpmbuild --define` interface for package builds. The supported way to +change buildroot macros is to build a small RPM that drops a file into `%{rpmmacrodir}` and then add +that package to the Copr chroot's additional packages list. This repo does that with +`copr-rpm-macros-x86-64-v3`. -Each repository artifact contains: -- `packages/` with the built binary RPMs -- `repodata/` generated for DNF -- a `.repo` template with a placeholder `baseurl` -- `README.txt` with Fedora 43 install instructions +With that package installed in the buildroot, packages that honor Fedora's standard `%optflags` / +`%set_build_flags` path will compile with `-march=x86-64-v3` on Copr builders. + +## Local validation + +Generate the macro SRPM: + +```bash +make -f .copr/Makefile srpm outdir=dist-srpms spec=packaging/copr-rpm-macros-x86-64-v3.spec +``` + +Generate a Fedora dist-git package SRPM the same way Copr will: + +```bash +make -f .copr/Makefile srpm outdir=dist-srpms spec=konsole +``` + +## Copr setup + +1. Create or reuse a Copr project with the `fedora-43-x86_64` chroot enabled. +2. Sync this repository's SCM package definitions into that project: + +```bash +python3 ci/sync-copr-packages.py \ + --project yourname/kde-x86-64-v3 \ + --clone-url https://github.com//.git \ + --commit \ + --submit-macro-build +``` + +3. After the macro package build succeeds, queue the package builds: + +```bash +python3 ci/sync-copr-packages.py \ + --project yourname/kde-x86-64-v3 \ + --clone-url https://github.com//.git \ + --commit \ + --submit-package-builds \ + --nowait-package-builds +``` + +The sync step also sets the Copr chroot's additional packages list to `copr-rpm-macros-x86-64-v3`, +which is the piece that makes the `x86-64-v3` compile target apply on Copr builders. diff --git a/ci/copr-distgit-make-srpm.py b/ci/copr-distgit-make-srpm.py new file mode 100644 index 0000000..93c7951 --- /dev/null +++ b/ci/copr-distgit-make-srpm.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import hashlib +import re +import shutil +import subprocess +import tempfile +import time +import urllib.parse +import urllib.request +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SOURCE_LINE_PATTERN = re.compile( + r"^(?P[A-Za-z0-9_+-]+)\s+\((?P.+)\)\s+=\s+(?P[0-9A-Fa-f]+)$" +) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Create an SRPM either from a local spec in this repository or by " + "cloning a Fedora dist-git package and downloading its lookaside sources." + ) + ) + parser.add_argument("--spec-ref", required=True, help="Local .spec path or Fedora dist-git package name") + parser.add_argument("--outdir", required=True, help="Directory where the generated SRPM should be written") + parser.add_argument("--branch", default="f43", help="Fedora dist-git branch to clone when spec-ref is a package") + parser.add_argument("--dist", default=".fc43", help="RPM dist suffix to define while generating the SRPM") + parser.add_argument("--namespace", default="rpms", help="Fedora dist-git namespace") + parser.add_argument( + "--lookaside-baseurl", + default="https://src.fedoraproject.org/repo/pkgs", + help="Base URL for the Fedora lookaside cache", + ) + parser.add_argument("--retry-count", type=int, default=3, help="Number of retries for network operations") + return parser.parse_args() + + +def run(command: list[str], *, cwd: Path | None = None) -> None: + subprocess.run(command, cwd=cwd, check=True) + + +def ensure_within_repo(path: Path) -> Path | None: + candidate = (REPO_ROOT / path).resolve() + try: + candidate.relative_to(REPO_ROOT) + except ValueError: + return None + if candidate.is_file() and candidate.suffix == ".spec": + return candidate + return None + + +def hash_file(path: Path, algorithm: str) -> str: + digest = hashlib.new(algorithm.lower()) + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def download_file(url: str, destination: Path, *, algorithm: str, checksum: str, attempts: int) -> None: + destination.parent.mkdir(parents=True, exist_ok=True) + for attempt in range(1, attempts + 1): + if destination.exists() and hash_file(destination, algorithm) == checksum.lower(): + return + + try: + with urllib.request.urlopen(url) as response, destination.open("wb") as handle: + shutil.copyfileobj(response, handle) + except Exception: + if destination.exists(): + destination.unlink() + if attempt == attempts: + raise + time.sleep(attempt * 3) + continue + + if hash_file(destination, algorithm) == checksum.lower(): + return + + destination.unlink(missing_ok=True) + if attempt == attempts: + raise RuntimeError(f"checksum mismatch for {destination.name} from {url}") + time.sleep(attempt * 3) + + +def build_srpm(spec_path: Path, *, source_dir: Path, outdir: Path, dist: str) -> Path: + outdir.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="copr-srpm-topdir-") as topdir_name: + topdir = Path(topdir_name) + command = [ + "rpmbuild", + "-bs", + str(spec_path), + "--define", + f"_topdir {topdir}", + "--define", + f"_builddir {topdir / 'BUILD'}", + "--define", + f"_buildrootdir {topdir / 'BUILDROOT'}", + "--define", + f"_rpmdir {topdir / 'RPMS'}", + "--define", + f"_srcrpmdir {outdir}", + "--define", + f"_sourcedir {source_dir}", + "--define", + f"_specdir {spec_path.parent}", + ] + if dist: + command.extend(["--define", f"dist {dist}"]) + run(command, cwd=source_dir) + + srpms = sorted(outdir.glob("*.src.rpm"), key=lambda path: path.stat().st_mtime, reverse=True) + if not srpms: + raise RuntimeError(f"rpmbuild did not produce an SRPM in {outdir}") + return srpms[0] + + +def parse_sources_file(path: Path) -> list[tuple[str, str, str]]: + entries: list[tuple[str, str, str]] = [] + if not path.exists(): + return entries + + for raw_line in path.read_text(encoding="utf-8").splitlines(): + line = raw_line.strip() + if not line: + continue + match = SOURCE_LINE_PATTERN.match(line) + if not match: + raise RuntimeError(f"unsupported lookaside source format: {line}") + entries.append( + ( + match.group("algo").lower(), + match.group("filename"), + match.group("checksum").lower(), + ) + ) + return entries + + +def download_lookaside_sources( + package_name: str, + source_dir: Path, + *, + namespace: str, + lookaside_baseurl: str, + attempts: int, +) -> None: + for algorithm, filename, checksum in parse_sources_file(source_dir / "sources"): + encoded_filename = urllib.parse.quote(filename) + url = ( + f"{lookaside_baseurl}/{namespace}/{package_name}/" + f"{encoded_filename}/{algorithm}/{checksum}/{encoded_filename}" + ) + download_file( + url, + source_dir / filename, + algorithm=algorithm, + checksum=checksum, + attempts=attempts, + ) + + +def build_from_distgit( + package_name: str, + *, + branch: str, + dist: str, + namespace: str, + lookaside_baseurl: str, + attempts: int, + outdir: Path, +) -> Path: + clone_url = f"https://src.fedoraproject.org/{namespace}/{package_name}.git" + with tempfile.TemporaryDirectory(prefix=f"{package_name}-distgit-") as tempdir_name: + tempdir = Path(tempdir_name) + package_dir = tempdir / package_name + for attempt in range(1, attempts + 1): + try: + if package_dir.exists(): + shutil.rmtree(package_dir) + run( + [ + "git", + "clone", + "--depth", + "1", + "--branch", + branch, + clone_url, + str(package_dir), + ] + ) + break + except subprocess.CalledProcessError: + if attempt == attempts: + raise + time.sleep(attempt * 3) + spec_files = sorted(package_dir.glob("*.spec")) + if not spec_files: + raise RuntimeError(f"no spec file found in {clone_url} branch {branch}") + spec_path = spec_files[0] + download_lookaside_sources( + package_name, + package_dir, + namespace=namespace, + lookaside_baseurl=lookaside_baseurl, + attempts=attempts, + ) + return build_srpm(spec_path, source_dir=package_dir, outdir=outdir, dist=dist) + + +def main() -> int: + args = parse_args() + outdir = Path(args.outdir).resolve() + + local_spec = ensure_within_repo(Path(args.spec_ref)) + if local_spec is not None: + srpm = build_srpm(local_spec, source_dir=local_spec.parent, outdir=outdir, dist=args.dist) + print(f"built local SRPM {srpm.name}") + return 0 + + srpm = build_from_distgit( + args.spec_ref, + branch=args.branch, + dist=args.dist, + namespace=args.namespace, + lookaside_baseurl=args.lookaside_baseurl.rstrip("/"), + attempts=args.retry_count, + outdir=outdir, + ) + print(f"built dist-git SRPM {srpm.name}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/ci/fedora-rpm-builder.Containerfile b/ci/fedora-rpm-builder.Containerfile index 9650222..16a1cec 100644 --- a/ci/fedora-rpm-builder.Containerfile +++ b/ci/fedora-rpm-builder.Containerfile @@ -6,13 +6,11 @@ RUN mkdir -p /etc/dnf/dnf.conf.d \ RUN dnf -y upgrade --refresh \ && dnf -y install \ - 'dnf-command(builddep)' \ - fedpkg \ git \ - nodejs \ + make \ python3 \ rpm-build \ - rpmdevtools \ - @kde-desktop \ - @development-tools \ + copr-cli \ + ca-certificates \ + curl \ && dnf clean all diff --git a/ci/sync-copr-packages.py b/ci/sync-copr-packages.py new file mode 100644 index 0000000..929f97c --- /dev/null +++ b/ci/sync-copr-packages.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import re +import subprocess +from pathlib import Path + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Register or update Copr SCM package definitions that use this " + "repository's .copr/Makefile and optionally submit builds." + ) + ) + parser.add_argument("--project", required=True, help="Copr project, e.g. user/project") + parser.add_argument("--clone-url", required=True, help="Git clone URL Copr should use for this repository") + parser.add_argument("--commit", default="HEAD", help="Git ref Copr should build from") + parser.add_argument("--chroot", default="fedora-43-x86_64", help="Copr chroot to configure and build for") + parser.add_argument("--packages-file", default="packages.txt", help="Package list to register") + parser.add_argument( + "--package-input", + default="", + help="Optional comma, space, or newline separated package list. Overrides --packages-file when set.", + ) + parser.add_argument( + "--macro-package-name", + default="copr-rpm-macros-x86-64-v3", + help="Name of the buildroot macro package in Copr", + ) + parser.add_argument( + "--macro-package-spec", + default="packaging/copr-rpm-macros-x86-64-v3.spec", + help="Local spec reference for the buildroot macro package", + ) + parser.add_argument("--webhook-rebuild", choices=("on", "off"), default="off") + parser.add_argument("--max-builds", type=int, default=0) + parser.add_argument("--timeout", type=int, default=18000) + parser.add_argument("--skip-chroot-update", action="store_true") + parser.add_argument("--submit-macro-build", action="store_true") + parser.add_argument("--submit-package-builds", action="store_true") + parser.add_argument("--nowait-package-builds", action="store_true") + return parser.parse_args() + + +def run(command: list[str], *, quiet: bool = False) -> subprocess.CompletedProcess[str]: + kwargs: dict[str, object] = { + "check": True, + "text": True, + } + if quiet: + kwargs["stdout"] = subprocess.DEVNULL + kwargs["stderr"] = subprocess.DEVNULL + return subprocess.run(command, **kwargs) + + +def package_exists(project: str, package_name: str) -> bool: + result = subprocess.run( + ["copr", "get-package", project, "--name", package_name], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + text=True, + ) + return result.returncode == 0 + + +def load_packages(packages_file: Path, package_input: str) -> list[str]: + if package_input.strip(): + packages = [entry for entry in re.split(r"[\s,]+", package_input.strip()) if entry] + else: + packages = [ + line.strip() + for line in packages_file.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.lstrip().startswith("#") + ] + deduped: list[str] = [] + seen: set[str] = set() + for package in packages: + if package not in seen: + deduped.append(package) + seen.add(package) + return deduped + + +def upsert_scm_package( + *, + project: str, + package_name: str, + spec_ref: str, + clone_url: str, + commit: str, + webhook_rebuild: str, + max_builds: int, + timeout: int, +) -> None: + action = "edit-package-scm" if package_exists(project, package_name) else "add-package-scm" + command = [ + "copr", + action, + project, + "--name", + package_name, + "--clone-url", + clone_url, + "--commit", + commit, + "--method", + "make_srpm", + "--spec", + spec_ref, + "--webhook-rebuild", + webhook_rebuild, + "--max-builds", + str(max_builds), + "--timeout", + str(timeout), + ] + run(command) + + +def submit_build(project: str, package_name: str, chroot: str, *, nowait: bool) -> None: + command = [ + "copr", + "build-package", + project, + "--name", + package_name, + "--chroot", + chroot, + ] + if nowait: + command.append("--nowait") + run(command) + + +def main() -> int: + args = parse_args() + packages = load_packages(Path(args.packages_file), args.package_input) + + upsert_scm_package( + project=args.project, + package_name=args.macro_package_name, + spec_ref=args.macro_package_spec, + clone_url=args.clone_url, + commit=args.commit, + webhook_rebuild=args.webhook_rebuild, + max_builds=args.max_builds, + timeout=args.timeout, + ) + + for package_name in packages: + upsert_scm_package( + project=args.project, + package_name=package_name, + spec_ref=package_name, + clone_url=args.clone_url, + commit=args.commit, + webhook_rebuild=args.webhook_rebuild, + max_builds=args.max_builds, + timeout=args.timeout, + ) + + if not args.skip_chroot_update: + run( + [ + "copr", + "edit-chroot", + f"{args.project}/{args.chroot}", + "--packages", + args.macro_package_name, + ] + ) + + if args.submit_macro_build: + submit_build(args.project, args.macro_package_name, args.chroot, nowait=False) + + if args.submit_package_builds: + for package_name in packages: + submit_build( + args.project, + package_name, + args.chroot, + nowait=args.nowait_package_builds, + ) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/packaging/copr-rpm-macros-x86-64-v3.spec b/packaging/copr-rpm-macros-x86-64-v3.spec new file mode 100644 index 0000000..532fc50 --- /dev/null +++ b/packaging/copr-rpm-macros-x86-64-v3.spec @@ -0,0 +1,29 @@ +Name: copr-rpm-macros-x86-64-v3 +Version: 1 +Release: 1%{?dist} +Summary: Copr buildroot macros for x86_64-v3 rebuilds + +License: MIT +BuildArch: noarch + +%description +This package installs a small RPM macro override for Copr buildroots. +It keeps Fedora's standard compiler and linker flag stack intact while +raising the x86_64 ISA baseline from x86-64 to x86-64-v3. + +%prep + +%build + +%install +mkdir -p %{buildroot}%{rpmmacrodir} +cat > %{buildroot}%{rpmmacrodir}/macros.copr-x86-64-v3 <<'EOF' +%__cflags_arch_x86_64_level -v3 +EOF + +%files +%{rpmmacrodir}/macros.copr-x86-64-v3 + +%changelog +* Mon Apr 27 2026 OpenAI Codex - 1-1 +- Install a Copr buildroot macro override for x86_64-v3