Files
KDE-x86_64-v4-Fedora/.github/workflows/build-fedora-rpms.yml
T

344 lines
12 KiB
YAML

name: Build Fedora RPMs
on:
workflow_dispatch:
inputs:
package_kind:
description: "Interpret package_input as source or binary package names"
required: true
default: "source"
type: choice
options:
- source
- binary
package_input:
description: "Comma, space, or newline separated package list. Leave blank to use the full manifest."
required: false
default: ""
type: string
source_manifest:
description: "Checked-in source manifest path"
required: true
default: "manifests/fedora-43/source-packages.txt"
type: string
source_map:
description: "Checked-in binary to source map path"
required: true
default: "manifests/fedora-43/source-map.tsv"
type: string
fedora_branch:
description: "Fedora dist-git branch"
required: true
default: "f43"
type: string
fedora_release:
description: "Fedora container release"
required: true
default: "43"
type: string
builder_image:
description: "Prebuilt Fedora RPM builder image reference"
required: true
default: "ghcr.io/dawsonc/kde-x86_64-v4-fedora-builder:f43"
type: string
optimization_level:
description: "Compiler optimization level"
required: true
default: "-O3"
type: choice
options:
- -O2
- -O3
target_march:
description: "Target march override"
required: true
default: "x86-64-v4"
type: string
permissions:
contents: read
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
package_count: ${{ steps.select.outputs.package_count }}
shard_count: ${{ steps.select.outputs.shard_count }}
steps:
- uses: actions/checkout@v4
- id: select
env:
PACKAGE_KIND: ${{ inputs.package_kind }}
PACKAGE_INPUT: ${{ inputs.package_input }}
SOURCE_MANIFEST: ${{ inputs.source_manifest }}
SOURCE_MAP: ${{ inputs.source_map }}
run: |
python3 <<'PY'
import json
import os
import re
from pathlib import Path
source_manifest = Path(os.environ["SOURCE_MANIFEST"])
source_map_path = Path(os.environ["SOURCE_MAP"])
requested_source_map_path = source_map_path
package_kind = os.environ["PACKAGE_KIND"].strip()
package_input = os.environ["PACKAGE_INPUT"]
if not source_manifest.exists():
raise SystemExit(f"Source manifest not found: {source_manifest}")
if not source_map_path.exists() and source_map_path.name == "binary-map.tsv":
compatibility_path = source_map_path.with_name("source-map.tsv")
if compatibility_path.exists():
source_map_path = compatibility_path
if not source_map_path.exists():
raise SystemExit(
f"Source map not found: {requested_source_map_path}. "
f"Expected an existing TSV such as {requested_source_map_path.with_name('source-map.tsv')}"
)
source_packages = [
line.strip()
for line in source_manifest.read_text(encoding="ascii").splitlines()
if line.strip()
]
binary_to_source = {}
for line in source_map_path.read_text(encoding="ascii").splitlines():
if not line.strip():
continue
binary_name, source_name = line.split("\t", 1)
binary_to_source[binary_name] = source_name
def tokenize(raw: str):
parts = re.split(r"[\s,]+", raw.strip())
return [part for part in parts if part]
if package_input.strip():
selected = tokenize(package_input)
else:
selected = source_packages
if package_kind == "binary":
mapped = []
missing = []
for binary_name in selected:
source_name = binary_to_source.get(binary_name)
if source_name is None:
missing.append(binary_name)
else:
mapped.append(source_name)
if missing:
raise SystemExit(
"Missing binary-to-source mappings for: " + ", ".join(sorted(missing))
)
selected = mapped
deduped = []
seen = set()
for package_name in selected:
if package_name not in seen:
seen.add(package_name)
deduped.append(package_name)
max_matrix = 256
if not deduped:
matrix = []
else:
shard_size = max(1, -(-len(deduped) // max_matrix))
shards = [
deduped[index:index + shard_size]
for index in range(0, len(deduped), shard_size)
]
matrix = [
{
"shard": shard_index + 1,
"packages": shard,
"package_count": len(shard),
}
for shard_index, shard in enumerate(shards)
]
github_output = Path(os.environ["GITHUB_OUTPUT"])
with github_output.open("a", encoding="utf-8") as fh:
fh.write(f"matrix={json.dumps(matrix)}\n")
fh.write(f"package_count={len(deduped)}\n")
fh.write(f"shard_count={len(matrix)}\n")
PY
- name: Summarize selection
run: |
echo "Selected packages: ${{ steps.select.outputs.package_count }}" >> "$GITHUB_STEP_SUMMARY"
echo "Shards: ${{ steps.select.outputs.shard_count }}" >> "$GITHUB_STEP_SUMMARY"
echo '${{ steps.select.outputs.matrix }}' >> "$GITHUB_STEP_SUMMARY"
build:
needs: prepare
if: ${{ needs.prepare.outputs.package_count != '0' }}
runs-on: ubuntu-latest
container:
image: ${{ inputs.builder_image }}
strategy:
fail-fast: false
max-parallel: 128
matrix:
include: ${{ fromJSON(needs.prepare.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
- name: Cache Fedora DNF data
uses: actions/cache@v4
with:
path: |
/var/cache/dnf
/var/lib/dnf
key: dnf-fedora-${{ inputs.fedora_release }}
restore-keys: |
dnf-fedora-${{ inputs.fedora_release }}-
- name: Verify builder toolchain
run: |
command -v dnf
command -v fedpkg
command -v rpmbuild
command -v git
command -v node
command -v gcc
command -v g-ir-scanner
command -v gi-docgen
rpm -q rpm-build rpmdevtools dnf-plugins-core
- name: Build shard packages
env:
SHARD_INDEX: ${{ matrix.shard }}
PACKAGES_JSON: ${{ toJSON(matrix.packages) }}
FEDORA_BRANCH: ${{ inputs.fedora_branch }}
OPT_LEVEL: ${{ inputs.optimization_level }}
TARGET_MARCH: ${{ inputs.target_march }}
run: |
python3 <<'PY'
import json
import os
import shutil
import subprocess
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"]
opt_level = os.environ["OPT_LEVEL"]
target_march = os.environ["TARGET_MARCH"]
optflags_override = (
f"{opt_level} -flto=auto -ffat-lto-objects -fexceptions -g "
"-grecord-gcc-switches -pipe -Wall -Wno-complain-wrong-lang "
"-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"shard-{shard_index:03d}"
shard_root.mkdir(parents=True, exist_ok=True)
rpmbuild_root = workspace / ".rpmbuild"
rpmbuild_root.mkdir(parents=True, exist_ok=True)
for package_name in packages:
package_dir = workspace / package_name
if package_dir.exists():
shutil.rmtree(package_dir)
subprocess.run(
[
"git",
"clone",
"--depth",
"1",
"--branch",
fedora_branch,
f"https://src.fedoraproject.org/rpms/{package_name}.git",
str(package_dir),
],
check=True,
)
subprocess.run(["fedpkg", "sources"], cwd=package_dir, check=True)
spec_file = next(package_dir.glob("*.spec"), None)
if spec_file is None:
raise SystemExit(f"No spec file found for {package_name}")
subprocess.run(
[
"dnf",
"-y",
"builddep",
"--setopt=keepcache=1",
"--setopt=skip_if_unavailable=True",
str(spec_file),
],
cwd=package_dir,
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}",
],
cwd=package_dir,
check=True,
)
outdir = shard_root / package_name
outdir.mkdir(parents=True, exist_ok=True)
for rpm_path in (topdir / "RPMS").rglob("*.rpm"):
shutil.copy2(rpm_path, outdir / rpm_path.name)
for src_path in (topdir / "SRPMS").rglob("*.src.rpm"):
shutil.copy2(src_path, outdir / src_path.name)
print(f"Built {package_name}")
PY
- name: Collect artifacts
env:
SHARD_INDEX: ${{ matrix.shard }}
run: |
SHARD_LABEL=$(printf "%03d" "${SHARD_INDEX}")
OUTDIR="${GITHUB_WORKSPACE}/artifacts/shard-${SHARD_LABEL}"
ls -la "${OUTDIR}"
- name: Upload RPM artifacts
uses: actions/upload-artifact@v4
with:
name: rpm-shard-${{ format('{0:03}', matrix.shard) }}
path: artifacts/shard-${{ format('{0:03}', matrix.shard) }}/
if-no-files-found: error
- name: Append build summary
if: always()
env:
SHARD_INDEX: ${{ matrix.shard }}
run: |
SHARD_LABEL=$(printf "%03d" "${SHARD_INDEX}")
echo "### shard-${SHARD_LABEL}" >> "$GITHUB_STEP_SUMMARY"
if [ -d "artifacts/shard-${SHARD_LABEL}" ]; then
find "artifacts/shard-${SHARD_LABEL}" -maxdepth 2 -type f | sort >> "$GITHUB_STEP_SUMMARY"
else
echo "No artifacts collected." >> "$GITHUB_STEP_SUMMARY"
fi