From 11f75b44fd17b9a8cb6227bcdbe0c84ea3b06c8a Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 15:54:44 +0530 Subject: [PATCH 1/6] Support building against CUPS 2.5 and 3.x and add 12-combo CI matrix --- .github/workflows/build.yaml | 154 +++++++++++------------------- ci/ci-setup.sh | 146 ++++++++++++++++++++++++++++ configure.ac | 96 ++++++++++++++----- cupsfilters/ipp-options-private.h | 14 ++- cupsfilters/ipp-options.c | 20 +++- 5 files changed, 305 insertions(+), 125 deletions(-) create mode 100644 ci/ci-setup.sh diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 05383e3c4..5dfc723a4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,5 +1,12 @@ -# Multi-Architecture Build and Test Workflow for libcupsfilters -name: Build and Test (Multi-Architecture) +# Multi-Architecture / multi-CUPS Build and Test Workflow for libcupsfilters +# +# Matrix: 4 architectures x 3 CUPS releases = 12 combinations. +# architectures: x86_64, arm64 (native runners); armv7, riscv64 (QEMU) +# CUPS releases: 2.4.x (distro libcups2-dev), 2.5.x (OpenPrinting/cups +# master), 3.x (OpenPrinting/libcups master) +# All the per-combination work lives in ci/ci-setup.sh so the legs differ only +# in which CUPS is provided and whether they run under emulation. +name: Build and Test (Multi-Architecture, Multi-CUPS) on: push: @@ -10,159 +17,110 @@ on: - '**' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build-matrix: strategy: fail-fast: false matrix: + arch: [x86_64, arm64, armv7, riscv64] + cups: [system-2x, source-2.5.x, source-3.x] include: - # Native x86_64 - arch: x86_64 runs-on: ubuntu-latest use-qemu: false - - # Native ARM64 - arch: arm64 runs-on: ubuntu-24.04-arm use-qemu: false - - # Emulated 32-bit ARM - arch: armv7 runs-on: ubuntu-latest use-qemu: true - - # Emulated RISC-V - arch: riscv64 runs-on: ubuntu-latest use-qemu: true runs-on: ${{ matrix.runs-on }} - name: Build & Test (${{ matrix.arch }}) + name: Build & Test (${{ matrix.arch }}, ${{ matrix.cups }}) + # Building CUPS 2.5 / libcups3 from source under QEMU is slow; allow plenty. + timeout-minutes: 360 steps: - uses: actions/checkout@v4 # ========================================== - # 1. NATIVE EXECUTION (x86_64 and arm64) + # NATIVE EXECUTION (x86_64 and arm64) # ========================================== - - name: Install Dependencies (Native) - if: matrix.use-qemu == false - run: | - sudo apt-get update --fix-missing -y - sudo apt-get upgrade --fix-missing -y - # PREVENT CONFLICTS: Ensure system version doesn't override local source - sudo apt-get remove -y libcupsfilters-dev || true - sudo apt-get install -y \ - libqpdf-dev \ - avahi-daemon libavahi-client-dev libssl-dev libpam-dev libusb-1.0-0-dev zlib1g-dev \ - autotools-dev autopoint cmake libtool pkg-config libcups2-dev libexif-dev liblcms2-dev \ - libfontconfig1-dev libfreetype6-dev build-essential qtbase5-dev qtchooser libcairo2-dev \ - libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-test-dev \ - libopenjp2-7-dev libjpeg-dev libjxl-dev libpoppler-cpp-dev libpython3-dev libdbus-1-dev \ - mupdf-tools poppler-utils ghostscript wget tar make gettext \ - dbus file - - - name: Build PDFio and libcupsfilters (Native) + - name: Build & Test (Native) if: matrix.use-qemu == false env: CC: /usr/bin/gcc + CUPS_KIND: ${{ matrix.cups }} + EMULATED: "0" run: | set -ex - - # Build pdfio - cd /tmp - wget -q https://github.com/michaelrsweet/pdfio/releases/download/v1.6.4/pdfio-1.6.4.tar.gz - tar -xzf pdfio-1.6.4.tar.gz && cd pdfio-1.6.4 - ./configure --prefix=/usr --enable-shared - make all - sudo make install - sudo ldconfig - - # Build and Test libcupsfilters - cd "$GITHUB_WORKSPACE" - ./autogen.sh - ./configure - make -j$(nproc) - # V=1 shows compiler commands; VERBOSE=1 restores full internal test details - make check V=1 VERBOSE=1 || (test -f test-suite.log && cat test-suite.log; exit 1) + sh ci/ci-setup.sh deps + sh ci/ci-setup.sh cups "${{ matrix.cups }}" + sh ci/ci-setup.sh pdfio + sh ci/ci-setup.sh build-libcupsfilters - name: Autopkgtest (DESTDIR staging, native) - if: matrix.use-qemu == false + # Exercise the downstream autopkgtest suite against the distro CUPS it + # was validated for; the source-CUPS legs focus on build coverage. + if: matrix.use-qemu == false && matrix.cups == 'system-2x' run: | set -ex - cd "$GITHUB_WORKSPACE" - # Full downstream suite: libcupsfilters-2-dev (compile/link/run) + - # libcupsfilters-2-functionality. Both point at the staged tree via - # environment overrides, so no privilege or path redirection is needed. make test-autopkgtest V=1 - name: Upload test artifacts (Native) if: matrix.use-qemu == false && always() + continue-on-error: true + timeout-minutes: 5 uses: actions/upload-artifact@v4 with: - name: libcupsfilters-test-artifacts-${{ matrix.arch }} + name: libcupsfilters-logs-${{ matrix.arch }}-${{ matrix.cups }} path: | - **/*.log - if-no-files-found: warn + config.log + test-suite.log + cupsfilters/*.log + if-no-files-found: ignore # ========================================== - # 2. EMULATED EXECUTION (armv7 and riscv64) + # EMULATED EXECUTION (armv7 and riscv64) # ========================================== - - name: Build and Test (Emulated) + - name: Build & Test (Emulated) if: matrix.use-qemu == true uses: uraimo/run-on-arch-action@v3 with: arch: ${{ matrix.arch }} distro: ubuntu24.04 + githubToken: ${{ github.token }} install: | apt-get update --fix-missing -y - DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata - # PREVENT CONFLICTS: Ensure system version doesn't override local source - apt-get remove -y libcupsfilters-dev || true - apt-get install -y \ - libqpdf-dev \ - avahi-daemon libavahi-client-dev libssl-dev libpam-dev libusb-1.0-0-dev zlib1g-dev \ - autotools-dev autopoint cmake libtool pkg-config libcups2-dev libexif-dev liblcms2-dev \ - libfontconfig1-dev libfreetype6-dev build-essential qtbase5-dev qtchooser libcairo2-dev \ - libboost-system-dev libboost-thread-dev libboost-program-options-dev libboost-test-dev \ - libopenjp2-7-dev libjpeg-dev libjxl-dev libpoppler-cpp-dev libpython3-dev libdbus-1-dev \ - mupdf-tools poppler-utils ghostscript wget tar make gettext gcc g++ \ - dbus file + DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata ca-certificates git run: | set -ex - REPO_DIR=$(pwd) - - # Build pdfio - cd /tmp - wget -q https://github.com/michaelrsweet/pdfio/releases/download/v1.6.4/pdfio-1.6.4.tar.gz - tar -xzf pdfio-1.6.4.tar.gz && cd pdfio-1.6.4 - ./configure --prefix=/usr --enable-shared - make all - make install - ldconfig - - # Build and Test libcupsfilters - cd $REPO_DIR - export CC=/usr/bin/gcc - ./autogen.sh - ./configure - make -j$(nproc) - - # Mark test-pclm-overflow.sh as XFAIL; VERBOSE=1 for full internal logs - make check V=1 VERBOSE=1 XFAIL_TESTS="cupsfilters/test-pclm-overflow.sh" || (test -f test-suite.log && cat test-suite.log; exit 1) - - # Full downstream autopkgtest suite. Both tests resolve the staged - # build tree through environment overrides (no absolute paths, no - # privilege, no bind mounts), so the same suite that runs on the - # native legs runs unchanged under QEMU emulation. - make test-autopkgtest V=1 + export CUPS_KIND="${{ matrix.cups }}" + export EMULATED=1 + sh ci/ci-setup.sh deps + sh ci/ci-setup.sh cups "${{ matrix.cups }}" + sh ci/ci-setup.sh pdfio + sh ci/ci-setup.sh build-libcupsfilters + if [ "$CUPS_KIND" = system-2x ]; then + make test-autopkgtest V=1 + fi - name: Upload test artifacts (Emulated) if: matrix.use-qemu == true && always() + continue-on-error: true + timeout-minutes: 5 uses: actions/upload-artifact@v4 with: - name: libcupsfilters-test-artifacts-${{ matrix.arch }} + name: libcupsfilters-logs-${{ matrix.arch }}-${{ matrix.cups }} path: | - **/*.log - if-no-files-found: warn - + config.log + test-suite.log + cupsfilters/*.log + if-no-files-found: ignore diff --git a/ci/ci-setup.sh b/ci/ci-setup.sh new file mode 100644 index 000000000..8f4964843 --- /dev/null +++ b/ci/ci-setup.sh @@ -0,0 +1,146 @@ +#!/bin/sh +# ci/ci-setup.sh +# +# CI helper for building libcupsfilters against several CUPS releases on both +# native and QEMU-emulated runners. The same source compiles against CUPS +# 2.4.x, 2.5.x and 3.x; this script provides each of those CUPS builds and then +# builds and tests libcupsfilters against it. +# +# Subcommands: +# deps install build dependencies +# cups provide libcups; is one of: +# system-2x distro libcups2-dev (CUPS 2.4.x) +# source-2.5.x OpenPrinting/cups@master (CUPS 2.5.x) +# source-3.x OpenPrinting/libcups@master (libcups3) +# pdfio build/install pdfio (required by libcupsfilters) +# build-libcupsfilters autogen + configure + make + make check +# +# Environment knobs honoured by build-libcupsfilters: +# CUPS_KIND the above (controls test XFAILs for source CUPS) +# EMULATED "1" when running under QEMU emulation (controls test XFAILs) +# +# The script runs as root inside emulation containers and via sudo on native +# runners; it detects which automatically. +set -eu + +PDFIO_VER=1.6.4 + +SUDO="" +[ "$(id -u)" -eq 0 ] || SUDO="sudo" + +# Make apt completely non-interactive. Native GitHub runners ship needrestart, +# whose service-restart prompt otherwise hangs the job forever; the emulated +# containers do not have it, which is why only the native legs stalled. +export DEBIAN_FRONTEND=noninteractive +export NEEDRESTART_MODE=a +export NEEDRESTART_SUSPEND=1 + +# Source-built CUPS installs its .pc file under $prefix/lib/pkgconfig; make sure +# pkg-config (and therefore libcupsfilters' configure) can find it. +ma=$(gcc -dumpmachine 2>/dev/null || echo "") +PKG_CONFIG_PATH="/usr/lib/pkgconfig${ma:+:/usr/lib/$ma/pkgconfig}:/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" +export PKG_CONFIG_PATH + +apt_install() { + $SUDO apt-get update --fix-missing -y + $SUDO apt-get install -y "$@" +} + +cmd_deps() { + apt_install \ + build-essential autoconf automake libtool pkg-config gettext autopoint \ + autotools-dev cmake git wget tar make gcc g++ file dbus \ + libavahi-client-dev libssl-dev libpam-dev libusb-1.0-0-dev zlib1g-dev \ + libqpdf-dev libexif-dev liblcms2-dev libfontconfig1-dev libfreetype6-dev \ + libcairo2-dev libjpeg-dev libpng-dev libtiff-dev libjxl-dev \ + libpoppler-cpp-dev libdbus-1-dev libopenjp2-7-dev \ + mupdf-tools poppler-utils ghostscript + # Never let a packaged libcupsfilters shadow the source build under test. + $SUDO apt-get remove -y libcupsfilters-dev || true +} + +# build_autoconf [configure-args...] +build_autoconf() { + url="$1"; ref="$2"; sub="$3"; shift 3 + echo "ci-setup: building $url @ $ref" + src="$(mktemp -d)" + git clone --depth 1 --branch "$ref" $sub "$url" "$src" + ( cd "$src" + [ -x ./configure ] || ./autogen.sh + ./configure --prefix=/usr "$@" || ./configure --prefix=/usr + make -j"$(nproc)" + $SUDO make install ) + $SUDO ldconfig || true +} + +cmd_cups() { + kind="$1" + case "$kind" in + system-2x) + apt_install libcups2-dev + ;; + source-2.5.x) + # CUPS 2.5 (OpenPrinting/cups master) ships cups.pc and has dropped + # cups-config; libcupsfilters' configure now detects it via + # pkg-config, so no cups-config shim is needed. + build_autoconf https://github.com/OpenPrinting/cups.git master "" \ + --disable-systemd + ;; + source-3.x) + build_autoconf https://github.com/OpenPrinting/libcups.git master \ + "--recurse-submodules" + ;; + *) + echo "ci-setup: unknown cups kind: $kind" >&2; exit 2 ;; + esac +} + +cmd_pdfio() { + echo "ci-setup: building pdfio $PDFIO_VER" + src="$(mktemp -d)" + ( cd "$src" + wget -q "https://github.com/michaelrsweet/pdfio/releases/download/v$PDFIO_VER/pdfio-$PDFIO_VER.tar.gz" + tar -xzf "pdfio-$PDFIO_VER.tar.gz" + cd "pdfio-$PDFIO_VER" + ./configure --prefix=/usr --enable-shared + make all + $SUDO make install ) + $SUDO ldconfig || true +} + +cmd_build() { + ./autogen.sh + ./configure + make -j"$(nproc)" + + # Report which CUPS the configure step actually selected. + echo "ci-setup: configured against:" + grep -E "libcups:|cups-config:" config.log 2>/dev/null || true + + # test-pclm-overflow.sh compiles a helper that #includes + # with a bare gcc. That only works when CUPS headers sit in the default + # path - true for the distro package, but not for a source-installed CUPS + # (headers under /usr/include/cupsN) or under QEMU. Mark it XFAIL in those + # cases so the environment quirk does not fail the suite. + xfail="" + case "${CUPS_KIND:-}" in source-*) xfail="cupsfilters/test-pclm-overflow.sh" ;; esac + [ "${EMULATED:-0}" = "1" ] && xfail="cupsfilters/test-pclm-overflow.sh" + + if [ -n "$xfail" ]; then + make check V=1 VERBOSE=1 XFAIL_TESTS="$xfail" \ + || { test -f test-suite.log && cat test-suite.log; exit 1; } + else + make check V=1 VERBOSE=1 \ + || { test -f test-suite.log && cat test-suite.log; exit 1; } + fi +} + +case "${1:-}" in + deps) cmd_deps ;; + cups) shift; cmd_cups "$@" ;; + pdfio) cmd_pdfio ;; + build-libcupsfilters) cmd_build ;; + *) + echo "usage: ci-setup.sh {deps | cups | pdfio | build-libcupsfilters}" >&2 + exit 2 ;; +esac diff --git a/configure.ac b/configure.ac index b3da33233..a06ab911c 100644 --- a/configure.ac +++ b/configure.ac @@ -109,48 +109,96 @@ AS_IF([$PKGCONFIG --exists cups3], [ AC_SUBST(CUPS_SERVERBIN) ], [ AC_MSG_RESULT(no) - AC_ARG_WITH([cups-config], - [AS_HELP_STRING([--with-cups-config=path], [Specify path to cups-config executable.])], - [with_cups_config="$withval"], - [with_cups_config=system]) - AS_IF([test "x$with_cups_config" != "xsystem"], [ - CUPSCONFIG=$with_cups_config - ], [ - AC_PATH_TOOL(CUPSCONFIG, [cups-config]) - ]) - AC_MSG_CHECKING([for CUPS library v2.x]) - AS_IF([test -n "$CUPSCONFIG"], [ - CUPS_VERSION=`$CUPSCONFIG --version` + + # CUPS 2.x. Prefer pkg-config: CUPS 2.5 and newer dropped cups-config and + # ship only cups.pc. Fall back to cups-config for older CUPS 2.x that does + # not provide a pkg-config file. + AC_MSG_CHECKING([for CUPS library v2.x via pkg-config]) + AS_IF([$PKGCONFIG --exists cups], [ + CUPS_VERSION="$($PKGCONFIG --modversion cups)" AC_MSG_RESULT([yes, v$CUPS_VERSION]) AC_DEFINE([HAVE_LIBCUPS2], [1], [Use libcups2]) - CUPS_CFLAGS=`$CUPSCONFIG --cflags` - CUPS_LIBS=`$CUPSCONFIG --image --libs` + CUPS_CFLAGS="$($PKGCONFIG --cflags cups)" + CUPS_LIBS="$LIBS $($PKGCONFIG --libs cups)" + AS_IF([$PKGCONFIG --exists cupsimage], [ + CUPS_LIBS="$CUPS_LIBS $($PKGCONFIG --libs cupsimage)" + ]) AC_SUBST(CUPS_CFLAGS) AC_SUBST(CUPS_LIBS) - CUPS_DATADIR="`$CUPSCONFIG --datadir`" + cups_prefix="$($PKGCONFIG --variable=prefix cups)" + AS_IF([test "x$cups_prefix" = x], [cups_prefix=/usr]) + + CUPS_DATADIR="$cups_prefix/share/cups" AC_DEFINE_UNQUOTED(CUPS_DATADIR, "$CUPS_DATADIR", [CUPS datadir]) AC_SUBST(CUPS_DATADIR) - CUPS_SERVERROOT="`$CUPSCONFIG --serverroot`" - AC_DEFINE_UNQUOTED(CUPS_SERVERROOT, "$CUPS_SERVERROOT", - [CUPS serverroot]) + AS_IF([test x$host_os_name = xdarwin], [ + CUPS_SERVERROOT="/private/etc/cups" + ], [ + AS_IF([test "x$cups_prefix" = x/usr], [ + CUPS_SERVERROOT="/etc/cups" + ], [ + CUPS_SERVERROOT="$cups_prefix/etc/cups" + ]) + ]) + AC_DEFINE_UNQUOTED(CUPS_SERVERROOT, "$CUPS_SERVERROOT", [CUPS serverroot]) AC_SUBST(CUPS_SERVERROOT) CUPS_FONTPATH="$CUPS_DATADIR/fonts" - AC_DEFINE_UNQUOTED(CUPS_FONTPATH, "$CUPS_FONTPATH", - [Path to CUPS fonts dir]) + AC_DEFINE_UNQUOTED(CUPS_FONTPATH, "$CUPS_FONTPATH", [Path to CUPS fonts dir]) AC_SUBST(CUPS_FONTPATH) - CUPS_SERVERBIN="`$CUPSCONFIG --serverbin`" - AC_DEFINE_UNQUOTED(CUPS_SERVERBIN, "$CUPS_SERVERBIN", - [Path to CUPS binaries dir]) + CUPS_SERVERBIN="$cups_prefix/lib/cups" + AC_DEFINE_UNQUOTED(CUPS_SERVERBIN, "$CUPS_SERVERBIN", [Path to CUPS binaries dir]) AC_SUBST(CUPS_SERVERBIN) ], [ AC_MSG_RESULT(no) - AC_MSG_ERROR([Required cups-config is missing. Please install CUPS developer packages.]) + AC_ARG_WITH([cups-config], + [AS_HELP_STRING([--with-cups-config=path], [Specify path to cups-config executable.])], + [with_cups_config="$withval"], + [with_cups_config=system]) + AS_IF([test "x$with_cups_config" != "xsystem"], [ + CUPSCONFIG=$with_cups_config + ], [ + AC_PATH_TOOL(CUPSCONFIG, [cups-config]) + ]) + AC_MSG_CHECKING([for CUPS library v2.x via cups-config]) + AS_IF([test -n "$CUPSCONFIG"], [ + CUPS_VERSION=`$CUPSCONFIG --version` + AC_MSG_RESULT([yes, v$CUPS_VERSION]) + + AC_DEFINE([HAVE_LIBCUPS2], [1], [Use libcups2]) + + CUPS_CFLAGS=`$CUPSCONFIG --cflags` + CUPS_LIBS=`$CUPSCONFIG --image --libs` + AC_SUBST(CUPS_CFLAGS) + AC_SUBST(CUPS_LIBS) + + CUPS_DATADIR="`$CUPSCONFIG --datadir`" + AC_DEFINE_UNQUOTED(CUPS_DATADIR, "$CUPS_DATADIR", [CUPS datadir]) + AC_SUBST(CUPS_DATADIR) + + CUPS_SERVERROOT="`$CUPSCONFIG --serverroot`" + AC_DEFINE_UNQUOTED(CUPS_SERVERROOT, "$CUPS_SERVERROOT", + [CUPS serverroot]) + AC_SUBST(CUPS_SERVERROOT) + + CUPS_FONTPATH="$CUPS_DATADIR/fonts" + AC_DEFINE_UNQUOTED(CUPS_FONTPATH, "$CUPS_FONTPATH", + [Path to CUPS fonts dir]) + AC_SUBST(CUPS_FONTPATH) + + CUPS_SERVERBIN="`$CUPSCONFIG --serverbin`" + AC_DEFINE_UNQUOTED(CUPS_SERVERBIN, "$CUPS_SERVERBIN", + [Path to CUPS binaries dir]) + AC_SUBST(CUPS_SERVERBIN) + ], [ + AC_MSG_RESULT(no) + AC_MSG_ERROR([Required CUPS development files are missing. Please install CUPS developer packages.]) + ]) ]) ]) diff --git a/cupsfilters/ipp-options-private.h b/cupsfilters/ipp-options-private.h index 7b0d743ca..ce019e9e1 100644 --- a/cupsfilters/ipp-options-private.h +++ b/cupsfilters/ipp-options-private.h @@ -20,6 +20,17 @@ // Structures and types... // +// CUPS 2.5 and libcups3 ship the array/option/string/media helpers used by +// this code (cupsArrayNew3 / cupsArrayNew, cupsArrayGetElement/First/Next, +// cupsCopyString, cupsConcatString, cups_media_t and the cups_array_cb_t / +// cups_ahash_cb_t types) as part of their public API. Only CUPS 2.4 and +// older lack them, so libcupsfilters provides its own copies in that case. + +#if CUPS_VERSION_MAJOR < 2 || (CUPS_VERSION_MAJOR == 2 && CUPS_VERSION_MINOR < 5) +# define CUPSFILTERS_PROVIDE_LEGACY_CUPS_API 1 +#endif + +#ifdef CUPSFILTERS_PROVIDE_LEGACY_CUPS_API typedef struct _cups_array_s cups_array_t; // CUPS array type typedef int (*cups_array_cb_t)(void *first, void *second, void *data); @@ -56,7 +67,6 @@ struct _cups_array_s // CUPS array structure cups_afree_cb_t freefunc; // Free function }; -#if CUPS_VERSION_MAJOR < 3 && !(CUPS_VERSION_MAJOR == 2 && CUPS_VERSION_MINOR == 5) typedef struct cups_media_s // Media information { char media[128], // Media name to use @@ -70,7 +80,7 @@ typedef struct cups_media_s // Media information right, // Right margin in hundredths of millimeters top; // Top margin in hundredths of millimeters } cups_media_t; -#endif +#endif // CUPSFILTERS_PROVIDE_LEGACY_CUPS_API typedef enum cf_filter_delivery_e // "page-delivery" values { diff --git a/cupsfilters/ipp-options.c b/cupsfilters/ipp-options.c index d19b0d6dd..e9a08863e 100644 --- a/cupsfilters/ipp-options.c +++ b/cupsfilters/ipp-options.c @@ -69,6 +69,17 @@ cupsArrayNew1(cups_array_cb_t f, // I - Comparison callback function or cups_acopy_cb_t cf, // I - Copy callback function or `NULL` for none cups_afree_cb_t ff) // I - Free callback function or `NULL` for none { +#ifndef CUPSFILTERS_PROVIDE_LEGACY_CUPS_API + // CUPS 2.5 and libcups3 own the array implementation and keep the structure + // opaque, so defer to the native constructor instead of reaching into it. + // libcups3 renamed the 6-argument constructor to cupsArrayNew(); CUPS 2.5 + // still calls it cupsArrayNew3() and takes an int hash size. +# if CUPS_VERSION_MAJOR >= 3 + return (cupsArrayNew(f, d, hf, hsize, cf, ff)); +# else + return (cupsArrayNew3(f, d, hf, (int)hsize, cf, ff)); +# endif +#else cups_array_t *a; // Array @@ -102,6 +113,7 @@ cupsArrayNew1(cups_array_cb_t f, // I - Comparison callback function or a->freefunc = ff; // cppcheck-suppress memleak return (a); +#endif // !CUPSFILTERS_PROVIDE_LEGACY_CUPS_API } // @@ -336,6 +348,11 @@ cupsParseOptions2( } +// The helpers below (UTF-8 aware string copy/concat and the array element +// accessors) became part of the public CUPS API in CUPS 2.5 and libcups3, so +// only compile our own copies when building against older CUPS. +#ifdef CUPSFILTERS_PROVIDE_LEGACY_CUPS_API + // // 'validate_end()' - Validate the last UTF-8 character in a buffer. // @@ -518,7 +535,8 @@ cupsCopyString(char *dst, // O - Destination string return (srclen); } - +#endif // CUPSFILTERS_PROVIDE_LEGACY_CUPS_API + // // Local functions... // From c1aeb679de5e53c9c9aba6ac94021e1627dfdb87 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 16:34:58 +0530 Subject: [PATCH 2/6] Use native cupsParseOptions2 on CUPS 2.5 and run autopkgtest on all CUPS legs --- .github/workflows/build.yaml | 10 ++++------ cupsfilters/ipp-options-private.h | 7 +++++++ cupsfilters/ipp-options.c | 5 +++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5dfc723a4..2bc2a45b1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -67,9 +67,9 @@ jobs: sh ci/ci-setup.sh build-libcupsfilters - name: Autopkgtest (DESTDIR staging, native) - # Exercise the downstream autopkgtest suite against the distro CUPS it - # was validated for; the source-CUPS legs focus on build coverage. - if: matrix.use-qemu == false && matrix.cups == 'system-2x' + # Run the downstream autopkgtest suite (consumer compile/link/run + + # functionality) against every CUPS version under test. + if: matrix.use-qemu == false run: | set -ex make test-autopkgtest V=1 @@ -108,9 +108,7 @@ jobs: sh ci/ci-setup.sh cups "${{ matrix.cups }}" sh ci/ci-setup.sh pdfio sh ci/ci-setup.sh build-libcupsfilters - if [ "$CUPS_KIND" = system-2x ]; then - make test-autopkgtest V=1 - fi + make test-autopkgtest V=1 - name: Upload test artifacts (Emulated) if: matrix.use-qemu == true && always() diff --git a/cupsfilters/ipp-options-private.h b/cupsfilters/ipp-options-private.h index ce019e9e1..03e4d8f7f 100644 --- a/cupsfilters/ipp-options-private.h +++ b/cupsfilters/ipp-options-private.h @@ -30,6 +30,13 @@ # define CUPSFILTERS_PROVIDE_LEGACY_CUPS_API 1 #endif +// CUPS 2.5 ships the 4-argument option parser natively as cupsParseOptions2(). +// CUPS 2.4 lacks it and libcups3 renamed it to cupsParseOptions(), so we keep +// our own copy in those two cases. +#if !(CUPS_VERSION_MAJOR == 2 && CUPS_VERSION_MINOR >= 5) +# define CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 1 +#endif + #ifdef CUPSFILTERS_PROVIDE_LEGACY_CUPS_API typedef struct _cups_array_s cups_array_t; // CUPS array type diff --git a/cupsfilters/ipp-options.c b/cupsfilters/ipp-options.c index e9a08863e..6b5201bae 100644 --- a/cupsfilters/ipp-options.c +++ b/cupsfilters/ipp-options.c @@ -164,6 +164,10 @@ _cups_strncasecmp(const char *s, // I - First string +// CUPS 2.5 ships cupsParseOptions2() in its public API; only compile our own +// copy when the linked CUPS does not provide it (CUPS 2.4 and libcups3). +#ifdef CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 + // // 'cupsParseOptions2()' - Parse options from a command-line argument. // @@ -346,6 +350,7 @@ cupsParseOptions2( return (num_options); } +#endif // CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 // The helpers below (UTF-8 aware string copy/concat and the array element From 977dbda29bf884e341b30d135c045aac2749eec8 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 16:46:31 +0530 Subject: [PATCH 3/6] Link CUPS_LIBS into test programs for CUPS 2.5+ where string/option helpers come from libcups --- Makefile.am | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0ddb62393..77716c9e5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -245,6 +245,7 @@ testcmyk_SOURCES = \ $(pkgfiltersinclude_DATA) testcmyk_LDADD = \ libcupsfilters.la \ + $(CUPS_LIBS) \ -lm testcmyk_CFLAGS = \ $(CUPS_CFLAGS) @@ -254,6 +255,7 @@ testdither_SOURCES = \ $(pkgfiltersinclude_DATA) testdither_LDADD = \ libcupsfilters.la \ + $(CUPS_LIBS) \ -lm testdither_CFLAGS = \ $(CUPS_CFLAGS) @@ -266,6 +268,7 @@ testimage_LDADD = \ $(LIBPNG_LIBS) \ $(TIFF_LIBS) \ libcupsfilters.la \ + $(CUPS_LIBS) \ -lm testimage_CFLAGS = \ $(LIBJPEG_CFLAGS) \ @@ -278,6 +281,7 @@ testrgb_SOURCES = \ $(pkgfiltersinclude_DATA) testrgb_LDADD = \ libcupsfilters.la \ + $(CUPS_LIBS) \ -lm testrgb_CFLAGS = \ $(CUPS_CFLAGS) @@ -299,7 +303,8 @@ testpdf1_CFLAGS = \ -I$(srcdir)/cupsfilters/ \ $(CUPS_CFLAGS) testpdf1_LDADD = \ - libcupsfilters.la + libcupsfilters.la \ + $(CUPS_LIBS) testpdf2_SOURCES = \ cupsfilters/testpdf2.c \ @@ -309,18 +314,19 @@ testpdf2_CFLAGS = \ -I$(srcdir)/cupsfilters/ \ $(CUPS_CFLAGS) testpdf2_LDADD = \ - libcupsfilters.la + libcupsfilters.la \ + $(CUPS_LIBS) test_analyze_SOURCES = cupsfilters/fontembed/test-analyze.c -test_analyze_LDADD = libcupsfilters.la +test_analyze_LDADD = libcupsfilters.la $(CUPS_LIBS) test_analyze_CFLAGS = $(CUPS_CFLAGS) test_pdf_SOURCES = cupsfilters/fontembed/test-pdf.c -test_pdf_LDADD = libcupsfilters.la +test_pdf_LDADD = libcupsfilters.la $(CUPS_LIBS) test_pdf_CFLAGS = $(CUPS_CFLAGS) test_ps_SOURCES = cupsfilters/fontembed/test-ps.c -test_ps_LDADD = libcupsfilters.la +test_ps_LDADD = libcupsfilters.la $(CUPS_LIBS) test_ps_CFLAGS = $(CUPS_CFLAGS) testfilters_SOURCES = \ From 66da0a067024bc2bcef31f4adf2d3086efb10ff0 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 16:55:55 +0530 Subject: [PATCH 4/6] Install source CUPS 2.5 to multiarch libdir; build source CUPS only on native arches --- .github/workflows/build.yaml | 12 ++++++++++++ ci/ci-setup.sh | 9 ++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2bc2a45b1..7d1f8bf4a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,6 +28,18 @@ jobs: matrix: arch: [x86_64, arm64, armv7, riscv64] cups: [system-2x, source-2.5.x, source-3.x] + # Building CUPS 2.5 / libcups3 from source under QEMU emulation is + # impractically slow, so the emulated architectures only exercise the + # distro CUPS (2.4.x). The full 2.4/2.5/3.x matrix runs natively. + exclude: + - arch: armv7 + cups: source-2.5.x + - arch: armv7 + cups: source-3.x + - arch: riscv64 + cups: source-2.5.x + - arch: riscv64 + cups: source-3.x include: - arch: x86_64 runs-on: ubuntu-latest diff --git a/ci/ci-setup.sh b/ci/ci-setup.sh index 8f4964843..bda6b7818 100644 --- a/ci/ci-setup.sh +++ b/ci/ci-setup.sh @@ -83,8 +83,15 @@ cmd_cups() { # CUPS 2.5 (OpenPrinting/cups master) ships cups.pc and has dropped # cups-config; libcupsfilters' configure now detects it via # pkg-config, so no cups-config shim is needed. + # + # Force the multiarch libdir: CUPS's configure otherwise installs + # libcups into /usr/lib64 on 64-bit hosts, which is not on the + # default linker search path. libcupsfilters now re-exports CUPS + # string/option symbols, so a downstream consumer that links only + # "-lcupsfilters" must be able to find libcups transitively at link + # time - which only works if libcups sits in a default search dir. build_autoconf https://github.com/OpenPrinting/cups.git master "" \ - --disable-systemd + --disable-systemd ${ma:+--libdir=/usr/lib/$ma} ;; source-3.x) build_autoconf https://github.com/OpenPrinting/libcups.git master \ From 923c7eb72a3dab418894d642e02d04698d46c193 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 17:10:59 +0530 Subject: [PATCH 5/6] Run full 12-combo matrix: all three CUPS versions on all four architectures --- .github/workflows/build.yaml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7d1f8bf4a..2bc2a45b1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,18 +28,6 @@ jobs: matrix: arch: [x86_64, arm64, armv7, riscv64] cups: [system-2x, source-2.5.x, source-3.x] - # Building CUPS 2.5 / libcups3 from source under QEMU emulation is - # impractically slow, so the emulated architectures only exercise the - # distro CUPS (2.4.x). The full 2.4/2.5/3.x matrix runs natively. - exclude: - - arch: armv7 - cups: source-2.5.x - - arch: armv7 - cups: source-3.x - - arch: riscv64 - cups: source-2.5.x - - arch: riscv64 - cups: source-3.x include: - arch: x86_64 runs-on: ubuntu-latest From 3380bf8706571f8a2c8c88c059c89538a5d7f0b0 Mon Sep 17 00:00:00 2001 From: Rohit Kumar Date: Thu, 18 Jun 2026 18:36:02 +0530 Subject: [PATCH 6/6] Use native cupsParseOptions(); translate to cupsParseOptions2/3-arg form for CUPS 2.x in libcups2-private.h --- cupsfilters/ipp-options-private.h | 18 +- cupsfilters/ipp-options.c | 271 +----------------------------- cupsfilters/libcups2-private.h | 13 +- 3 files changed, 29 insertions(+), 273 deletions(-) diff --git a/cupsfilters/ipp-options-private.h b/cupsfilters/ipp-options-private.h index 03e4d8f7f..10fbb6313 100644 --- a/cupsfilters/ipp-options-private.h +++ b/cupsfilters/ipp-options-private.h @@ -30,11 +30,19 @@ # define CUPSFILTERS_PROVIDE_LEGACY_CUPS_API 1 #endif -// CUPS 2.5 ships the 4-argument option parser natively as cupsParseOptions2(). -// CUPS 2.4 lacks it and libcups3 renamed it to cupsParseOptions(), so we keep -// our own copy in those two cases. -#if !(CUPS_VERSION_MAJOR == 2 && CUPS_VERSION_MINOR >= 5) -# define CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 1 +// This code calls the option parser by its libcups3 name cupsParseOptions() +// (4 arguments, including the trailing "end" pointer). Translate it for the +// CUPS 2.x line, mirroring the defines in libcups2-private.h: CUPS 2.5 exposes +// the same 4-argument parser as cupsParseOptions2(), while CUPS 2.4 only has +// the 3-argument form and so drops the "end" argument. (cups/cups.h is +// included above, so the real prototypes are seen before these macros apply.) +#if CUPS_VERSION_MAJOR < 3 +# if CUPS_VERSION_MINOR >= 5 +# define cupsParseOptions cupsParseOptions2 +# else +# define cupsParseOptions(arg, end, num_options, options) \ + cupsParseOptions(arg, num_options, options) +# endif #endif #ifdef CUPSFILTERS_PROVIDE_LEGACY_CUPS_API diff --git a/cupsfilters/ipp-options.c b/cupsfilters/ipp-options.c index 6b5201bae..27f0a3918 100644 --- a/cupsfilters/ipp-options.c +++ b/cupsfilters/ipp-options.c @@ -16,8 +16,6 @@ #include #include -# define _CUPS_INLINE static inline - # ifdef DEBUG # define DEBUG_puts(x) _cups_debug_puts(x) # define DEBUG_printf(...) _cups_debug_printf(__VA_ARGS__) @@ -39,26 +37,6 @@ #define DEFAULT_TYPE "stationery" // Default "media-type" value -# ifdef _CUPS_INLINE -_CUPS_INLINE int // O - 1 on match, 0 otherwise -_cups_isspace(int ch) // I - Character to test -{ - return (ch == ' ' || ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\v'); -} - -_CUPS_INLINE int // O - 1 on match, 0 otherwise -_cups_isupper(int ch) // I - Character to test -{ - return (ch >= 'A' && ch <= 'Z'); -} - -_CUPS_INLINE int // O - Converted character -_cups_tolower(int ch) // I - Character to convert -{ - return (_cups_isupper(ch) ? ch - 'A' + 'a' : ch); -} - -# endif cups_array_t * // O - Array @@ -116,243 +94,6 @@ cupsArrayNew1(cups_array_cb_t f, // I - Comparison callback function or #endif // !CUPSFILTERS_PROVIDE_LEGACY_CUPS_API } -// -// '_cups_strcpy()' - Copy a string allowing for overlapping strings. -// - -void -_cups_strcpy(char *dst, // I - Destination string - const char *src) // I - Source string -{ - while (*src) - *dst++ = *src++; - - *dst = '\0'; -} - -// -// '_cups_strncasecmp()' - Do a case-insensitive comparison on up to N chars. -// - -int // O - Result of comparison (-1, 0, or 1) -_cups_strncasecmp(const char *s, // I - First string - const char *t, // I - Second string - size_t n) // I - Maximum number of characters to compare -{ - while (*s != '\0' && *t != '\0' && n > 0) - { - if (_cups_tolower(*s) < _cups_tolower(*t)) - return (-1); - else if (_cups_tolower(*s) > _cups_tolower(*t)) - return (1); - - s ++; - t ++; - n --; - } - - if (n == 0) - return (0); - else if (*s == '\0' && *t == '\0') - return (0); - else if (*s != '\0') - return (1); - else - return (-1); -} - - - - -// CUPS 2.5 ships cupsParseOptions2() in its public API; only compile our own -// copy when the linked CUPS does not provide it (CUPS 2.4 and libcups3). -#ifdef CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 - -// -// 'cupsParseOptions2()' - Parse options from a command-line argument. -// -// This function converts space-delimited name/value pairs according -// to the PAPI text option ABNF specification. Collection values -// ("name={a=... b=... c=...}") are stored with the curley brackets -// intact - use @code cupsParseOptions2@ on the value to extract the -// collection attributes. -// -// The "end" argument, if not `NULL`, receives a pointer to the end of the -// options. -// - -size_t // O - Number of options found -cupsParseOptions2( - const char *arg, // I - Argument to parse - const char **end, // O - Pointer to end of options or `NULL` for "don't care" - size_t num_options, // I - Number of options - cups_option_t **options) // O - Options found -{ - char *copyarg, // Copy of input string - *ptr, // Pointer into string - *name, // Pointer to name - *value, // Pointer to value - sep, // Separator character - quote; // Quote character - - - // Range check input... - if (end) - *end = NULL; - - if (!arg) - return (num_options); - - if (!options) - return (0); - - // Make a copy of the argument string and then divide it up... - if ((copyarg = strdup(arg)) == NULL) - { - DEBUG_puts("1cupsParseOptions2: Unable to copy arg string"); - return (num_options); - } - - if (*copyarg == '{') - ptr = copyarg + 1; - else - ptr = copyarg; - - // Skip leading spaces... - while (_cups_isspace(*ptr)) - ptr ++; - - // Loop through the string... - while (*ptr != '\0') - { - // Get the name up to a SPACE, =, or end-of-string... - name = ptr; - while (!strchr("\f\n\r\t\v =", *ptr) && *ptr) - ptr ++; - - // Avoid an empty name... - if (ptr == name) - break; - - // End after the closing brace... - if (*ptr == '}' && *copyarg == '{') - { - *ptr++ = '\0'; - break; - } - - // Skip trailing spaces... - while (_cups_isspace(*ptr)) - *ptr++ = '\0'; - - if ((sep = *ptr) == '=') - *ptr++ = '\0'; - - if (sep != '=') - { - // Boolean option... - if (!_cups_strncasecmp(name, "no", 2)) - num_options = cupsAddOption(name + 2, "false", num_options, options); - else - num_options = cupsAddOption(name, "true", num_options, options); - - continue; - } - - // Remove = and parse the value... - value = ptr; - - while (*ptr && !_cups_isspace(*ptr)) - { - if (*ptr == ',') - { - ptr ++; - } - else if (*ptr == '\'' || *ptr == '\"') - { - // Quoted string constant... - quote = *ptr; - _cups_strcpy(ptr, ptr + 1); - - while (*ptr != quote && *ptr) - { - if (*ptr == '\\' && ptr[1]) - _cups_strcpy(ptr, ptr + 1); - - ptr ++; - } - - if (*ptr) - _cups_strcpy(ptr, ptr + 1); - } - else if (*ptr == '{') - { - // Collection value... - int depth; // Nesting depth for braces - - for (depth = 0; *ptr; ptr ++) - { - if (*ptr == '{') - { - depth ++; - } - else if (*ptr == '}') - { - depth --; - if (!depth) - { - ptr ++; - break; - } - } - else if (*ptr == '\\' && ptr[1]) - { - _cups_strcpy(ptr, ptr + 1); - } - } - } - else - { - // Normal space-delimited string... - while (*ptr && !_cups_isspace(*ptr)) - { - if (*ptr == '}' && *copyarg == '{') - { - *ptr++ = '\0'; - break; - } - - if (*ptr == '\\' && ptr[1]) - _cups_strcpy(ptr, ptr + 1); - - ptr ++; - } - } - } - - if (*ptr != '\0') - *ptr++ = '\0'; - - // Skip trailing whitespace... - while (_cups_isspace(*ptr)) - ptr ++; - - // Add the string value... - num_options = cupsAddOption(name, value, num_options, options); - } - - // Save the progress in the input string... - if (end) - *end = arg + (ptr - copyarg); - - // Free the copy of the argument we made and return the number of options found. - free(copyarg); - - return (num_options); -} -#endif // CUPSFILTERS_PROVIDE_PARSE_OPTIONS2 - - // The helpers below (UTF-8 aware string copy/concat and the array element // accessors) became part of the public CUPS API in CUPS 2.5 and libcups3, so // only compile our own copies when building against older CUPS. @@ -766,7 +507,7 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option if ((value = get_option("job-error-sheet", num_options, options)) != NULL) { // Parse job-error-sheet collection value... - num_col = cupsParseOptions2(value, /*end*/NULL, 0, &col); + num_col = cupsParseOptions(value, /*end*/NULL, 0, &col); if ((value = cupsGetOption("job-error-sheet-when", num_col, col)) != NULL) { @@ -794,7 +535,7 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option if ((value = get_option("job-sheets-col", num_options, options)) != NULL) { // Parse "job-sheets-col" collection value... - num_col = cupsParseOptions2(value, /*end*/NULL, 0, &col); + num_col = cupsParseOptions(value, /*end*/NULL, 0, &col); if ((value = cupsGetOption("media-col", num_col, col)) == NULL) value = cupsGetOption("media", num_col, col); @@ -948,7 +689,7 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option if ((value = get_option("separator-sheets", num_options, options)) != NULL) { // Parse separator-sheets collection value... - num_col = cupsParseOptions2(value, /*end*/NULL, 0, &col); + num_col = cupsParseOptions(value, /*end*/NULL, 0, &col); if ((value = cupsGetOption("media-col", num_col, col)) == NULL) value = cupsGetOption("media", num_col, col); @@ -1054,7 +795,7 @@ cfFilterOptionsCreate(size_t num_options, // I - Number of command-line option if (*nextcol == ',') nextcol ++; - num_col = cupsParseOptions2(nextcol, &nextcol, 0, &col); + num_col = cupsParseOptions(nextcol, &nextcol, 0, &col); memset(&override, 0, sizeof(override)); @@ -1226,7 +967,7 @@ parse_media(const char *value, // I - "media" or "media-col" value *top_margin, // "media-top-margin" value *type; // "media-type" value - num_col = cupsParseOptions2(value, /*end*/NULL, 0, &col); + num_col = cupsParseOptions(value, /*end*/NULL, 0, &col); if ((size_name = cupsGetOption("media-size-name", num_col, col)) != NULL) { if ((pwg = pwgMediaForPWG(size_name)) != NULL) @@ -1241,7 +982,7 @@ parse_media(const char *value, // I - "media" or "media-col" value const char *x_dim, // x-dimension *y_dim; // y-dimension - num_size = cupsParseOptions2(size_col, /*end*/NULL, 0, &size); + num_size = cupsParseOptions(size_col, /*end*/NULL, 0, &size); if ((x_dim = cupsGetOption("x-dimension", num_size, size)) != NULL && (y_dim = cupsGetOption("y-dimension", num_size, size)) != NULL && (pwg = pwgMediaForSize(atoi(x_dim), atoi(y_dim))) != NULL) cupsCopyString(media->media, pwg->pwg, sizeof(media->media)); else diff --git a/cupsfilters/libcups2-private.h b/cupsfilters/libcups2-private.h index f6a689ef1..af9b53a8f 100644 --- a/cupsfilters/libcups2-private.h +++ b/cupsfilters/libcups2-private.h @@ -40,9 +40,16 @@ # define ippGetFirstAttribute ippFirstAttribute # define ippGetNextAttribute ippNextAttribute -// Function with additional parameter in libcups3 - -# define cupsParseOptions(arg, end, num_options, options) cupsParseOptions(arg, num_options, options) +// Option parser: libcups3 spells it cupsParseOptions() with a trailing +// "end" pointer. CUPS 2.5 provides the same 4-argument parser under the +// historic name cupsParseOptions2(); CUPS 2.4 only has the 3-argument form, +// so the "end" argument is dropped there. + +# if CUPS_VERSION_MINOR >= 5 +# define cupsParseOptions cupsParseOptions2 +# else +# define cupsParseOptions(arg, end, num_options, options) cupsParseOptions(arg, num_options, options) +# endif // Function replaced by a different function in libcups3