Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-crate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
# Knowledge-graph data releases (kg-snapshots-v*) are Releases too, and
# every one of them failed this workflow. Only a v* tag is an engine release.
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# The engine's full test suite runs before anything is uploaded (see
# "Test every crate"), and CI gives that suite 45 minutes on its own.
timeout-minutes: 120
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/publish-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
# only routes were a second release or a hand-built image, and the same
# gap was already closed for npm (#1292) and PyPI (#1249).
if: startsWith(github.ref, 'refs/tags/v') || inputs.version != ''
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
Expand Down Expand Up @@ -186,7 +186,7 @@ jobs:
# available in a job-level `if`, and a gate that silently evaluated to
# empty would skip the mirror on every release without saying why.
if: vars.ECR_PUBLIC_REGISTRY != ''
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
# Same as publish-crate, publish-npm and publish-pypi. The role ARN lives
# here as an environment secret, so it is reachable only from a job that
# names this environment -- and any required-reviewer rule added later
Expand Down Expand Up @@ -293,7 +293,7 @@ jobs:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
- runner: ubuntu-24.04
arch: amd64
- runner: ubuntu-24.04-arm
arch: arm64
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
name: Publish to npm
# kg-snapshots-v* data releases are Releases too; only a v* tag is an engine release.
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 15

environment: npm
Expand Down
131 changes: 118 additions & 13 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ name: Publish Python SDK to PyPI
on:
release:
types: [published]
# The matrix only ever ran at release time, on a tag, after the tag existed:
# every break was discovered at the one moment when the options are a hotfix
# or a retag. v1.12.0's Windows wheel failed on a runner image that rolled
# under us (MSVC 14.51 / Visual Studio 18) with no commit from this repo.
# A weekly build of the same matrix publishes nothing and turns that class of
# failure into ordinary CI noise on a Tuesday instead of a blocked release.
schedule:
- cron: "17 5 * * 2"
workflow_dispatch:
inputs:
target:
Expand Down Expand Up @@ -47,7 +55,7 @@ jobs:
# manylinux_2_28 (AlmaLinux 8) rather than `auto` (manylinux2014,
# CentOS 7), whose clang is too old for bindgen. See before-script-linux.
- platform: linux-x86_64
runner: ubuntu-latest
runner: ubuntu-24.04
target: x86_64
manylinux: "2_28"
sccache: "true"
Expand Down Expand Up @@ -78,7 +86,7 @@ jobs:
manylinux: "off"
sccache: "true"
- platform: windows-x64
runner: windows-latest
runner: windows-2022
target: x64
manylinux: "off"
sccache: "false"
Expand All @@ -87,6 +95,29 @@ jobs:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
fetch-depth: 0

- name: Select the release lock policy
id: lock_policy
shell: bash
run: |
set -euo pipefail
lockfile="$PYTHON_SDK_DIR/Cargo.lock"
if [ -f "$lockfile" ]; then
echo 'cargo_flags=--locked' >> "$GITHUB_OUTPUT"
else
# A backfill may check out a tag older than this lockfile policy.
# Only an ancestor of its introduction qualifies; a new release
# that deletes/omits the lockfile must fail, not resolve afresh.
introduced=$(git log --reverse --diff-filter=A --format=%H "$GITHUB_SHA" -- "$lockfile" | sed -n '1p')
if [ -n "$introduced" ] && git merge-base --is-ancestor HEAD "$introduced"; then
echo '::warning::legacy source predates the Python lockfile; resolving its original manifest'
echo 'cargo_flags=' >> "$GITHUB_OUTPUT"
else
echo '::error::Python Cargo.lock is missing from a source that does not predate the lockfile policy'
exit 1
fi
fi

- uses: actions/setup-python@v5
with:
Expand All @@ -104,19 +135,23 @@ jobs:
working-directory: ${{ env.PYTHON_SDK_DIR }}
target: ${{ matrix.target }}
manylinux: ${{ matrix.manylinux }}
args: --release --out dist
args: --release --out dist ${{ steps.lock_policy.outputs.cargo_flags }}
# Per-platform: windows-x64 builds without it. MSVC 14.51 (Visual
# Studio 18, new on windows-latest) made every rocksdb translation
# unit fail under the sccache wrapper with
# Studio 18, which arrived on the then-floating `windows-latest`)
# made every rocksdb translation unit fail under the sccache wrapper
# with
# c1xx: fatal error C1083: Cannot open source file: '/WX-'
# -- cl.exe receiving `/WX-` as an input path rather than a flag.
# The flag is in neither cc-rs nor librocksdb-sys (both checked at
# the versions involved), so it is introduced between cc-rs and
# cl.exe, which is where sccache sits. v1.11.0 built fine here on
# 2026-10-05; nothing in sdk/python's native build changed between
# the two tags, so the trigger is the toolchain, not this repo.
# A cache miss costs minutes; this cost 1.12.0's entire PyPI
# release, because `publish` needs all six wheels.
# the two tags, so the trigger was the toolchain, not this repo.
#
# The runner is pinned to windows-2022 below, so sccache would very
# likely work again -- it stays off anyway. A cache miss costs
# minutes on one job; this cost v1.12.0 its entire PyPI release, and
# the pin will eventually have to move.
sccache: ${{ matrix.sccache }}
# librocksdb-sys generates its bindings with bindgen, which needs
# libclang, and the manylinux images ship none: v1.8.0's x86_64 wheel
Expand Down Expand Up @@ -157,7 +192,7 @@ jobs:
sdist:
name: sdist
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -188,11 +223,23 @@ jobs:
# A dispatch to the real PyPI must build a tag: the one it runs on, as
# tag-release.yml's does (#1249), or the one named by `ref`. From a branch
# it would upload whatever that branch holds.
#
# `!cancelled()` rather than an implicit all-green `needs`: one platform
# failing used to withhold every wheel AND the sdist, so v1.12.0 reached
# PyPI as nothing at all over a Windows toolchain problem. That is a
# self-inflicted outage -- five working wheels help five platforms, and the
# sdist is the floor for the sixth. `skip-existing: true` below already
# makes the backfill run safe once the platform is fixed, so a partial
# upload is a release that can be completed rather than one that must be
# redone. The matrix-complete job keeps the run red so nobody reads a
# partial release as a clean one.
if: >-
github.event_name == 'release'
|| (inputs.target == 'testpypi')
|| (inputs.target == 'pypi' && (github.ref_type == 'tag' || startsWith(inputs.ref, 'v')))
runs-on: ubuntu-latest
!cancelled()
&& needs.sdist.result == 'success'
&& (github.event_name == 'release'
|| (inputs.target == 'testpypi')
|| (inputs.target == 'pypi' && (github.ref_type == 'tag' || startsWith(inputs.ref, 'v'))))
runs-on: ubuntu-24.04
timeout-minutes: 20

# Trusted Publishing: PyPI verifies a short-lived OIDC token naming this
Expand All @@ -212,6 +259,37 @@ jobs:
- name: List artifacts
run: ls -la dist/

# Says plainly which platforms a user will not find a wheel for, in the
# run summary rather than only in a log nobody opens on a green release.
- name: Report any platform with no wheel
run: |
set -euo pipefail
declare -A want=(
[manylinux_2_28_x86_64]=linux-x86_64 [manylinux_2_28_aarch64]=linux-aarch64
[macosx_10_15_x86_64]=macos-x86_64 [macosx_11_0_arm64]=macos-aarch64
[win_amd64]=windows-x64
)
missing=()
for tag in "${!want[@]}"; do
compgen -G "dist/*${tag}*.whl" > /dev/null || missing+=("${want[$tag]}")
done
{
echo "### PyPI upload contents"
echo
for f in dist/*; do echo "- \`$(basename "$f")\`"; done
} >> "$GITHUB_STEP_SUMMARY"
if [ ${#missing[@]} -gt 0 ]; then
{
echo
echo "> [!WARNING]"
echo "> **No wheel for: ${missing[*]}.** Those platforms fall back to the"
echo "> sdist and must compile RocksDB from source. Fix the platform, then"
echo "> re-dispatch with the same \`ref\`: \`skip-existing\` tops up the"
echo "> missing files without touching what already landed."
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning::no wheel for: ${missing[*]} -- uploading a partial set"
fi

# Two steps rather than one with a computed repository-url: the action
# defaults to PyPI when the input is absent, and feeding it an empty
# string is not the same as omitting it.
Expand All @@ -233,3 +311,30 @@ jobs:
with:
packages-dir: dist/
skip-existing: true

# Publishing a partial set is better than publishing nothing, but it must not
# look like a clean release. This is the job that fails, after the upload, so
# the red run is attached to the release that is actually incomplete.
matrix-complete:
name: every platform built
needs: [wheels, sdist, publish]
if: >-
!cancelled()
&& (github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v'))
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Fail if any platform did not build
run: |
set -euo pipefail
w='${{ needs.wheels.result }}'; s='${{ needs.sdist.result }}'
echo "wheels=$w sdist=$s publish=${{ needs.publish.result }}"
if [ "$s" != success ]; then
echo "::error::the sdist did not build, so nothing was published"
exit 1
fi
if [ "$w" != success ]; then
echo "::error::at least one wheel did not build. Anything that did was still published (see the publish job summary); fix the platform and re-dispatch with the same ref to top up the missing files."
exit 1
fi
echo "every platform built"
6 changes: 5 additions & 1 deletion sdk/python/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Cargo.lock is deliberately NOT ignored here. This is its own workspace
# (excluded from the root one), so the root lockfile does not constrain it and
# an ignored lock meant the native build re-resolved on every run: `cc` moved
# 1.6.0 -> 1.9.0 between v1.11.0 and v1.12.0 with no commit from this repo.
# A published wheel should be reproducible from the tag that built it.
target/
Cargo.lock
*.so
*.dylib
__pycache__/
Expand Down
Loading
Loading