https://gcc.gnu.org/g:17cc07b4d374173be3c51636abd2c9b9e56b2588
commit r16-6576-g17cc07b4d374173be3c51636abd2c9b9e56b2588 Author: Pietro Monteiro <[email protected]> Date: Thu Jan 8 07:31:40 2026 -0500 Containerfile for base forge actions Build autoconf and automake and add autoregen.py from https://sourceware.org/git/builder.git Add forge action to build container images. ChangeLog: * .forgejo/workflows/build-containers.yaml: New file. contrib/ChangeLog: * ci-containers/README: New file. * ci-containers/autoregen/Containerfile: New file. * ci-containers/autoregen/autoregen.py: New file. * ci-containers/build-image.sh: New file. Signed-off-by: Pietro Monteiro <[email protected]> Diff: --- .forgejo/workflows/build-containers.yaml | 55 ++++++++++ contrib/ci-containers/README | 47 +++++++++ contrib/ci-containers/autoregen/Containerfile | 83 +++++++++++++++ contrib/ci-containers/autoregen/autoregen.py | 146 ++++++++++++++++++++++++++ contrib/ci-containers/build-image.sh | 104 ++++++++++++++++++ 5 files changed, 435 insertions(+) diff --git a/.forgejo/workflows/build-containers.yaml b/.forgejo/workflows/build-containers.yaml new file mode 100644 index 000000000000..93eaeb7515f4 --- /dev/null +++ b/.forgejo/workflows/build-containers.yaml @@ -0,0 +1,55 @@ +on: +# push: +# branches: +# - trunk +# # run on changes to any file for ci containers or to this file +# paths: +# - .forgejo/workflows/build-containers.yaml +# - 'contrib/ci-containers/**/*' + # similar for pull requests + pull_request: + types: [opened, synchronize, reopened] + paths: + - .forgejo/workflows/build-containers.yaml + - 'contrib/ci-containers/**/*' + +jobs: + containers: + runs-on: sourceware-runner + container: + image: fedora:latest + env: + # the default overlayfs doesn't work when running on docker, which uses overlayfs + STORAGE_DRIVER: vfs + # we can't run containers in docker, so use a chroot to build the image + BUILDAH_ISOLATION: chroot + steps: + - name: install dependencies + run: | + dnf -y --setopt=install_weak_deps=False install buildah git nodejs + + # Checkout sources + - uses: actions/checkout@v4 + + - name: build containers + run: | + echo "Building containers from contrib/ci-containers" + for DIR in ./contrib/ci-containers/* + do + ! [ -d "$DIR" ] && continue + CONTAINER="$(basename "$DIR")" + if [ "$FORGEJO_EVENT_NAME" = pull_request ]; then + # branch name in lowercase, replace non-alphanumerics with '-', and remove leading and trailling '-' + TAG="$(echo "$FORGEJO_HEAD_REF" | sed -e 's/\(.*\)/\L\1/' -e 's/[^[:alnum:]-]/-/g' -e 's/^-\+//;s/-\+$//')" + else + # branch name + TAG="$FORGEJO_REF_NAME" + fi + echo "Building $CONTAINER with tag $TAG" + ./contrib/ci-containers/build-image.sh -d "$DIR" -t "$TAG" -- --network=host + echo "Built $CONTAINER:$TAG should push it somewhere" + buildah images --json "$CONTAINER:$TAG" + echo "Removing container image from localhost" + buildah rmi "$CONTAINER:$TAG" + buildah rmi --prune + done diff --git a/contrib/ci-containers/README b/contrib/ci-containers/README new file mode 100644 index 000000000000..bd985e9ebe4c --- /dev/null +++ b/contrib/ci-containers/README @@ -0,0 +1,47 @@ +# CI Containers + +Each subdirectory under `contrib/ci-containers/` holds a hermetic description of +a container image that powers jobs on the [Sourceware +Forge](https://forge.sourceware.org). The directory itself is used as the build +context, so any assets referenced by the `Containerfile` must be present +in the subdirectory. + +Keeping the description self-contained guarantees reproducible builds. + +## Building Images + +Images are built with [buildah](https://buildah.io) via the helper script +`build-image.sh`. A typical invocation looks like: + +```bash +./contrib/ci-containers/build-image.sh \ + -d ./contrib/ci-containers/foo \ + -t v1.0 \ + -- --layers --no-cache +``` + +* `-d` - Path to the directory containing the `Containerfile`. +* `-t` - Tag to apply to the resulting image. +* The trailing `--` passes additional flags directly to `buildah` (here we +request layered output and disable the cache). + +The full image tag will be the basename of the directory, in this case `foo`, +and the value passed to the `-t/--tag` argument. Our hypothetical image will be +tagged locally as `foo:v1.0`. + +### Verify the build + +```bash +buildah images --json foo:v1.0 +``` + +The command returns a JSON object with the image's ID, size, and other metadata. + +### Test the image locally + +```bash +podman run --rm -it foo:v1.0 /bin/bash +``` + +By running the image interactively you can confirm that the environment behaves +as expected. diff --git a/contrib/ci-containers/autoregen/Containerfile b/contrib/ci-containers/autoregen/Containerfile new file mode 100644 index 000000000000..3cabba827e69 --- /dev/null +++ b/contrib/ci-containers/autoregen/Containerfile @@ -0,0 +1,83 @@ +FROM debian:stable-slim + +ARG AUTOCONF_VERSION=2.69 +ARG AUTOMAKE_VERSION=1.15.1 + +# Run time deps +RUN set -eux; \ + apt-get update; \ + apt-get upgrade -y; \ + apt-get install -y --no-install-recommends \ + autogen \ + ca-certificates \ + git \ + m4 \ + nodejs \ + perl \ + python3 \ + python3-git \ + python3-termcolor \ + python3-unidiff \ + wget; \ + rm -rf /var/lib/apt/lists/* + +# Get and install the autoregen.py script +COPY --chmod=755 autoregen.py /usr/local/bin/autoregen.py + +# Build and install autoconf and automake +# Automake depends on autoconf, which is built and installed first +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + gzip \ + m4 \ + tar \ + wget \ + ; \ + rm -r /var/lib/apt/lists/*; \ + \ + builddir="$(mktemp -d)"; \ + \ + cd "${builddir}"; \ + AUTOCONF_VERSION_SHORT="$(echo $AUTOCONF_VERSION | awk -F. '{ print $1 "." $2 }')"; \ + wget -q "https://ftp.gnu.org/gnu/autoconf/autoconf-${AUTOCONF_VERSION}.tar.gz"; \ + tar xf "autoconf-${AUTOCONF_VERSION}.tar.gz"; \ + cd "autoconf-${AUTOCONF_VERSION}"; \ + ./configure --program-suffix="-${AUTOCONF_VERSION}"; \ + make; \ + make install; \ + cd .. ;\ + rm -rf autoconf*; \ + cd /usr/local/bin; \ + for f in autoconf autoheader autom4te autoreconf autoscan autoupdate ifnames; do \ + ln -sv "$f-$AUTOCONF_VERSION" "$f"; \ + [ ! "$AUTOCONF_VERSION" = "$AUTOCONF_VERSION_SHORT" ] && \ + ln -sv "$f-$AUTOCONF_VERSION" "$f-$AUTOCONF_VERSION_SHORT"; \ + done; \ + \ + cd "${builddir}"; \ + AUTOMAKE_VERSION_SHORT="$(echo $AUTOMAKE_VERSION | awk -F. '{ print $1 "." $2 }')"; \ + wget -q "https://ftp.gnu.org/gnu/automake/automake-${AUTOMAKE_VERSION}.tar.gz"; \ + tar xf "automake-${AUTOMAKE_VERSION}.tar.gz"; \ + cd "automake-${AUTOMAKE_VERSION}"; \ + ./configure --program-suffix="-${AUTOMAKE_VERSION}"; \ + make; \ + make install; \ + cd ..; \ + rm -rf automake*; \ + cd /usr/local/bin; \ + for f in aclocal automake; do \ + ln -sv "$f-$AUTOMAKE_VERSION" "$f"; \ + [ ! "$AUTOMAKE_VERSION" = "$AUTOMAKE_VERSION_SHORT" ] && \ + ln -sv "$f-$AUTOMAKE_VERSION" "$f-$AUTOMAKE_VERSION_SHORT"; \ + done; \ + \ + rm -rf "${builddir}"; \ + \ + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false diff --git a/contrib/ci-containers/autoregen/autoregen.py b/contrib/ci-containers/autoregen/autoregen.py new file mode 100755 index 000000000000..7ac17c1b9dd8 --- /dev/null +++ b/contrib/ci-containers/autoregen/autoregen.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +# This script helps to regenerate files managed by autotools and +# autogen in binutils-gdb and gcc repositories. + +# It can be used by buildbots to check that the current repository +# contents has been updated correctly, and by developers to update +# such files as expected. + +import os +import shutil +import subprocess +from pathlib import Path + + +# On Gentoo, vanilla unpatched autotools are packaged separately. +# We place the vanilla names first as we want to prefer those if both exist. +AUTOCONF_NAMES = ["autoconf-vanilla-2.69", "autoconf-2.69", "autoconf"] +AUTOMAKE_NAMES = ["automake-vanilla-1.15", "automake-1.15.1", "automake"] +ACLOCAL_NAMES = ["aclocal-vanilla-1.15", "aclocal-1.15.1", "aclocal"] +AUTOHEADER_NAMES = ["autoheader-vanilla-2.69", "autoheader-2.69", "autoheader"] +AUTORECONF_NAMES = ["autoreconf-vanilla-2.69", "autoreconf-2.69", "autoreconf"] + +# Pick the first for each list that exists on this system. +AUTOCONF_BIN = next(name for name in AUTOCONF_NAMES if shutil.which(name)) +AUTOMAKE_BIN = next(name for name in AUTOMAKE_NAMES if shutil.which(name)) +ACLOCAL_BIN = next(name for name in ACLOCAL_NAMES if shutil.which(name)) +AUTOHEADER_BIN = next(name for name in AUTOHEADER_NAMES if shutil.which(name)) +AUTORECONF_BIN = next(name for name in AUTORECONF_NAMES if shutil.which(name)) + +AUTOGEN_BIN = "autogen" + +# autoconf-wrapper and automake-wrapper from Gentoo look at this environment variable. +# It's harmless to set it on other systems though. +EXTRA_ENV = { + "WANT_AUTOCONF": AUTOCONF_BIN.split("-", 1)[1] if "-" in AUTOCONF_BIN else "", + "WANT_AUTOMAKE": AUTOMAKE_BIN.split("-", 1)[1] if "-" in AUTOMAKE_BIN else "", + "AUTOCONF": AUTOCONF_BIN, + "ACLOCAL": ACLOCAL_BIN, + "AUTOMAKE": AUTOMAKE_BIN, + "AUTOGEN": AUTOGEN_BIN, +} +ENV = os.environ.copy() +ENV.update(EXTRA_ENV) + + +# Directories we should skip entirely because they're vendored or have different +# autotools versions. +SKIP_DIRS = [ + # readline and minizip are maintained with different autotools versions + "readline", + "minizip", +] + +MANUAL_CONF_DIRS = [ + ".", + # autoreconf does not update aclocal.m4 + "fixincludes", + ] + +# Run the shell command CMD. +# +# Print the command on stdout prior to running it. +def run_shell(cmd: str): + print(f"+ {cmd}", flush=True) + res = subprocess.run( + f"{cmd}", + shell=True, + encoding="utf8", + env=ENV, + ) + res.check_returncode() + + +def regenerate_with_autoreconf(): + run_shell(f"{AUTORECONF_BIN} -f") + +def regenerate_with_autogen(): + run_shell(f"{AUTOGEN_BIN} Makefile.def") + +def regenerate_manually(): + configure_lines = open("configure.ac").read().splitlines() + if folder.stem == "fixincludes" or any( + True for line in configure_lines if line.startswith("AC_CONFIG_MACRO_DIR") + ): + include_arg = "" + include_arg2 = "" + if (folder / ".." / "config").is_dir(): + include_arg = "-I../config" + + if folder.stem == "fixincludes": + include_arg = "-I.." + include_arg2 = "-I../config" + + # aclocal does not support the -f short option for force + run_shell(f"{ACLOCAL_BIN} --force {include_arg} {include_arg2}") + + if (folder / "config.in").is_file() or any( + True for line in configure_lines if line.startswith("AC_CONFIG_HEADERS") + ): + run_shell(f"{AUTOHEADER_BIN} -f") + + # apparently automake is somehow unstable -> skip it for gotools + if any( + True for line in configure_lines if line.startswith("AM_INIT_AUTOMAKE") + ) and not str(folder).endswith("gotools"): + run_shell(f"{AUTOMAKE_BIN} -f") + + run_shell(f"{AUTOCONF_BIN} -f") + + +run_shell(f"{AUTOCONF_BIN} --version") +run_shell(f"{AUTOMAKE_BIN} --version") +run_shell(f"{ACLOCAL_BIN} --version") +run_shell(f"{AUTOHEADER_BIN} --version") + +print(f"Extra environment: {EXTRA_ENV}", flush=True) + +config_folders: list[Path] = [] +autogen_folders: list[Path] = [] +repo_root = Path.cwd() + +for root, _, files in os.walk("."): + for file in files: + if file == "configure.ac": + config_folders.append(Path(root).resolve()) + if file == "Makefile.tpl": + autogen_folders.append(Path(root).resolve()) + +for folder in sorted(autogen_folders): + print(f"Entering directory {folder}", flush=True) + os.chdir(folder) + regenerate_with_autogen() + +for folder in sorted(config_folders): + if folder.stem in SKIP_DIRS: + print(f"Skipping directory {folder}", flush=True) + continue + + print(f"Entering directory {folder}", flush=True) + os.chdir(folder) + + if str(folder.relative_to(repo_root)) in MANUAL_CONF_DIRS: + regenerate_manually() + else: + regenerate_with_autoreconf() diff --git a/contrib/ci-containers/build-image.sh b/contrib/ci-containers/build-image.sh new file mode 100755 index 000000000000..0df8fdfc1f3a --- /dev/null +++ b/contrib/ci-containers/build-image.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# +# Build a container using buildah +# +set -euo pipefail + +usage() { + cat <<EOF +Usage: build-image.sh -d <directory> -t <tag> [-e timestamp] [-- buildah-args...] + +Options: + -d, --dir <path> Directory with the Containerfile (required). + -t, --tag <tag> Tag to apply to the built image (required). + -e, --epoch <ts> Set the "created" timestamp for the built image to this number of seconds since the epoch (optional). + Default is to use the timestamp of the current commit. + Needs buildah 1.41 or newer. + -h, --help Show this help message and exit. + +All arguments after a double-dash (--) are forwarded unchanged to 'buildah'. + +Example: + ./build-image.sh -d src -t v1.0 -- --layers --no-cache +EOF + exit 1 +} + +DIR="" +TAG="" +EXTRA_ARGS=() + +while (( "$#" )); do + case "$1" in + -d|--dir) + if [[ -n "${2-}" ]]; then + DIR="$2" + shift 2 + else + echo "error: --dir requires a value" >&2 + exit 1 + fi + ;; + -t|--tag) + if [[ -n "${2-}" ]]; then + TAG="$2" + shift 2 + else + echo "error: --tag requires a value" >&2 + exit 1 + fi + ;; + -e|--epoch) + if [[ -n "${2-}" ]]; then + SOURCE_DATE_EPOCH="$2" + shift 2 + else + echo "error: --source-date-epoch requires a value" >&2 + exit 1 + fi + ;; + -h|--help) + usage + ;; + --) + shift + EXTRA_ARGS+=("$@") + break + ;; + *) + echo "error: unknown option '$1'" >&2 + usage + ;; + esac +done + +if [[ -z "$DIR" ]]; then + echo "error: directory (-d/--dir) is required" >&2 + usage +fi + +if [[ -z "$TAG" ]]; then + echo "error: Tag (-t/--tag) is required." >&2 + usage +fi + +if [[ ! -e "${DIR}/Containerfile" ]]; then + echo "error: '${DIR}/Containerfile' does not exist." >&2 + usage +fi + +CONTAINER="$(basename "$DIR")" +IMAGE_TAG="${CONTAINER}:${TAG}" + +if [[ -z "${SOURCE_DATE_EPOCH-}" ]]; then + SCRIPT_DIR="$(dirname "$0")" + SOURCE_DATE_EPOCH="$(cd "${SCRIPT_DIR}" && git log -1 --pretty=%ct)" +fi +export SOURCE_DATE_EPOCH + + +buildah build \ + -f "${DIR}/Containerfile" \ + -t "$IMAGE_TAG" \ + "${EXTRA_ARGS[@]}" \ + "$DIR"
