Skip to content

python-fcl: add build-python-fcl.yml for riscv64 wheels - #1770

Merged
luhenry merged 6 commits into
mainfrom
python-fcl
Sep 12, 2026
Merged

python-fcl: add build-python-fcl.yml for riscv64 wheels#1770
luhenry merged 6 commits into
mainfrom
python-fcl

Conversation

@luhenry

@luhenry luhenry commented Sep 11, 2026

Copy link
Copy Markdown
Member

Compiles a Cython wrapper over FCL, libccd and octomap, all three built from source at build time. Upstream publishes no riscv64 wheel.

Mirrors upstream's push.yml, driven entirely through pyproject.toml's [tool.cibuildwheel].

Differs from upstream

  • before-all skips fcl's own test-binary build (-DBUILD_TESTING=OFF) - not shipped in the wheel, cuts ~25min per interpreter.

Testing

  • same as upstream

License: Wheel bundles fcl, libccd and octomap (all BSD); upstream ships none of their licence text in any platform's wheel, so the build adds it.

Patches

  • 0001-Widen-license-files-to-cover-the-vendored-fcl-libcc.patch - To upstream [same gap in every python-fcl wheel on PyPI, not riscv64-specific]. Without it dist-info/licenses ships only python-fcl's own LICENSE.
  • 0002-Guard-against-an-empty-but-present-LD_LIBRARY_PATH-.patch - To upstream [only reproduces when LD_LIBRARY_PATH is exported empty, which our build container does but upstream's own CI apparently does not]. Without it -lfcl is silently swallowed as the path argument of a bare -L (from "".split(":")), so libfcl is never linked and the wheel fails to import with undefined symbol: typeinfo for fcl::CollisionGeometry<double>.

Built on cp312; 15 passed.

Builds eigen/libccd/octomap/fcl from source via build_dependencies/
install_linux.sh (mirrors upstream's own push.yml), then the Cython
extension against them. Widens license-files to ship the vendored
fcl/libccd/octomap licences alongside python-fcl's own.
All 4 matrix legs on PR #1770 failed identically at import with
'undefined symbol: typeinfo for fcl::CollisionGeometry<double>'
(_ZTIN3fcl17CollisionGeometryIdEE). fcl's own CMake config sets no
-fvisibility, and Debian's riscv64 buildd builds+tests the same
upstream FCL 0.7.0 successfully, so this isn't a compiler/arch RTTI
limitation. auditwheel's repair log shows zero libraries vendored
(only a platform-tag relabel), unlike the official x86_64 wheel,
which points at a link-time/dependency-resolution gap in our build
rather than fcl's source. Trims the matrix to cp312 and adds before-
build diagnostics (ldd + nm -D on the freshly built libfcl.so) to
confirm whether the symbol is exported from the library itself.
Also skips fcl's own ~25min test-binary build (BUILD_TESTING=OFF,
not shipped in the wheel) to speed up iteration.
before-build confirmed libfcl.so exports the missing symbol
(_ZTIN3fcl17CollisionGeometryIdEE, weak+global, verified via nm -D)
and ldd resolves all of libfcl.so's own deps cleanly, so the gap is
between the built Cython extension and that export, not the library.
Dumps the extension .so's own ldd/NEEDED/RUNPATH/undefined-symbol
state via find_spec (no import, so it doesn't trip the crash) before
the pytest run that's expected to still fail.
find_spec('fcl.fcl') still imports the fcl package first (to locate
the submodule), tripping the same crash with none of the intended
diagnostics printed. importlib.metadata.distribution(...).files
lists the RECORD without importing anything, so use that to find the
.so path instead.
readelf -d on the built extension shows liboctomap correctly NEEDED
and vendored, but libfcl.so is entirely absent from NEEDED despite
being requested via setup.py's libraries=["fcl", "octomap"] and
despite the extension having genuine unresolved references into it
(nm -D --undefined-only lists exactly fcl::CollisionGeometry<double>'s
ctor/dtor/typeinfo/vtable, which only exist via fcl's own
`extern template ... FCL_EXPORT CollisionGeometry<double>` pattern).
libfcl.so does export those symbols (weak+global, confirmed via
before-build's nm -D --defined-only), so this looks like the image's
--as-needed linker default dropping -lfcl. LDFLAGS=-Wl,--no-as-needed
forces every requested -l onto NEEDED regardless.
… clean up

setup.py's get_libraries_dir() checks "LD_LIBRARY_PATH" in os.environ
and unconditionally splits its value; our build container exports it
empty (present, not unset), so "".split(":") appends an empty string
to library_dirs. distutils turns that into a bare -L with no argument,
which swallows the following -lfcl as -L's path instead of a library
request (confirmed via the actual printed link command: '-L -lfcl').
-loctomap, being last on the line, is unaffected - matching the
asymmetry seen in the wheel (liboctomap present and vendored, libfcl
entirely absent from NEEDED). That's the real source of the
'undefined symbol: typeinfo for fcl::CollisionGeometry<double>'
failure on every interpreter; it has nothing to do with riscv64's
toolchain or --as-needed (confirmed: forcing -Wl,--no-as-needed in
610ff2f made no difference, since -lfcl was never parsed as a
library flag to begin with).

0002-*.patch guards get_libraries_dir() (and the identical CPATH
pattern in get_include_dirs()) against an empty-but-present value.
Drops the now-unneeded LDFLAGS override and the debug-only
before-build/test-command introspection, and restores the full
cp312/cp313/cp314/cp314t matrix.
luhenry added a commit that referenced this pull request Sep 11, 2026
@luhenry
luhenry merged commit 18e70d6 into main Sep 12, 2026
10 checks passed
@luhenry
luhenry deleted the python-fcl branch September 12, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant