From 3cd0292ba1ddddeb3575857d21e90ba2b010685c Mon Sep 17 00:00:00 2001 From: ecrum19 Date: Fri, 11 Sep 2026 22:27:20 +0200 Subject: [PATCH] Release v3.0.1: fix the multi-architecture Docker build The v3.0.0 image never published: the linux/arm64 build failed outright, so 3.0.0 exists on PyPI with no matching container image. Two defects, one behind the other: - The runtime stage copied QLever's shared libraries from /lib/x86_64-linux-gnu by soname. Upstream publishes an arm64 image whose multiarch directory is /lib/aarch64-linux-gnu, so the COPY could not resolve its sources. The libraries are now resolved with ldd inside the QLever stage and staged into a fixed path, which is both architecture- and soname-independent: an upstream rebuild that bumps Boost or ICU no longer needs an edit here. Verified to stage the same nine libraries on both architectures. - That staging step then failed on both platforms, because upstream's image runs as a non-root user and /opt is not writable by it. The stage switches to root and stages into /tmp. QLever acquisition is now best-effort throughout, which is what the surrounding comments always claimed: an image without it is still fully functional and the build-time marker says why. Previously a missing COPY source failed the build before that marker could be written, and the marker could not detect a missing binary at all, since ldd on a nonexistent file does not print "not found". Also pins the QLever image by its multi-architecture index digest rather than :latest, which is a rolling build of upstream's main branch. The pinned digest is build bfd5741 -- the one this repository's QLever assertions were actually measured against. Finally, publish-python skips already-published PyPI files. PyPI reserves a version permanently once uploaded, so re-pushing a release tag to correct anything outside the Python artifact used to fail that job on the duplicate. Co-Authored-By: Claude Opus 5 --- .github/workflows/publish-python.yml | 7 +++++++ CITATION.cff | 2 +- Dockerfile | 24 ++++++++++++++++-------- README.md | 4 ++-- conda-recipe/README.md | 4 ++-- conda-recipe/meta.yaml | 2 +- pyproject.toml | 2 +- 7 files changed, 30 insertions(+), 15 deletions(-) diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 0001d09..ba2d41b 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -130,5 +130,12 @@ jobs: name: python-dist path: dist + # PyPI reserves a version permanently once it is uploaded, so re-pushing a + # release tag -- to correct a Dockerfile, a workflow, or anything else + # outside the Python artifact -- used to fail this job on the duplicate. + # Skipping what is already published makes a tag re-push idempotent: the + # Docker and release jobs re-run, and PyPI is left exactly as it was. - name: Publish package uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true diff --git a/CITATION.cff b/CITATION.cff index cc199c6..1b0b3ba 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -2,7 +2,7 @@ cff-version: 1.2.0 message: "If you use VCF-RDFizer in your research, please cite it using the metadata below." title: "VCF-RDFizer" type: software -version: "3.0.0" +version: "3.0.1" authors: - name: "VCF-RDFizer maintainers" repository-code: "https://github.com/ecrum19/VCF-RDFizer" diff --git a/Dockerfile b/Dockerfile index 6187f68..ad81833 100644 --- a/Dockerfile +++ b/Dockerfile @@ -91,6 +91,12 @@ RUN cargo build --locked --release \ # Named stage so the runtime image can COPY QLever's binaries out of it. FROM ${QLEVER_IMAGE} AS qlever +# Upstream's image runs as a non-root user, so writing anywhere outside that +# user's own tree fails. This stage needs to write a staging directory, so it +# switches to root; the stage is discarded after the COPY below, and nothing +# here changes the user the published image runs as. +USER root + # Stage QLever's binaries and the release-specific libraries they need into one # fixed layout, so the runtime image copies from architecture-independent paths. # @@ -112,20 +118,22 @@ FROM ${QLEVER_IMAGE} AS qlever # runtime base is newer than QLever's. Only the families the binaries cannot # find in that base are taken, which is the same set the previous soname list # named. +# /tmp rather than /opt: it is world-writable in every base image, so the +# staging directory is created even if this stage ever runs unprivileged again. RUN set -eu; \ - mkdir -p /opt/qlever-stage/bin /opt/qlever-stage/lib; \ + mkdir -p /tmp/qlever-stage/bin /tmp/qlever-stage/lib; \ for binary in qlever-index qlever-server; do \ [ -f "/qlever/$binary" ] || continue; \ - cp "/qlever/$binary" /opt/qlever-stage/bin/; \ + cp "/qlever/$binary" /tmp/qlever-stage/bin/ || continue; \ ldd "/qlever/$binary" 2>/dev/null \ | sed -n 's/.*=> \(\/[^ ]*\).*/\1/p' \ | grep -E '/(libboost_|libicu|libjemalloc|liburing|libgomp)[^/]*$' \ | while read -r library; do \ - cp -Ln "$library" /opt/qlever-stage/lib/ 2>/dev/null || true; \ - done; \ + cp -Ln "$library" /tmp/qlever-stage/lib/ 2>/dev/null || true; \ + done || true; \ done; \ - echo "staged QLever binaries:"; ls -1 /opt/qlever-stage/bin || true; \ - echo "staged QLever libraries:"; ls -1 /opt/qlever-stage/lib || true + echo "staged QLever binaries:"; ls -1 /tmp/qlever-stage/bin || true; \ + echo "staged QLever libraries:"; ls -1 /tmp/qlever-stage/lib || true FROM eclipse-temurin:11-jre @@ -205,8 +213,8 @@ COPY --from=build-hdtc /opt/third_party_licenses/ /usr/share/licenses/vcf-rdfize # pointed at, so they cannot shadow anything the rest of the image links # against. (glibc itself is not copied - it is backward compatible, and this # base is newer than QLever's.) -COPY --from=qlever /opt/qlever-stage/bin/ /opt/qlever/bin/ -COPY --from=qlever /opt/qlever-stage/lib/ /opt/qlever/lib/ +COPY --from=qlever /tmp/qlever-stage/bin/ /opt/qlever/bin/ +COPY --from=qlever /tmp/qlever-stage/lib/ /opt/qlever/lib/ COPY THIRD_PARTY_NOTICES.md /usr/share/licenses/vcf-rdfizer/THIRD_PARTY_NOTICES.md COPY src/*.sh /opt/vcf-rdfizer/ COPY src/*.py /opt/vcf-rdfizer/ diff --git a/README.md b/README.md index 936ad48..eaf2b18 100644 --- a/README.md +++ b/README.md @@ -1080,7 +1080,7 @@ Safe termination: If you use VCF-RDFizer in a publication, please cite: -VCF-RDFizer maintainers. (2026). *VCF-RDFizer* (Version 3.0.0) [Computer software]. GitHub. https://github.com/ecrum19/VCF-RDFizer +VCF-RDFizer maintainers. (2026). *VCF-RDFizer* (Version 3.0.1) [Computer software]. GitHub. https://github.com/ecrum19/VCF-RDFizer BibTeX: @@ -1089,7 +1089,7 @@ BibTeX: author = {{VCF-RDFizer maintainers}}, title = {VCF-RDFizer}, year = {2026}, - version = {3.0.0}, + version = {3.0.1}, url = {https://github.com/ecrum19/VCF-RDFizer}, note = {Computer software} } diff --git a/conda-recipe/README.md b/conda-recipe/README.md index 9986fde..d91c2ce 100644 --- a/conda-recipe/README.md +++ b/conda-recipe/README.md @@ -7,11 +7,11 @@ do not submit this package to `staged-recipes`. ## Before submitting to conda-forge -1. Commit the version bump, then create and push a Git tag (for example `v3.0.0`). +1. Commit the version bump, then create and push a Git tag (for example `v3.0.1`). 2. Download the source tarball and compute sha256: ```bash curl -L -o vcf-rdfizer.tar.gz \ - https://github.com/ecrum19/VCF-RDFizer/archive/refs/tags/v3.0.0.tar.gz + https://github.com/ecrum19/VCF-RDFizer/archive/refs/tags/v3.0.1.tar.gz shasum -a 256 vcf-rdfizer.tar.gz ``` 3. Replace `version` and `sha256` in the feedstock's `recipe/meta.yaml`. diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 3af2ed0..599a3ac 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -1,5 +1,5 @@ {% set name = "vcf-rdfizer" %} -{% set version = "3.0.0" %} +{% set version = "3.0.1" %} package: name: {{ name|lower }} diff --git a/pyproject.toml b/pyproject.toml index 539a99a..d1285ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "vcf-rdfizer" -version = "3.0.0" +version = "3.0.1" description = "Docker-first VCF to RDF conversion targeting the VCF Core vocabulary, with compressed queryable representations (HDT, COTTAS), semantic validation, and data linking" readme = "README.md" requires-python = ">=3.10"