diff --git a/Dockerfile.go-server b/Dockerfile.go-server index b3bbc122f2..2a869e39eb 100644 --- a/Dockerfile.go-server +++ b/Dockerfile.go-server @@ -100,6 +100,9 @@ RUN mkdir -p /dist/lib/pkgconfig /dist/bin /dist/model \ && find /build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) \ \( -type f -o -type l \) -exec cp -a {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp /build/meson-private/libvmaf.pc /dist/lib/pkgconfig/libvmaf.pc \ && cp /build/meson-private/libvmafx.pc /dist/lib/pkgconfig/libvmafx.pc \ && cp /build/tools/vmaf /dist/bin/ \ @@ -181,8 +184,10 @@ LABEL org.opencontainers.image.source="https://github.com/VMAFx/vmafx" LABEL org.opencontainers.image.licenses="EUPL-1.2 AND BSD-2-Clause-Patent AND BSD-3-Clause AND BSD-2-Clause AND ISC AND MIT AND Unlicense AND LicenseRef-LIVE-BRISQUE AND WTFPL" LABEL org.opencontainers.image.vendor="Lusoris" -# Copy libvmaf.so* and the vmaf CLI from the vmaf-builder stage. +# Copy libvmaf.so* and the vmaf CLI from the vmaf-builder stage, with the Debian +# record of the libzimg2 the zimg component reads (issue #3242). COPY --from=vmaf-builder /dist/lib/ /usr/local/lib/ +COPY --from=vmaf-builder /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=vmaf-builder /dist/bin/ /usr/local/bin/ COPY --from=vmaf-builder /dist/model/ /usr/local/share/vmafx/model/ diff --git a/changelog.d/fixed/production-zimg-debian-record.md b/changelog.d/fixed/production-zimg-debian-record.md new file mode 100644 index 0000000000..7a85b01d3a --- /dev/null +++ b/changelog.d/fixed/production-zimg-debian-record.md @@ -0,0 +1,12 @@ +- **The production images credit the libzimg they ship.** The `vmaf` command now links zimg by + default ([ADR-3396](../docs/adr/3396-zimg-on-by-default.md)), so every production image copies + `libzimg.so*` out of Debian's `libzimg2` package — but the `zimg` entry of + `tools/rc1-tester/image/licensing.json` still described upstream zimg 3.0.6 and claimed no file in + the image, so the six builders that copy that library had no Debian record for it (issue #3242). + The `zimg` entry of `production-cli-image` now reads the package's Debian record exactly as the + tester entry already does: each builder stages `usr/share/doc/libzimg2/copyright` plus a + `dpkg-query` `SOURCE` line, the runtime stages install both at `/usr/share/doc/libzimg2`, and the + eight production records that inherit the component follow. The image's source offer names + `libzimg2`'s Debian source package instead of upstream zimg 3.0.6, and + `tools/rc1-tester/tests/test_dockerfile_runtime_libs.py` holds all six builders and the nine + published runtime stages to it. diff --git a/docker/AGENTS.md b/docker/AGENTS.md index a14b9c6403..8ea79e993d 100644 --- a/docker/AGENTS.md +++ b/docker/AGENTS.md @@ -143,6 +143,31 @@ stage Meson's generated `meson-private/libvmaf.pc`; never synthesize it from `scripts/ci/test_e2e_runtime_contract.py` pins these invariants for both Dockerfiles. +## Debian zimg record per copying builder (issue #3242) + +`libzimg.so*` copied from `/usr/lib/*-linux-gnu*` = Debian `libzimg2`, never +upstream 3.0.6 archive. So `zimg` component of `production-cli-image` reads +`debian_source_file: usr/share/doc/libzimg2/SOURCE` and takes package copyright +file as licence text, same as tester entry. + +- Every copying builder (`Dockerfile.go-server`, `docker/Dockerfile.controller`, + `.node`, `.production`, `.production-gpu`) and shared + `scripts/ci/build-oneapi-libvmaf.sh` (both oneAPI images) stage `copyright` + + `dpkg-query` `SOURCE` line under `/dist/doc/libzimg2`. New copying builder: same. +- Every runtime stage publishing `libzimg.so*` installs that directory at + `/usr/share/doc/libzimg2`. +- Missing record -> image `licensing.py sources` fails (source offer names no + Debian package without record); notice names version image does not ship. +- `tools/rc1-tester/tests/test_dockerfile_runtime_libs.py` pins builders, + published runtime stages (follows each `FROM` chain) and shared script. Copy + sites discovered from `COPY_SOURCES`, not listed: stage starting to copy Debian + library fails contract by name until declared. +- One audited build-only exception: `docker/Dockerfile.tester` `vmaf-build`. + `runtime` overwrites `/opt/vmafx/lib` from `python-venv` (carries record) before + install; test proves order. +- macOS bundle, Windows zips, `release-native` build zimg from source; keep 3.0.6 + archive entry. + ## Fedora optional SYCL repository `dev/fedora-40.Dockerfile` writes seven literal oneAPI repository lines with diff --git a/docker/Dockerfile.controller b/docker/Dockerfile.controller index 6d078c15de..57374782c9 100644 --- a/docker/Dockerfile.controller +++ b/docker/Dockerfile.controller @@ -96,6 +96,9 @@ RUN mkdir -p /dist/lib/pkgconfig /dist/bin /dist/model \ && find /build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) \ \( -type f -o -type l \) -exec cp -a {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp /build/meson-private/libvmaf.pc /dist/lib/pkgconfig/libvmaf.pc \ && cp /build/meson-private/libvmafx.pc /dist/lib/pkgconfig/libvmafx.pc \ && cp /build/tools/vmaf /dist/bin/ \ @@ -180,6 +183,7 @@ LABEL org.opencontainers.image.vendor="Lusoris" # Copy libvmaf.so* and the vmaf CLI from the vmaf-builder stage. COPY --from=vmaf-builder /dist/lib/ /usr/local/lib/ +COPY --from=vmaf-builder /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=vmaf-builder /dist/bin/ /usr/local/bin/ COPY --from=vmaf-builder /dist/model/ /usr/local/share/vmafx/model/ diff --git a/docker/Dockerfile.node b/docker/Dockerfile.node index 122f686415..97e93f11a1 100644 --- a/docker/Dockerfile.node +++ b/docker/Dockerfile.node @@ -182,6 +182,9 @@ RUN mkdir -p /dist/lib/pkgconfig /dist/bin /dist/model \ && find /build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) \ \( -type f -o -type l \) -exec cp -a {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp /build/meson-private/libvmaf.pc /dist/lib/pkgconfig/libvmaf.pc \ && cp /build/meson-private/libvmafx.pc /dist/lib/pkgconfig/libvmafx.pc \ && cp /build/tools/vmaf /dist/bin/ \ @@ -471,8 +474,10 @@ ENV VMAFX_MODEL_DIR="/usr/local/share/vmafx/model" # Canonical ffmpeg path consumed by the node binary and its encoder probe. ENV VMAFX_FFMPEG_BIN="/usr/local/bin/ffmpeg" -# Copy libvmaf.so* and models +# Copy libvmaf.so* and models, with the Debian record of the libzimg2 the zimg +# component reads (issue #3242). COPY --from=vmaf-builder /dist/lib/ /usr/local/lib/ +COPY --from=vmaf-builder /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=vmaf-builder /dist/model/ /usr/local/share/vmafx/model/ COPY --from=vmaf-builder /dist/bin/vmaf /usr/local/bin/vmaf # pkg/storage resolves s3://, gs://, rclone:// and remote:path inputs by @@ -783,7 +788,9 @@ FROM node-assembled AS node-sycl-assembled # The Intel GPU user-mode stack and its dpkg records (ADR-3371) COPY --from=intel-gpu-runtime-libs /out/ / -# The oneAPI SYCL build of libvmaf, CLI, and Intel SYCL runtime files (ADR-3371) +# The oneAPI SYCL build of libvmaf, CLI, and Intel SYCL runtime files (ADR-3371). +# The libzimg2 Debian record needs no copy here: node-sycl-assembled inherits +# runtime-base, which installs it (issue #3242). COPY --from=builder-oneapi2026 /dist/lib/ /usr/local/lib/ COPY --from=builder-oneapi2026 /dist/bin/vmaf /usr/local/bin/vmaf COPY --from=builder-oneapi2026 /stage/lib/intel/ /usr/local/lib/intel/ diff --git a/docker/Dockerfile.production b/docker/Dockerfile.production index 91cf5babb5..6763553661 100644 --- a/docker/Dockerfile.production +++ b/docker/Dockerfile.production @@ -84,6 +84,9 @@ RUN mkdir -p /dist/lib /dist/bin /dist/model \ && find /build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) -not -type d \ -exec cp {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp /build/tools/vmaf /dist/bin/ \ && cp -r model/. /dist/model/ @@ -153,6 +156,7 @@ LABEL org.opencontainers.image.vendor="Lusoris" # Copy libvmaf.so* into /usr/local/lib and set up symlinks COPY --from=builder /dist/lib/ /usr/local/lib/ +COPY --from=builder /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=builder /dist/model/ /usr/local/share/vmafx/model/ ENV LD_LIBRARY_PATH="/usr/local/lib" @@ -253,6 +257,7 @@ LABEL org.opencontainers.image.licenses="EUPL-1.2 AND BSD-2-Clause-Patent AND BS LABEL org.opencontainers.image.vendor="Lusoris" COPY --from=builder /dist/lib/ /usr/local/lib/ +COPY --from=builder /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=builder /dist/model/ /usr/local/share/vmafx/model/ COPY --from=builder /dist/bin/vmaf /usr/local/bin/vmaf diff --git a/docker/Dockerfile.production-gpu b/docker/Dockerfile.production-gpu index 1e0f37d410..fd41c7de22 100644 --- a/docker/Dockerfile.production-gpu +++ b/docker/Dockerfile.production-gpu @@ -128,6 +128,9 @@ RUN meson setup build core --buildtype=release --strip -Db_lto=false \ RUN mkdir -p /dist/lib /dist/bin /dist/model /stage/licenses/nvidia /licence-inputs \ && find build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) -not -type d -exec cp -a {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp build/tools/vmaf /dist/bin/ \ && cp -r model/. /dist/model/ \ && for binary in /dist/bin/vmaf /dist/lib/libvmaf.so.* /dist/lib/libvmafx.so.*; do \ @@ -180,6 +183,9 @@ RUN gfx_targets="$(jq -r '.dist_amdgpu_targets' /opt/rocm/share/therock/dist_inf RUN mkdir -p /dist/lib /dist/bin /dist/model /licence-inputs \ && find build/src -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) -not -type d -exec cp -a {} /dist/lib/ \; \ && cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \ + && mkdir -p /dist/doc/libzimg2 \ + && cp /usr/share/doc/libzimg2/copyright /dist/doc/libzimg2/copyright \ + && dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 > /dist/doc/libzimg2/SOURCE \ && cp build/tools/vmaf /dist/bin/ \ && cp -r model/. /dist/model/ \ && python3 tools/rc1-tester/image/prepare_build.py rocm-runtime \ @@ -277,6 +283,7 @@ ENV LD_LIBRARY_PATH="/usr/local/lib:/usr/lib/wsl/lib" \ NVIDIA_DRIVER_CAPABILITIES=compute,utility \ VMAF_MODEL_PATH="/usr/local/share/vmafx/model" COPY --from=builder-cuda13 /dist/lib/ /usr/local/lib/ +COPY --from=builder-cuda13 /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=builder-cuda13 /dist/bin/ /usr/local/bin/ COPY --from=builder-cuda13 /dist/model/ /usr/local/share/vmafx/model/ COPY --from=builder-cuda13 /stage/licenses/ /usr/local/share/vmafx/licenses/ @@ -385,6 +392,7 @@ LABEL org.opencontainers.image.vendor="Lusoris" ENV LD_LIBRARY_PATH="/usr/local/lib:/usr/local/lib/rocm" \ VMAF_MODEL_PATH="/usr/local/share/vmafx/model" COPY --from=builder-rocm10 /dist/lib/ /usr/local/lib/ +COPY --from=builder-rocm10 /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=builder-rocm10 /dist/bin/ /usr/local/bin/ COPY --from=builder-rocm10 /dist/model/ /usr/local/share/vmafx/model/ COPY --from=builder-rocm10 /stage/lib/rocm/ /usr/local/lib/rocm/ @@ -521,6 +529,7 @@ RUN --mount=type=secret,id=github_token,env=GITHUB_TOKEN,required=false \ ENV LD_LIBRARY_PATH="/usr/local/lib:/usr/local/lib/intel:/usr/lib/wsl/lib" \ VMAF_MODEL_PATH="/usr/local/share/vmafx/model" COPY --from=builder-oneapi2026 /dist/lib/ /usr/local/lib/ +COPY --from=builder-oneapi2026 /dist/doc/libzimg2 /usr/share/doc/libzimg2 COPY --from=builder-oneapi2026 /dist/bin/ /usr/local/bin/ COPY --from=builder-oneapi2026 /dist/model/ /usr/local/share/vmafx/model/ COPY --from=builder-oneapi2026 /stage/lib/intel/ /usr/local/lib/intel/ diff --git a/docs/state.md b/docs/state.md index 441a6afeaf..de8b630d8e 100644 --- a/docs/state.md +++ b/docs/state.md @@ -1176,6 +1176,7 @@ landed fix yet._ ## Recently closed +| **T-PRODUCTION-ZIMG-DEBIAN-RECORD-2026-11-10** — the production licence manifest named upstream zimg 3.0.6 while every production image ships Debian's `libzimg2` 3.0.5+ds1-1+b2, so the generated notice named the wrong version and source, and six builders copied `libzimg.so*` out of `/usr/lib/*-linux-gnu*` with nothing in the image the `zimg` component could point at | **FIXED on `fix/issue-3242-zimg-debian-source` (2026-11-11).** Each of the six builder lines the maintainer named (`docker/Dockerfile.production`, `docker/Dockerfile.production-gpu` twice, `docker/Dockerfile.controller`, `Dockerfile.go-server`, `docker/Dockerfile.node`) and the shared `scripts/ci/build-oneapi-libvmaf.sh` behind the two oneAPI images stores `/usr/share/doc/libzimg2/copyright` plus a `dpkg-query` `SOURCE` record under `/dist/doc/libzimg2`; the nine published runtime stages install both at `/usr/share/doc/libzimg2`; the `zimg` entry of `production-cli-image` reads them through `debian_source_file` and an `artifact` text, and the eight production records that inherit it follow (ADR-1517). `tools/rc1-tester/tests/test_dockerfile_runtime_libs.py` now **discovers** the copy sites instead of listing them, which surfaced a seventh site the old table had never covered: `docker/Dockerfile.tester`'s `vmaf-build` copies libzimg and its `/opt/vmafx` reaches the published image through `runtime`'s `COPY --from=vmaf-build /opt/vmafx /opt/vmafx`, so a test proves `runtime` overwrites `/opt/vmafx/lib` from `python-venv` — which does carry the record — before installing it (an audited build-only exclusion, not a hole). On master 8 of 12 copy sites and 9 of 13 runtime stages fail, on the fix all pass; the macOS, Windows and `release-native` entries stay on the 3.0.6 archive, which each of those links itself. | #3242 | 2026-11-10 | closed | | **T-CI-PRECOMMIT-DIFF-SCOPE-HOOK-INPUT-2026-11-01** — #3237 added the hook `check-fork-python-subprocess` and its script; the Pre-Commit job ran on the pull request's diff only (`--from-ref`), so the new hook never saw the files it flags elsewhere in the tree, the pull request passed, and master `a3ea48309` went red on the whole-tree run of the push | **FIXED on `fix/precommit-scope-config-change` (#3253).** `scripts/ci/precommit_scope.py` runs the whole tree when a pull request changes a hook input: `.pre-commit-config.yaml`, any file under `scripts/ci/` (CI authority) or `scripts/lib/`, any path a hook's command names (local and remote `entry` overrides, `args`, commands inside `bash -c`, `python -m` modules, deleted scripts included) or a module those scripts import; an unreadable file list, a git timeout or an entry it cannot split also select the whole tree. Invariant page: `scripts/ci/AGENTS.d/precommit-scope.md`. | `scripts/ci/tests/test_precommit_scope.py` (21 tests); against the diff-only selector of `634e636a7` 7 of them fail, the #3237 file list among them | `fix/precommit-scope-config-change` | | **T-HOOKS-WINDOWS-POSIX-FIXTURES-2026-09-30** — four local fixture hooks fail on a Windows host | **FIXED in #3282 (part of #2438).** `test_lint_configured.py` normalises the public-model path via `as_posix()` and skips `ConfiguredLintTests` off POSIX with a stated reason; `test_write_compile_commands.py` skips `CompileCommandsExportTest` off POSIX with a stated reason; `test_tidy_ratchet.py` skips off POSIX with a stated reason; `test-sync-pelorus-interop.sh` skips on Windows with a stated reason. `scripts/ci/check-source-adr-citations.py --write` writes the registry with `newline="\n"`, guards against concurrent writes, cleans up temporary files on failure, and `test_check_source_adr_citations.py` verifies canonical byte rewriting. | unit test suite | #3282 | 2026-11-03 | closed | | **T-CI-WARNINGS-NON-MSVC-LEGS-2026-10-07** — remaining: Dev Container Build compiles third-party sources (vpl-gpu-rt -Wstringop-overflow, FFmpeg) and prints gcc's LTO "serial compilation" note | **CLOSED in #3282 (part of #2438).** Recorded the dev container image build as outside the compiler warning gate in `docs/development/ci.md` ("Builds outside the gate") per ADR-2170 and ADR-2828: external container dependencies (`vpl-gpu-rt`, `FFmpeg`) are compiled into temporary container layers during OCI image assembly rather than from repository sources; in-tree VMAFx sources are compiled and gated with `-Dwerror=true` across all CI compiler matrix legs. The Windows `icx-cl` lane was previously resolved and gated with `-Dwerror=true` in PR #2650 (ADR-2828). | `docs/development/ci.md` | #3282 | 2026-11-03 | closed | diff --git a/scripts/ci/build-oneapi-libvmaf.sh b/scripts/ci/build-oneapi-libvmaf.sh index b43a527473..60634bb372 100755 --- a/scripts/ci/build-oneapi-libvmaf.sh +++ b/scripts/ci/build-oneapi-libvmaf.sh @@ -40,6 +40,12 @@ nice -n 10 ninja -C "${build_dir}" -j"${jobs}" mkdir -p "${dist_dir}/lib" "${dist_dir}/bin" "${dist_dir}/model" "${licence_inputs_dir}" find "${build_dir}/src" -maxdepth 1 \( -name 'libvmaf.so*' -o -name 'libvmafx.so*' \) -not -type d -exec cp -a {} "${dist_dir}/lib/" \; cp -d /usr/lib/*-linux-gnu*/libzimg.so* "${dist_dir}/lib/" +# The Debian record of the libzimg2 those images ship (issue #3242): the zimg +# component of licensing.json reads usr/share/doc/libzimg2/SOURCE and takes its +# copyright file as the licence text, as the tester images already do. +mkdir -p "${dist_dir}/doc/libzimg2" +cp /usr/share/doc/libzimg2/copyright "${dist_dir}/doc/libzimg2/copyright" +dpkg-query -W -f='${source:Package}=${source:Version}\n' libzimg2 >"${dist_dir}/doc/libzimg2/SOURCE" cp "${build_dir}/tools/vmaf" "${dist_dir}/bin/" cp -r model/. "${dist_dir}/model/" diff --git a/tools/rc1-tester/image/licensing.json b/tools/rc1-tester/image/licensing.json index 59d98f6408..234b0bc7bf 100644 --- a/tools/rc1-tester/image/licensing.json +++ b/tools/rc1-tester/image/licensing.json @@ -2748,22 +2748,23 @@ { "id": "zimg", "kind": "fixed", - "name": "zimg image scaling and colours-pace conversion library (libzimg.so.2)", + "name": "zimg image scaling and colours-pace conversion library (Debian libzimg2)", "paths": [ - "usr/local/lib/libzimg.so*" + "usr/local/lib/libzimg.so*", + "usr/share/doc/libzimg2/**" ], "licence": "WTFPL", "copyright": [ - "Copyright (c) 2014-2024, sekrit-twc" + "Copyright (c) 2014-2020, sekrit-twc" ], - "source": "https://github.com/sekrit-twc/zimg.git at commit f819b14e8f39d1282400b0d9543e8ef73c1b2bbd, tag release-3.0.6", + "debian_source_file": "usr/share/doc/libzimg2/SOURCE", "texts": [ { - "name": "WTFPL.txt", - "fetched": "zimg-3.0.6-COPYING", - "label": "zimg 3.0.6 licence text (WTFPL v2)" + "artifact": "usr/share/doc/libzimg2/copyright", + "label": "Debian copyright file of libzimg2" } - ] + ], + "source": "the Debian source package named in usr/share/doc/libzimg2/SOURCE, in the source image" }, { "id": "notices", diff --git a/tools/rc1-tester/tests/test_dockerfile_runtime_libs.py b/tools/rc1-tester/tests/test_dockerfile_runtime_libs.py index eb6f888c56..b99544c4e3 100644 --- a/tools/rc1-tester/tests/test_dockerfile_runtime_libs.py +++ b/tools/rc1-tester/tests/test_dockerfile_runtime_libs.py @@ -1,13 +1,25 @@ # Copyright 2026 Lusoris # SPDX-License-Identifier: EUPL-1.2 -"""Every GPU runtime stage of the tester image finds the libraries its build stage stages. - -The GPU build stages of `docker/Dockerfile.tester` copy the shared libraries the -`vmaf` binary links (libzimg since zimg became a default dependency) into -`/opt/vmafx/lib`. The matching runtime stage must copy that directory and put it -on `LD_LIBRARY_PATH`, or the stage's `ldd` check stops the image build with -`libzimg.so.2 => not found`. The GPU images build only on a push to master, so -this test is the pull-request coverage of the contract. +"""Every image stage that copies a Debian library ships that package's record. + +The build stages copy the shared libraries the `vmaf` binary links (libzimg +since zimg became a default dependency) out of Debian's `libzimg2` package. Two +contracts follow, and this module holds both: + +- the **tester** image's GPU runtime stages find the libraries their build stage + staged in `/opt/vmafx/lib`, or the stage's `ldd` check stops the image build + with `libzimg.so.2 => not found` (the GPU images build only on a push to + master, so this is the pull-request coverage of that contract); +- every stage that copies such a library also carries the record the licence + manifest reads — the package's copyright file plus a `dpkg-query` `SOURCE` + line — and every published runtime stage installs it at + `/usr/share/doc/libzimg2` (issue #3241 for the tester images, #3242 for the + production ones). + +The copy sites are **discovered** from the tree rather than listed, so a stage +that starts copying a Debian library lands in the contract without anybody +remembering to add it; `KNOWN_COPY_SITES` names the ones the tree has today and +fails when discovery finds one it does not list. """ from __future__ import annotations @@ -21,6 +33,79 @@ DOCKERFILE = REPO / "docker/Dockerfile.tester" KITS = ("sycl", "cuda", "hip") +# Files that can copy a library out of a Debian package into an image tree: the +# Dockerfiles and Containerfiles of the tree, and the shared build scripts a +# builder stage runs (scripts/ci/build-oneapi-libvmaf.sh is the one that stages +# /dist/lib for the two oneAPI images). +COPY_SOURCES = ( + "Dockerfile", + "Dockerfile.go-server", + "Containerfile.vmafx", + "dev/Containerfile", + "docker/Dockerfile.controller", + "docker/Dockerfile.node", + "docker/Dockerfile.production", + "docker/Dockerfile.production-gpu", + "docker/Dockerfile.tester", + "scripts/ci/build-oneapi-libvmaf.sh", +) + +# Every stage that copies a Debian library today, as (file, stage, copy verb). +# `vmaf-build` was missing from the pre-#3242 coverage of `Dockerfile.tester` +# even though its /opt/vmafx reaches the published image at `runtime`'s +# `COPY --from=vmaf-build /opt/vmafx /opt/vmafx`; discovery below found it. +KNOWN_COPY_SITES = ( + ("docker/Dockerfile.tester", "python-venv", "cp -a"), + ("docker/Dockerfile.tester", "vmaf-build", "cp -a"), + ("docker/Dockerfile.tester", "sycl-build", "cp -a"), + ("docker/Dockerfile.tester", "cuda-build", "cp -a"), + ("docker/Dockerfile.tester", "hip-build", "cp -a"), + ("docker/Dockerfile.production", "builder", "cp -d"), + ("docker/Dockerfile.controller", "vmaf-builder", "cp -d"), + ("docker/Dockerfile.production-gpu", "builder-cuda13", "cp -d"), + ("docker/Dockerfile.production-gpu", "builder-rocm10", "cp -d"), + ("Dockerfile.go-server", "vmaf-builder", "cp -d"), + ("docker/Dockerfile.node", "vmaf-builder", "cp -d"), + ("scripts/ci/build-oneapi-libvmaf.sh", "", "cp -d"), +) + +# One copy site is build-only: `vmaf-build` stages /opt/vmafx for the golden +# build profile, and the published `runtime` stage overwrites /opt/vmafx/lib +# from `python-venv` (which does carry the record) before it installs +# /usr/share/doc/libzimg2. `test_a_build_only_copy_site_is_superseded` proves +# that order, so this is an audited exclusion rather than a hole. +BUILD_ONLY_COPY_SITES = (("docker/Dockerfile.tester", "vmaf-build", "cp -a"),) + +# The published runtime stages that install the record: (file, stage). The +# install may sit in the stage or in a stage it is FROM. +RUNTIME_STAGES = ( + ("docker/Dockerfile.tester", "runtime"), + ("docker/Dockerfile.tester", "sycl-runtime"), + ("docker/Dockerfile.tester", "cuda-runtime"), + ("docker/Dockerfile.tester", "hip-runtime"), + ("docker/Dockerfile.production", "cli-assembled"), + ("docker/Dockerfile.production", "server-assembled"), + ("docker/Dockerfile.controller", "controller-assembled"), + ("docker/Dockerfile.production-gpu", "cuda13-assembled"), + ("docker/Dockerfile.production-gpu", "rocm10-assembled"), + ("docker/Dockerfile.production-gpu", "oneapi2026-assembled"), + ("Dockerfile.go-server", "go-server-assembled"), + ("docker/Dockerfile.node", "node-assembled"), + ("docker/Dockerfile.node", "node-sycl-assembled"), +) + +DEBIAN_COPIES = {"libzimg": "libzimg2", "libgomp": "libgomp1"} +DOC_ROOT = "/usr/share/doc" +# Where the shared oneAPI build script writes the record (its `dist_dir`). +ONEAPI_DIST = "/dist" + +# A builder that stages the record because a *script* it runs writes it, not a +# COPY/RUN line of its own: (file, stage) -> the script's dist_dir. +SCRIPT_STAGED = { + ("docker/Dockerfile.production-gpu", "builder-oneapi2026"): ONEAPI_DIST, + ("docker/Dockerfile.node", "builder-oneapi2026"): ONEAPI_DIST, +} + def _stage(text: str, name: str) -> str: """The body of stage `name`, up to the next FROM line.""" @@ -53,7 +138,7 @@ def test_the_library_path_reader_refuses_a_missing_entry() -> None: """Negative case: a runtime stage without the directory on its path fails.""" stage = "ENV LD_LIBRARY_PATH=/opt/vmafx/build/src:/opt/vmafx/lib/rocm \\\n" assert "/opt/vmafx/lib" not in _library_path(stage) - assert _library_path("ENV LD_LIBRARY_PATH=/a:/opt/vmafx/lib \\\n")[-1] == "/opt/vmafx/lib" + assert _library_path("ENV LD_LIBRARY_PATH=/opt/vmafx/lib \\\n")[-1] == "/opt/vmafx/lib" def test_the_stage_reader_refuses_an_unknown_stage() -> None: @@ -62,34 +147,291 @@ def test_the_stage_reader_refuses_an_unknown_stage() -> None: _stage("FROM x AS a\nRUN true\n", "b") -DEBIAN_COPIES = {"libzimg": "libzimg2", "libgomp": "libgomp1"} +COPY_LINE = re.compile( + r"(?:^|&&|\|\||\n|\bRUN[ \t])\s*(cp -[ad]) /usr/lib/\*-linux-gnu\*?/(lib\w+)\.so", re.MULTILINE +) + + +def _copied_debian_packages(stage: str) -> list[str]: + """Debian packages whose library the stage copies out of a /usr/lib directory.""" + return [DEBIAN_COPIES.get(lib, lib) for _verb, lib in sorted(COPY_LINE.findall(stage))] -def _uncredited_debian_copies(stage: str) -> list[str]: +def _copy_gaps(stage: str) -> list[str]: """Debian packages whose library the stage copies without recording its SOURCE and copyright.""" missing = [] - for lib in sorted(set(re.findall(r"cp -a /usr/lib/\*-linux-gnu\*?/(lib\w+)\.so", stage))): + for _verb, lib in sorted(COPY_LINE.findall(stage)): package = DEBIAN_COPIES.get(lib, lib) source = rf"dpkg-query -W -f='\$\{{source:Package\}}=\$\{{source:Version\}}\\n' {package}\b" - if not (re.search(source, stage) and f"/usr/share/doc/{package}/copyright" in stage): + if not (re.search(source, stage) and f"{DOC_ROOT}/{package}/copyright" in stage): missing.append(package) - return missing + return sorted(set(missing)) + + +def _stages(text: str) -> dict[str, tuple[str, str]]: + """Stage name -> (parent stage name or base image, body), in file order.""" + found: dict[str, tuple[str, str]] = {} + headers = list(re.finditer(r"^FROM (\S+) AS (\S+)\n", text, re.MULTILINE)) + for index, match in enumerate(headers): + end = headers[index + 1].start() if index + 1 < len(headers) else len(text) + found[match.group(2)] = (match.group(1), text[match.end() : end]) + return found + + +def _inherit_chain(stages: dict[str, tuple[str, str]], name: str) -> list[tuple[str, str]]: + """(stage name, body) of stage `name` and of every stage it is FROM, innermost first.""" + chain, current, seen = [], name, set() + while current in stages and current not in seen: + seen.add(current) + parent, body = stages[current] + chain.append((current, body)) + current = parent + return chain + + +def _discovered_copy_sites() -> set[tuple[str, str, str]]: + """(file, stage, verb) of every stage in the tree that copies a Debian library. + + A Dockerfile is read stage by stage; a shell script (which has no stages) is + read whole and reported with an empty stage name. + """ + found: set[tuple[str, str, str]] = set() + for rel in COPY_SOURCES: + text = (REPO / rel).read_text(encoding="utf-8") + stages = _stages(text) + if stages: + for stage, (_parent, body) in stages.items(): + for verb, _lib in COPY_LINE.findall(body): + found.add((rel, stage, verb)) + else: + for verb, _lib in COPY_LINE.findall(text): + found.add((rel, "", verb)) + return found + + +def test_the_declared_copy_sites_are_every_copy_site_in_the_tree() -> None: + """Discovery and the declared list agree, so a new copy site cannot slip past. + + This is the check the earlier hard-coded tuple lacked: a stage that starts + copying a Debian library, or one the list forgot, shows up here by name. + """ + discovered = _discovered_copy_sites() + declared = {(path, stage, verb) for path, stage, verb in KNOWN_COPY_SITES} + assert ( + discovered - declared == set() + ), "copy sites not covered by KNOWN_COPY_SITES: " + ", ".join( + f"{p}:{s} ({v})" for p, s, v in sorted(discovered - declared) + ) + assert ( + declared - discovered == set() + ), "KNOWN_COPY_SITES names stages that copy no Debian library any more: " + ", ".join( + f"{p}:{s} ({v})" for p, s, v in sorted(declared - discovered) + ) + + +def test_the_copy_site_discovery_finds_a_planted_site() -> None: + """Known gap: discovery reads COPY_SOURCES, so a Dockerfile outside it is not seen. + + The list is the contract: adding a Dockerfile that copies a Debian library + means adding it to COPY_SOURCES (or the site goes unheld). + """ + assert "docker/Dockerfile.tester" in COPY_SOURCES + assert "docker/Dockerfile.production-gpu" in COPY_SOURCES + assert "docker/Dockerfile.operator" not in COPY_SOURCES # copies nothing from /usr/lib -@pytest.mark.parametrize("stage", ("python-venv", *(f"{kit}-build" for kit in KITS))) -def test_copied_debian_libraries_carry_their_source(stage: str) -> None: - """A library copied out of a Debian package ships that package's copyright and SOURCE.""" - assert _uncredited_debian_copies(_stage(DOCKERFILE.read_text(), stage)) == [] +@pytest.mark.parametrize( + "path,stage,verb", + [site for site in KNOWN_COPY_SITES if site not in BUILD_ONLY_COPY_SITES], + ids=[ + f"{Path(path).name}:{stage or 'script'}" + for path, stage, _verb in KNOWN_COPY_SITES + if (path, stage, _verb) not in BUILD_ONLY_COPY_SITES + ], +) +def test_copied_debian_libraries_carry_their_source(path: str, stage: str, verb: str) -> None: + """A library copied out of a Debian package ships that package's copyright and SOURCE. + + The non-empty check keeps the parametrization honest: a stage that stops + copying the library would otherwise pass while asserting nothing. + """ + text = (REPO / path).read_text(encoding="utf-8") + body = _stage(text, stage) if stage else text + assert verb in body, f"{path}:{stage} has no '{verb}' copy line; update KNOWN_COPY_SITES" + assert "libzimg2" in _copied_debian_packages( + body + ), f"{path}:{stage or 'script'} copies no libzimg2" + assert _copy_gaps(body) == [] + + +def test_a_build_only_copy_site_is_superseded() -> None: + """The one build-only copy site's library is replaced, with its record, in the image. + + `vmaf-build` copies libzimg into /opt/vmafx/lib without a doc record. The + published `runtime` stage copies /opt/vmafx from it and then overwrites + /opt/vmafx/lib from `python-venv` — which does stage the record — before + installing /usr/share/doc/libzimg2. Docker's COPY order is the proof: the + later COPY of the same destination wins, so the image holds python-venv's + libzimg, whose record is installed at line 156. If either half of that + pairing changes (the record source, or the lib overwrite), this fails. + """ + text = DOCKERFILE.read_text(encoding="utf-8") + runtime = _stage(text, "runtime") + lib_source = re.search(r"^COPY --from=(\S+) (\S+) /opt/vmafx/lib$", runtime, re.MULTILINE) + assert lib_source, "runtime no longer copies /opt/vmafx/lib from a stage" + winner, source_dir = lib_source.groups() + assert source_dir == "/extra-libs/lib", source_dir + assert winner == "python-venv", winner + # The stage that wins must itself carry the record, and the runtime installs it. + venv = _stage(text, winner) + assert _copy_gaps(venv) == [] + assert f"COPY --from={winner} /extra-libs/doc/libzimg2 {DOC_ROOT}/libzimg2" in runtime + # And it must be copied at all, so the build-only copy is superseded and not dropped. + whole_tree = "COPY --from=vmaf-build /opt/vmafx /opt/vmafx" + assert whole_tree in runtime + assert runtime.index(whole_tree) < runtime.index( + f"COPY --from={winner} {source_dir} /opt/vmafx/lib" + ) + + +@pytest.mark.parametrize("verb", ("cp -a", "cp -d")) +def test_an_uncredited_debian_copy_fails(verb: str) -> None: + """Negative case: a stage that copies a Debian library without its record is reported.""" + stage = f"RUN {verb} /usr/lib/*-linux-gnu*/libzimg.so* /somewhere/lib/ \\\n" + assert _copy_gaps(stage) == ["libzimg2"] + + +def test_the_copy_reader_refuses_a_record_for_another_package() -> None: + """Negative case: recording libgomp1's source does not credit a copied libzimg2.""" + stage = ( + "RUN cp -d /usr/lib/*-linux-gnu*/libzimg.so* /dist/lib/ \\\n" + " && dpkg-query -W -f='${source:Package}=${source:Version}\\n' libgomp1 \\\n" + " > /dist/doc/libgomp1/SOURCE \\\n" + " && cp /usr/share/doc/libgomp1/copyright /dist/doc/libgomp1/copyright\n" + ) + assert _copy_gaps(stage) == ["libzimg2"] + + +def test_the_copy_reader_sees_the_forms_the_tree_uses() -> None: + """The forms the reader models, and the one it does not, recorded rather than implied. + + It reads `cp -a` / `cp -d` from a `/usr/lib/*-linux-gnu*/` glob — every form + in COPY_SOURCES today, whether the line starts the `RUN` or continues its + `&&` chain. A path spelled with a concrete arch (`/usr/lib/x86_64-linux-gnu`) + or built in a shell variable, and `install` instead of `cp`, are not + modelled; that is why discovery and the declared list are compared, and why + a new copy written that way has to be added to `COPY_SOURCES` and this + reader together. + """ + assert _copy_gaps("RUN cp -a /usr/lib/*-linux-gnu*/libzimg.so* /x \\\n") == ["libzimg2"] + assert _copy_gaps("RUN cp -d /usr/lib/*-linux-gnu*/libzimg.so* /x \\\n") == ["libzimg2"] + assert _copy_gaps("RUN cp -a /usr/lib/*-linux-gnu/libzimg.so* /x \\\n") == ["libzimg2"] + assert _copy_gaps( + "RUN mkdir -p /x \\\n && cp -a /usr/lib/*-linux-gnu*/libzimg.so* /x/ \\\n" + ) == ["libzimg2"] + # Known gap, deliberate: a concrete arch path and an `install` call are unseen. + assert _copy_gaps("RUN cp -a /usr/lib/x86_64-linux-gnu/libzimg.so* /x \\\n") == [] + assert _copy_gaps("RUN install -m 644 /usr/lib/*-linux-gnu*/libzimg.so.2 /x \\\n") == [] @pytest.mark.parametrize("kit", KITS) def test_gpu_runtime_ships_the_libzimg2_record(kit: str) -> None: - """The runtime stage installs the libzimg2 copyright and SOURCE its licence entry reads.""" + """The runtime stage copies the record its build stage wrote, into the image.""" runtime = _stage(DOCKERFILE.read_text(), f"{kit}-runtime") - assert f"COPY --from={kit}-build /zimg-doc /usr/share/doc/libzimg2" in runtime + installs = _record_installs(runtime) + assert installs, f"{kit}-runtime installs no {DOC_ROOT}/libzimg2" + + +def _record_installs(body: str) -> list[tuple[str, str]]: + """(source, builder) of every `COPY --from= /usr/share/doc/libzimg2`.""" + pattern = rf"^COPY --from=(\S+) (\S+) {re.escape(DOC_ROOT)}/libzimg2\b" + return [(source, builder) for builder, source in re.findall(pattern, body, re.MULTILINE)] + + +def _builder_writes_record(path: str, builder: str, source: str) -> bool: + """Whether stage `builder` of `path` writes the record under `source`. + + A stage writes it either with a COPY/RUN line of its own, or by running a + shared script that writes to the stage's dist_dir — `SCRIPT_STAGED` names + those, so `builder-oneapi2026` (which only runs + scripts/ci/build-oneapi-libvmaf.sh) is not held to a line it does not have. + """ + staged = _stages((REPO / path).read_text(encoding="utf-8")).get(builder) + if staged is None: + return False + body = staged[1] + if f"{source.rstrip('/')}/copyright" in body or f"{source.rstrip('/')}/" in body: + return True + dist = SCRIPT_STAGED.get((path, builder)) + return bool(dist) and source.rstrip("/") == f"{dist}/doc/libzimg2" + + +@pytest.mark.parametrize( + "path,stage", + RUNTIME_STAGES, + ids=[f"{Path(path).name}:{stage}" for path, stage in RUNTIME_STAGES], +) +def test_a_published_runtime_installs_the_record(path: str, stage: str) -> None: + """The runtime stage copies the record from the builder that staged it. + + The install must be a real `COPY --from= /usr/share/doc/libzimg2` + in the stage or in a stage it inherits, AND that stage must write `` — + a comment or an `ENV` naming the path does not satisfy this, and neither + does a COPY whose source the builder never creates. + """ + stages = _stages((REPO / path).read_text(encoding="utf-8")) + for current, body in _inherit_chain(stages, stage): + for source, builder in _record_installs(body): + assert ( + builder in stages + ), f"{path}:{current} copies the record from unknown stage {builder}" + assert _builder_writes_record( + path, builder, source + ), f"{path}:{builder} never writes {source}, which {current} copies" + return + raise AssertionError(f"{path}:{stage} installs no {DOC_ROOT}/libzimg2") + + +def test_the_record_install_reader_refuses_a_comment_or_env() -> None: + """Negative cases: naming the path is not installing the record.""" + assert _record_installs("# we used to COPY /dist/doc/libzimg2 /usr/share/doc/libzimg2\n") == [] + assert _record_installs("ENV X=/usr/share/doc/libzimg2\n") == [] + assert _record_installs("RUN mkdir -p /usr/share/doc/libzimg2\n") == [] + assert _record_installs("COPY --from=builder /dist/doc/libzimg2 /usr/share/doc/libzimg2\n") == [ + ("/dist/doc/libzimg2", "builder") + ] + + +def test_the_inheritance_reader_sees_a_parent_stage() -> None: + """Negative case: an install by the FROM stage counts, an absent one does not.""" + text = ( + "FROM base AS parent\n" + "COPY --from=b /dist/doc/libzimg2 /usr/share/doc/libzimg2\n" + "FROM parent AS child\n" + "RUN true\n" + ) + stages = _stages(text) + assert [name for name, _ in _inherit_chain(stages, "child")] == ["child", "parent"] + assert [name for name, _ in _inherit_chain(stages, "absent")] == [] + + +def test_the_oneapi_build_script_records_the_libzimg2_it_stages() -> None: + """The shared oneAPI build script stages libzimg.so*, so it stages the record too. + + Two published images take /dist/lib from builder-oneapi2026: the production + oneAPI image and the node SYCL image. The `builder-oneapi2026` stages carry + no copy line of their own — they run this script — so the script is the copy + site, and it is in `COPY_SOURCES` so discovery sees it. + """ + script = (REPO / "scripts/ci/build-oneapi-libvmaf.sh").read_text(encoding="utf-8") + assert _copied_debian_packages(script) == ["libzimg2"] + assert _copy_gaps(script) == [] + assert "dpkg-query" in script and f"{DOC_ROOT}/libzimg2/copyright" in script -def test_an_uncredited_debian_copy_fails() -> None: - """Negative case: a stage that copies libzimg without its Debian record is reported.""" - stage = "RUN cp -a /usr/lib/*-linux-gnu*/libzimg.so* /opt/vmafx/lib/ \\\n" - assert _uncredited_debian_copies(stage) == ["libzimg2"] +def test_the_oneapi_stages_stage_no_library_themselves() -> None: + """The two builder-oneapi2026 stages run the shared script and copy nothing themselves.""" + for path in ("docker/Dockerfile.production-gpu", "docker/Dockerfile.node"): + body = _stage((REPO / path).read_text(encoding="utf-8"), "builder-oneapi2026") + assert _copied_debian_packages(body) == [], path + assert "build-oneapi-libvmaf.sh" in body, path diff --git a/tools/rc1-tester/tests/test_licensing_production.py b/tools/rc1-tester/tests/test_licensing_production.py index 09f8130cbc..d5c547e93f 100644 --- a/tools/rc1-tester/tests/test_licensing_production.py +++ b/tools/rc1-tester/tests/test_licensing_production.py @@ -335,6 +335,91 @@ def test_the_release_files_carry_checked_notices() -> None: assert {"release-native", "release-models"} <= records.keys() +# ------------------------------------------------- the Debian zimg record (issue #3242) + + +def test_the_production_zimg_component_reads_the_debian_record() -> None: + """The production images copy libzimg2 out of Debian, so their zimg entry says so. + + The tester entry already reads `usr/share/doc/libzimg2/SOURCE` and takes the + package's copyright file as its licence text; the production entry described + upstream zimg 3.0.6, which those images do not ship. + """ + expanded = lic.load_manifest()["artifacts"] + tester = next(c for c in expanded["image"]["components"] if c["id"] == "zimg") + production = next( + c for c in expanded["production-cli-image"]["components"] if c["id"] == "zimg" + ) + assert production["debian_source_file"] == tester["debian_source_file"] + assert production["texts"] == tester["texts"] + assert production["copyright"] == tester["copyright"] + assert production["licence"] == "WTFPL" + assert "usr/share/doc/libzimg2/**" in production["paths"] + assert production["source"] == tester["source"] + + +def test_every_production_image_that_ships_zimg_reads_that_one_record() -> None: + """The eight production records inherit the component, so they agree with it.""" + expanded = lic.load_manifest()["artifacts"] + cli = next(c for c in expanded["production-cli-image"]["components"] if c["id"] == "zimg") + for kind in ( + "production-server-image", + "production-go-server-image", + "production-controller-image", + "production-node-image", + "production-node-sycl-image", + "production-cuda-image", + "production-rocm-image", + "production-oneapi-image", + ): + component = next(c for c in expanded[kind]["components"] if c["id"] == "zimg") + assert component == cli, kind + + +def test_the_debian_record_is_the_text_the_gate_demands() -> None: + """The gate requires the copyright text by path, so the one in the record is it.""" + record = lic.load_manifest()["artifacts"]["production-cli-image"] + scan = {"licences": [], "files": []} + needed = lic.needed_texts(record, scan, lic.load_manifest(), set()) + assert "/usr/share/doc/libzimg2/copyright" in needed + assert needed["/usr/share/doc/libzimg2/copyright"]["artifact"] == ( + "usr/share/doc/libzimg2/copyright" + ) + + +def test_the_images_that_link_zimg_from_source_keep_their_own_record() -> None: + """macOS, the Windows zips and the native release build zimg themselves. + + Issue #3242 leaves them alone: each links zimg from upstream's source archive + (Homebrew, vcpkg, the release container's zimg-devel), so none of them has a + Debian SOURCE file to point at and none may claim one. + """ + expanded = lic.load_manifest()["artifacts"] + # Each of them links the 3.0.6 release, and says so in its own `source`. + expected = "https://github.com/sekrit-twc/zimg.git at commit f819b14e8f39d1282400b0d9543e8ef73c1b2bbd, tag release-3.0.6" + for kind in ("macos", "windows-zip", "windows-cuda-zip", "windows-sycl-zip", "release-native"): + component = next(c for c in expanded[kind]["components"] if c["id"] == "zimg") + assert "debian_source_file" not in component, kind + assert not any( + "usr/share/doc/libzimg2" in text for text in json.dumps(component["texts"]).split() + ), kind + assert component["source"] == expected, kind + # And the WTFPL text is the one the 3.0.6 archive ships, by name. + assert component["texts"] == [ + { + "name": "WTFPL.txt", + "repo": "tools/rc1-tester/image/licenses/zimg-3.0.6-COPYING.txt", + "label": "zimg 3.0.6 licence text (WTFPL v2)", + } + ] or component["texts"] == [ + { + "name": "WTFPL.txt", + "fetched": "zimg-3.0.6-COPYING", + "label": "zimg 3.0.6 licence text (WTFPL v2)", + } + ], kind + + def test_production_node_sycl_image_shares_runtime_and_node_components() -> None: """production-node-sycl-image shares the oneAPI SYCL runtime components and the Intel GPU user-mode stack from sycl-image and the worker node components from production-node-image (ADR-3371).