Skip to content

Updates needed for PyPI wheel support (part 1 of 2) - #571

Open
xylar wants to merge 4 commits into
esmf-org:developfrom
xylar:add-pypi-support
Open

Updates needed for PyPI wheel support (part 1 of 2)#571
xylar wants to merge 4 commits into
esmf-org:developfrom
xylar:add-pypi-support

Conversation

@xylar

@xylar xylar commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR makes the ESMPy loader "wheel-aware" so that ESMPy can be packaged as a self-contained, pip install-able wheel that bundles the compiled ESMF library, without changing behavior for existing conda, source, and HPC installations. It is the ESMF-side prerequisite for distributing ESMPy via PyPI, as requested in #256 and conda-forge/esmpy-feedstock#72.

The actual wheel-building pipeline (building ESMF + HDF5 + NetCDF from source, grafting libesmf_fullylinked and esmf.mk into the pure-Python ESMPy package, and vendoring native dependencies with auditwheel/delocate) is prototyped in a separate repository: https://github.com/xylar/esmpy-wheels. This PR contains only the changes to ESMF itself that that pipeline depends on.

Motivation

Today ESMPy locates its native ESMF library through the ESMFMKFILE environment variable or a small set of hard-coded conda paths under sys.prefix, and reads the absolute ESMF_LIBSDIR baked into esmf.mk at build time. Neither assumption holds for a relocatable pip wheel: the library ships inside the installed package, pip can place that package anywhere, and the baked-in absolute path is meaningless after installation. This PR teaches the loader to discover and load a bundled ESMF while leaving every existing installation path untouched.

Changes

  • Wheel-aware esmf.mk discovery. The lookup is factored into _find_esmf_mk and a new _esmf_mk_is_bundled helper (src/addon/esmpy/src/esmpy/interface/loadESMF_helpers.py). The resolution order is unchanged for existing users — ESMFMKFILE first, then the conda layouts under sys.prefix — with a new step in between that finds an esmf.mk bundled inside the installed package (esmpy/_esmf/lib/esmf.mk). When, and only when, esmf.mk lives inside the package, ESMF_LIBSDIR is resolved relative to it rather than trusting the absolute build-time path.
  • Real-MPI runtime preload. For an MPI-enabled wheel (ESMF_COMM != mpiuni), libmpi is supplied by a separate runtime wheel (e.g. mpich) that installs it under the wheel "data" scheme — a directory not on the default dynamic-loader search path. The loader now best-effort preloads libmpi with RTLD_GLOBAL before libesmf is loaded, so its MPI symbols bind correctly. This is skipped for serial (mpiuni) builds and whenever MPI is already on the loader path (conda/HPC/system MPI), so it is a no-op there.
  • Variant distribution-name resolution (src/addon/esmpy/src/esmpy/__init__.py). An MPI build may be published under a variant distribution name (e.g. esmpy-mpich) that still ships the esmpy import package. The metadata lookup no longer assumes the distribution is named esmpy; it resolves whichever installed distribution provides the esmpy package, with a fallback for Python 3.8/3.9.
  • Windows loader groundwork. A _ESMF_OS_WINDOWS constant and a Windows .dll load branch (using os.add_dll_directory) are included so the same loader can drive a Windows wheel. The larger Windows ESMF/dependency build changes will follow in a separate PR; this PR only lays the Python-side groundwork.

Compatibility

All changes are no-ops for existing conda, source-build, and HPC installations: ESMFMKFILE and the conda sys.prefix paths are tried exactly as before, the baked-in ESMF_LIBSDIR is used unless esmf.mk is detected inside the package, and the MPI preload is skipped whenever libmpi is already reachable.

Tests

Extended src/addon/esmpy/test/test_interface/test_loadESMF_helpers.py to cover _find_esmf_mk (environment variable, bundled, conda, and not-found cases) and _esmf_mk_is_bundled.

xylar and others added 3 commits July 18, 2026 21:16
Prepare the ESMPy loader for self-contained PyPI wheels that bundle
libesmf_fullylinked and esmf.mk inside the installed package.

- Add a package-relative guess (esmpy/_esmf/lib/esmf.mk) to esmf.mk
  discovery, tried before the conda sys.prefix locations.
- When esmf.mk is found inside the installed esmpy package (a wheel),
  resolve ESMF_LIBSDIR relative to esmf.mk instead of the absolute path
  baked in at build time, which is invalid once pip relocates the package.
- Add a Windows (nt) branch that loads libesmf_fullylinked.dll and puts
  its bundled dependencies on the DLL search path, plus a private
  _ESMF_OS_WINDOWS constant. Forward-prep for the Windows wheel phase.

Discovery and bundled-detection logic are factored into testable helpers
(_find_esmf_mk, _esmf_mk_is_bundled) in loadESMF_helpers.py with unit
tests. Behavior is unchanged for conda/source/HPC installs, where
ESMFMKFILE is set or esmf.mk lives outside the package.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A real-MPI ESMPy wheel (ESMF_COMM != mpiuni) links libesmf_fullylinked against
the C MPI runtime (libmpi). To keep the wheel from double-shipping an MPI, the
runtime is provided by a separate wheel (e.g. `mpich`) that installs libmpi into
<prefix>/lib via the wheel "data" scheme -- a directory not on the default
dynamic-loader search path. Preload it with RTLD_GLOBAL before dlopening
libesmf, discovering it via sysconfig's data path. Best-effort and gated on a
real-MPI build, so serial (mpiuni) wheels and conda/HPC/system-MPI installs
(libmpi already on the loader path) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
esmpy/__init__.py fetched its own version/metadata via a hard-coded
importlib.metadata.metadata("esmpy"). That breaks when ESMPy is installed under
a variant distribution name that ships the same `esmpy` import package -- e.g.
`esmpy-mpich`, the MPI-enabled wheel -- raising PackageNotFoundError at import
(and the version it sets feeds loadESMF's ESMF-version check).

Fall back to resolving whichever distribution provides the `esmpy` package
(packages_distributions on 3.10+, a distributions() top_level.txt scan on
3.8/3.9) when the direct "esmpy" lookup misses. The serial `esmpy` distribution
still resolves via the direct lookup, unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend the loadESMF.py libmpi preload candidate list to include Open MPI's
C libmpi soname (libmpi.so.40 / libmpi.40.dylib) alongside MPICH's
libmpi.so.12, so the esmpy-openmpi wheel finds and preloads the libmpi
shipped by the PyPI `openmpi` runtime wheel. The list stays comm-agnostic:
the first present candidate is loaded; a serial or system-MPI install skips
it. Serial and esmpy-mpich behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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