mirror of
https://github.com/funkemunky/KDE-x86_64-v4-Fedora.git
synced 2026-05-31 00:51:56 +00:00
Adding srpm build process
This commit is contained in:
@@ -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)"
|
||||
@@ -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
|
||||
|
||||
@@ -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[@]}"
|
||||
|
||||
-305
@@ -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/
|
||||
|
||||
@@ -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/<owner>/<repo>.git \
|
||||
--commit <git-ref> \
|
||||
--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/<owner>/<repo>.git \
|
||||
--commit <git-ref> \
|
||||
--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.
|
||||
|
||||
@@ -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<algo>[A-Za-z0-9_+-]+)\s+\((?P<filename>.+)\)\s+=\s+(?P<checksum>[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())
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -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 <codex@openai.com> - 1-1
|
||||
- Install a Copr buildroot macro override for x86_64-v3
|
||||
Reference in New Issue
Block a user