Skip to content

Commit 0186b4e

Browse files
committed
Merge origin/main
2 parents 9787924 + 15e3272 commit 0186b4e

7 files changed

Lines changed: 231 additions & 5 deletions

File tree

‎.queue.yml‎

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

‎skills/python-project-porting/references/gotchas-index.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Gotchas index — router for the themed gotcha files
22

3-
The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), split by theme so only the relevant slice loads. Every gotcha keeps a **permanent number** cited elsewhere as "gotcha N" (and in workflow comments as "CLAUDE.md gotcha N"). Numbers are stable IDs — **not sequential**, and four are **reused** with different content (two each of 33, 55, 56, 57), disambiguated by theme below.
3+
The porting gotchas (387 of them) live in [`references/gotchas/`](gotchas/), split by theme so only the relevant slice loads. Every gotcha keeps a **permanent number** cited elsewhere as "gotcha N" (and in workflow comments as "CLAUDE.md gotcha N"). Numbers are stable IDs — **not sequential**, and four are **reused** with different content (two each of 33, 55, 56, 57), disambiguated by theme below.
44

55
## How to find the gotcha you need
66

@@ -158,6 +158,12 @@ The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), spl
158158
is a dead stub — count the sources that branch globs, diff the built `.so` against the
159159
published CUDA one, and call an op instead of trusting a "did the extension load" flag
160160
(the xformers case).
161+
- **426** — A `-cpu` sibling can be an *x86_64-only label* rather than a portable CPU variant:
162+
where the base package's wheel is already CPU-only on every non-x86 arch, the sibling closes
163+
no riscv64 gap, is never cheaper than the base, and inherits the base's park — scan the
164+
sibling's whole release history for platform tags, compare the base's per-arch wheel sizes,
165+
and re-verify any sibling-family blocker at the revision your target actually pins
166+
(the tensorflow-cpu case).
161167

162168
### Sdist source & versioning — [`gotchas/sdist-source-and-versioning.md`](gotchas/sdist-source-and-versioning.md)
163169

@@ -355,6 +361,10 @@ The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), spl
355361
- **424** — Audit a chromium-style DEPS for riscv64-less CIPD packages with
356362
`cipd describe <pkg>/linux-riscv64` (and `gclient_eval.EvaluateCondition`) before spending a
357363
build cycle discovering them one abort at a time.
364+
- **427** — `VPYTHON_BYPASS` also picks the interpreter gsutil runs on, and a chromium-style
365+
checkout holds two gsutils: the one its DEPS pins (4.68, vendoring six 1.12) cannot import on
366+
python ≥ 3.12, so its `download_from_google_storage` hooks fail — reproducible on x86 in
367+
seconds, fixed by conditioning those test-data hooks off.
358368

359369
### The manylinux image & toolchain — [`gotchas/manylinux-image-and-toolchain.md`](gotchas/manylinux-image-and-toolchain.md)
360370

@@ -413,6 +423,11 @@ The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), spl
413423
library (XNNPACK's `zvfh` kernels), where the fix is that dependency's own feature
414424
`--define` rather than an `-march` probe — and `--keep_going` hides a single-cause failure
415425
behind five hours of unrelated progress, making it look like a timeout.
426+
- **428** — A project still on the deprecated `find_package(PythonLibs REQUIRED)` has no
427+
`Development.Module` way out of gotcha 374's static-libpython wall: satisfying it with
428+
manylinux's non-PIC `libpython3.XX.a` only moves the failure to the final link after hours,
429+
the project may already strip libpython from its own non-Windows link lines (making the
430+
`REQUIRED` vestigial), and the header-only fix rehearses locally on x86_64 in a minute.
416431

417432
### Native dependencies & linking — [`gotchas/native-deps-and-linking.md`](gotchas/native-deps-and-linking.md)
418433

@@ -572,6 +587,9 @@ The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), spl
572587
- **350** — A suite's own `try: import X except ImportError: X = None` plus
573588
- **355** — Gotcha 339 generalizes past `pytest` to any unpinned runtime dependency whose
574589
own heuristic changed across a major version — pin it for the test venv only.
590+
- **429** — A media project's suite is written against upstream's *full* FFmpeg; an FFmpeg
591+
you configure yourself has no H.264/HEVC/VP9/AV1/MP3 encoder at all, and the failures
592+
blame the wrong codec.
575593

576594
### Test failures, flakes & arch-specific bugs — [`gotchas/test-failures-and-flakes.md`](gotchas/test-failures-and-flakes.md)
577595

@@ -672,6 +690,9 @@ The porting gotchas (385 of them) live in [`references/gotchas/`](gotchas/), spl
672690
arch macros in a scratch copy to exercise its generic architecture path natively — a
673691
restricted-egress host can still prove compilability without a container (the
674692
bitsandbytes case).
693+
- **430** — A `-k`/`--ignore` change is verifiable offline with no wheel at all: rebuild the
694+
failed run's node ids into a synthetic test tree, then run the YAML-folded
695+
`CIBW_TEST_COMMAND` through `sh -c`.
675696

676697
### PR, CI, triggers, publishing & maintainer signals — [`gotchas/pr-ci-and-maintainer.md`](gotchas/pr-ci-and-maintainer.md)
677698

‎skills/python-project-porting/references/gotchas/feasibility-and-triage.md‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/feasibility-and-triage
9494
- **419** — Gotcha 411's "is the CPU backend the default?" test can pass and still not yield a
9595
port: the non-CUDA branch of a torch extension can compile operator *schemas* with no
9696
implementations, so the build succeeds and the wheel is a dead stub (the xformers case).
97+
- **426** — A `-cpu` sibling can be an *x86_64-only label* rather than a portable CPU variant:
98+
where the base package's wheel is already CPU-only on every non-x86 arch, the sibling name
99+
closes no gap and inherits the base's park (the tensorflow-cpu case).
97100

98101
---
99102

@@ -2372,3 +2375,54 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/feasibility-and-triage
23722375
note reading "1 Linux wheel (abi: py39)" on a torch-extension package is that
23732376
convention, not a pure-Python tell — and it means the port, had it been feasible,
23742377
would have been one wheel rather than a per-interpreter matrix.
2378+
2379+
426. **A `-cpu` sibling can be an *x86_64-only label*, not a portable CPU variant — if the
2380+
base package already ships a CPU-only wheel on every non-x86 arch, the sibling name
2381+
closes no riscv64 gap (the tensorflow-cpu case).** Gotcha 79's `-headless`/`-gpu`/`-lite`
2382+
sibling is usually a legitimate second port, and gotcha 50's `-binary` sibling is where
2383+
the wheels actually live. This is the third shape: a sibling that exists **only because
2384+
one architecture's default wheel is the GPU one**. `tensorflow-cpu` and `tensorflow`
2385+
2.21.0 are the same tree — `tensorflow/tools/pip_package/utils/tf_wheel.bzl` reads
2386+
`WHEEL_NAME` out of `@python_version_repo` and its own docstring says "Should be set via
2387+
`--repo_env=WHEEL_NAME=tensorflow_cpu`" — and their PyPI metadata is identical down to
2388+
the same 12 `nvidia-*; extra == "and-cuda"` requirements. What differs is only which
2389+
arch each name is *built* for, and that is the whole triage:
2390+
- **Scan the sibling's entire release history for platform tags, not just the version in
2391+
the queue entry.** One pass over `https://pypi.org/pypi/<sibling>/json`'s `releases`
2392+
counting the trailing tag of every file: `tensorflow-cpu` has published `win_amd64`
2393+
and `manylinux*_x86_64` **only** — across every release ever, zero aarch64, zero
2394+
ppc64le, zero other Linux arch, and no sdist. A sibling that has never left x86_64 is
2395+
a label for "x86_64 without the GPU bits", not a CPU variant.
2396+
- **Then compare the base package's per-arch wheel *sizes* to find which arches are
2397+
already CPU-only under the base name.** `tensorflow` 2.21.0 is 545 MB on
2398+
`manylinux_2_27_x86_64` but 268 MB on `manylinux_2_27_aarch64` — and `tensorflow_cpu`
2399+
x86_64 is 261 MB. The aarch64 wheel matching the *cpu* wheel's size rather than its own
2400+
arch's GPU wheel is the proof: on aarch64 upstream ships the CPU-only build under the
2401+
plain name, because there is no CUDA there to ship. riscv64 is in exactly that
2402+
position, so the riscv64 deliverable for "CPU-only TensorFlow" is `tensorflow`, and a
2403+
`manylinux_riscv64` wheel named `tensorflow-cpu` would invent a name upstream uses on
2404+
exactly one Linux architecture — gotcha 50's divergence-with-no-gap-closed, reached
2405+
from the sibling side.
2406+
- **So the `-cpu` sibling is never *cheaper* than the base, and inherits its verdict.**
2407+
The tempting inference is "the CPU-only variant sidesteps whatever made the full
2408+
package impractical (no CUDA build to worry about)". It is backwards: on an arch with
2409+
no CUDA the base package's build *is already* the CPU build, so the sibling saves
2410+
nothing and is the identical compile under a worse name. If the base entry is
2411+
`parked`, park the sibling for the base's reason plus the naming one, and say so in
2412+
both notes — an under-documented base park is what makes an agent re-derive this.
2413+
- **Don't inherit a sibling-family blocker citation across versions — re-verify it at
2414+
the revision your target actually pins.** jaxlib (PR #526, parked) died in XLA's
2415+
`xla/codegen/intrinsic/cpp` `embed_bitcode`, which links every LLVM backend except
2416+
RISC-V, and TensorFlow vendors the whole XLA tree at `third_party/xla/`, so that reads
2417+
like a ready-made blocker for TF too. At TF 2.21.0 it is not one: that tag's XLA
2418+
predates the restructure (the rule is `cc_ir_header` in `cc_to_llvm_ir.bzl`, whose
2419+
`ir_to_string` tool deps are just `llvm:Object`+`llvm:Support`, no per-arch CodeGen),
2420+
and `xla/backends/cpu/codegen/BUILD` *does* wire `if_llvm_riscv_available(["@llvm-project//llvm:RISCVCodeGen"])`
2421+
for the CPU JIT, with `linux_riscv64` and `riscv64_or_cross` defined in
2422+
`xla/tsl/BUILD`. Citing the sibling's hunk anyway would put a false hard blocker in the
2423+
queue; the honest note says "scope and naming, *not* Bazel-blocked like jaxlib".
2424+
- **Before costing a big Bazel build, check whether its wheel is per-interpreter.**
2425+
`_get_full_wheel_name` formats `cp{v}-cp{v}` from `HERMETIC_PYTHON_VERSION`, so TF is
2426+
one full build **per** interpreter (cp310–cp313 = 4), with none of the abi3/`py3-none`
2427+
collapse that let mediapipe serve every interpreter from a single ctypes-loaded `.so`.
2428+
That multiplier belongs in the estimate before anything else.

‎skills/python-project-porting/references/gotchas/local-validation-and-rehearsal.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/local-validation-and-r
3030
- **410** — Gotcha 188's "lower the optimisation level for the local rehearsal only" can
3131
silently produce a broken wheel when the project has a C99 `inline` helper with no
3232
`static` — and the suite still passes, because the pure-Python fallback catches it.
33+
- **430** — A `-k`/`--ignore` change is verifiable offline with no wheel at all: rebuild the
34+
failed run's node ids into a synthetic test tree, then run the YAML-folded
35+
`CIBW_TEST_COMMAND` through `sh -c`.
3336

3437
---
3538

@@ -444,3 +447,25 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/local-validation-and-r
444447
FFmpeg build into a few minutes and changed nothing about the bug under
445448
investigation — but say so in the PR, because it does mean the *decode* tests were
446449
left to CI.
450+
451+
430. **A `-k`/`--ignore` change is verifiable offline with no wheel at all: rebuild the
452+
failed run's node ids into a synthetic test tree, then run the YAML-folded
453+
`CIBW_TEST_COMMAND` through `sh -c` (the torchcodec case).** Dropping a couple of
454+
hundred failing tests by name risks two silent mistakes, each costing a full CI cycle:
455+
a clause that misses some failures (job still red) and a substring that also matches a
456+
test that *passed* (coverage lost quietly, job green). Both are decidable on the host.
457+
Scrape `FAILED <nodeid>` out of the failed job's log, generate one throwaway module per
458+
test file — a class per class, and for a parametrised test
459+
`@pytest.mark.parametrize("p", [pytest.param(0, id="<the exact param string>")])` so
460+
the ids match character for character — add a handful of ids you know passed, and run
461+
`pytest --collect-only -q -k "<expr>"` in a plain `python:3.x-slim` container: the
462+
deselected count must equal the failures in scope, and every known-passing id must
463+
still be selected. Then close the loop on the workflow file itself rather than on your
464+
draft of the expression: `yaml.safe_load()` it, pull `CIBW_TEST_COMMAND` out of the
465+
`cibuildwheel` step's `env`, assert `cmd.count("\n") == 0` (gotcha 93's folding trap)
466+
and run `subprocess.run(["sh", "-c", cmd], cwd=<synthetic tree>)` — which is exactly
467+
how cibuildwheel invokes it, so this also catches a shell-quoting bug in the `-k`
468+
string. One artefact to expect: the log truncates a long parametrised id in its
469+
`FAILED` line, so the generated tree grows both a truncated and a full variant of the
470+
same test and the "passing test dropped" list fills with truncated twins — compare
471+
names, not counts, before believing you have collateral damage.

‎skills/python-project-porting/references/gotchas/manylinux-image-and-toolchain.md‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/manylinux-image-and-to
6464
`/usr/include/ev.h`.
6565
- **401** — Rocky 10 riscv64 ships OpenBLAS, LAPACK and FFTW but no SuiteSparse, GSL or
6666
GLPK, and a numeric package's optional-extension set has to be cut along that line.
67+
- **428** — A project on the *deprecated* `find_package(PythonLibs REQUIRED)` has no
68+
`Development.Module` way out of gotcha 374's static-libpython wall — and satisfying it
69+
with manylinux's non-PIC `libpython3.XX.a` only moves the failure to the final link.
6770

6871
---
6972

@@ -1129,3 +1132,46 @@ To pull up one entry: `grep -n '^N\. ' references/gotchas/manylinux-image-and-to
11291132
`grep -c '^ERROR'` plus `grep -oE 'from target @@[^)]*' | sort -u`: one distinct
11301133
target and one distinct extension name is what tells you a single `--define` fixes
11311134
the whole run, rather than guessing from the first error you happen to see.
1135+
1136+
428. **A project still on the *deprecated* `find_package(PythonLibs REQUIRED)` has no
1137+
`Development.Module` escape hatch from gotcha 374's static-libpython wall, and the
1138+
obvious way to satisfy it is a trap that only fails at the very end of the build (the
1139+
paddlepaddle case).** manylinux configures every interpreter it ships with
1140+
`--disable-shared` — `pypa/manylinux`'s `build_scripts/build-cpython.sh`, with no
1141+
architecture condition — so `/opt/python/cp3XX-cp3XX` carries `Python.h` and a
1142+
`libpython3.XX.a` but no `libpython3.XX.so`, and the old `FindPythonLibs` module, which
1143+
never consults `PYTHON_EXECUTABLE` at all, stops configure with `Could NOT find
1144+
PythonLibs (missing: PYTHON_LIBRARIES PYTHON_INCLUDE_DIRS)`. Two things to establish
1145+
before touching it:
1146+
- **Never point `PYTHON_LIBRARY` at the static archive to make the error go away.** It
1147+
configures, compiles for hours and *then* fails at the final link. CPython's
1148+
`configure` adds `CFLAGSFORSHARED` (i.e. `-fPIC`) only
1149+
`if test ! "$LIBRARY" = "$LDLIBRARY"`, which a `--disable-shared` build never
1150+
satisfies, so `libpython3.XX.a` holds no position-independent code and cannot be
1151+
linked into a shared object on any architecture. The cycle this wastes is the whole
1152+
build, not the configure step.
1153+
- **Check whether the project already refuses to link libpython, which makes the
1154+
`REQUIRED` vestigial.** Paddle's `cmake/generic.cmake` strips `python` out of every
1155+
non-Windows target's `target_link_libraries()`, keeps it only as an
1156+
`add_dependencies()` ordering edge and links `-Wl,-undefined,dynamic_lookup`
1157+
instead — citing pybind11's own "Building manually" notes — so `PYTHON_LIBRARIES` is
1158+
read only by the `cc_test()` executables that embed an interpreter (off under
1159+
`WITH_TESTING=OFF`) and by two dead variables. A `grep -rn '${PYTHON_LIBRARIES}'`
1160+
across the cmake tree is the whole audit, and it decides whether dropping the library
1161+
changes any link line at all.
1162+
The fix is to require only the headers, and to take them from the interpreter being
1163+
built against rather than from whatever the module finds on the host: pre-seed the
1164+
`PYTHON_INCLUDE_DIR` cache entry from `sysconfig.get_config_var('INCLUDEPY')`, which
1165+
keeps pointing at the real installation from inside a virtualenv. Pre-seeding also makes
1166+
a now-optional `find_package(PythonLibs)` skip its own `find_path()` and still report the
1167+
right `PYTHONLIBS_VERSION_STRING` out of `patchlevel.h`, so a distro build that does have
1168+
a shared libpython keeps behaving exactly as before. Guard the imported target too: a
1169+
`SHARED IMPORTED` target with an empty `IMPORTED_LOCATION` is invalid, so create
1170+
`add_library(<name> INTERFACE IMPORTED GLOBAL)` when no library was found.
1171+
**All of this rehearses locally on x86_64 in a minute, with no image pull and no QEMU**,
1172+
which matters when the real build is a multi-hour riscv64 job: `include()` the patched
1173+
`.cmake` from a throwaway CMake project, stub the project's own helper modules, and force
1174+
the manylinux branch with `-DCMAKE_DISABLE_FIND_PACKAGE_PythonLibs=TRUE`; then build a
1175+
real `.so` that calls a `Py_*` function the way the project builds its extension module,
1176+
and confirm `nm -D --undefined-only` reports the symbols as `U` and `readelf -d` shows no
1177+
`libpython` in `DT_NEEDED`.

0 commit comments

Comments
 (0)