Updates needed for PyPI wheel support (part 2 of 2): WinGW and Windows updates - #572
Draft
xylar wants to merge 17 commits into
Draft
Updates needed for PyPI wheel support (part 2 of 2): WinGW and Windows updates#572xylar wants to merge 17 commits into
xylar wants to merge 17 commits into
Conversation
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>
The MinGW.gfortran build_rules.mk only recognized ESMF_MACHINE=i686, a label from the 32-bit era. On modern 64-bit MSYS2/MinGW, `uname -m` reports x86_64, so the auto-detected ESMF_MACHINE was rejected with "ESMF_MACHINE = x86_64 not recognized. Stop." before any compilation. Accept x86_64 alongside i686, mapping it through the same ABISTRING logic (ESMF_ABI selects the 32- vs 64-bit memory model), mirroring how Linux.gfortran.default already handles x86_64. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
dmp_diff.hpp declared `enum Operation { EQUAL, INSERT, DELETE }`. On
MinGW/Windows, ESMCI_Util_F.C includes <windows.h>, which #defines DELETE as
a Win32 access-right constant. The preprocessor rewrote the DELETE enumerator
to `(0x00010000L)` before compilation, producing "expected identifier before
'(' token" and "invalid conversion from 'long int' to 'Operation'". A scoped
enum class would not help, since the macro expands the token regardless.
Rename EQUAL/INSERT/DELETE to OP_EQUAL/OP_INSERT/OP_DELETE (the enum is local
to this header, included only by ESMCI_Util_F.C, which does not reference the
enumerators). This removes the collision independent of include order.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-std=c++NN / -std=cNN define __STRICT_ANSI__ on MinGW, which makes <io.h> and <unistd.h> hide their POSIX-named entry points (dup, dup2, close, open, read, write, ...). ESMF's VM code uses those names, so the strict dialect failed to compile (e.g. "dup2 was not declared in this scope"). Override the std flags in the MinGW build_rules.mk to -std=gnu++NN / -std=gnuNN, which keeps them visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MinGW/Windows has no fcntl() or F_DUPFD. The stdout/stderr redirect helper used fcntl(fd, F_DUPFD, 0), which is exactly equivalent to dup(fd) (both return the lowest available descriptor). dup() exists on all supported platforms, so switch to it unconditionally -- no behavior change on Unix, and it builds on MinGW. (The socket-nonblocking fcntl calls elsewhere are already ESMF_OS_MinGW-guarded.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mpiuni stub macros swallow their arguments via (void*)(long)(arg) to suppress unused-variable warnings. On Win64 (LLP64) long is 32-bit while pointers are 64-bit, so casting a pointer through long truncates it -- which gfortran/g++'s -fpermissive rejects as "cast ... loses precision". Cast through intptr_t (pointer-sized on every platform) instead, and include <stdint.h>. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ESMCI_VMKernel.h included only <windows.h> on MinGW, never a header declaring
the file-descriptor calls its Redirects helper uses (dup2, close). Every TU that
pulled in the header failed with "'dup2' was not declared in this scope". Include
<io.h> (the Windows CRT home of dup/dup2/close/open) and define the POSIX
STD{IN,OUT,ERR}_FILENO constants, which Windows does not provide.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The bind(C) interfaces in ESMF_InfoCDef.F90 declared their address arguments (base_address, src/dst_base_address, vmAddress) as integer(C_LONG), but the corresponding C functions (ESMC_BaseGetInfo, ESMC_InfoCopyForAttributeReference, ESMC_InfoBaseSync) take long long int&. On Win64 (LLP64) C_LONG is 4 bytes while long long / pointers are 8, so gfortran rejected passing the 8-byte ESMF_Pointer (C_SIZE_T) with "passed INTEGER(8) to INTEGER(4)". It only worked on LP64 by coincidence. Use C_LONG_LONG, which matches C long long everywhere. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ESMF_InfoGetI8 copied the 8-byte ESMF_KIND_I8 `default` into a C_LONG staging variable and passed C_LOC(local_default) to ESMC_InfoGetI8(..., long long int* def). On Win64 C_LONG is 4 bytes, so the default was truncated and C read 8 bytes from a 4-byte buffer -- a silent data bug (it compiled fine on every platform). Declare local_default as ESMF_KIND_I8 to match the argument and the C long long*. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
<windows.h> pulls in the COM headers, which #define interface as struct. When an ESMF TU includes ESMCI_VMKernel.h (-> windows.h) and later a MOAB header, MOAB's identifiers named `interface` (e.g. moab/Interface.hpp's release_interface(IFace* interface)) expand to `struct`, failing with "expected ',' or '...' before 'struct'" (seen building ESMCI_Grid.o). ESMF/MOAB use no COM, so #undef interface right after the windows.h include. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MinGW build added -DNO_TIMES only to ESMF_CXXCOMPILECPPFLAGS, so C++ files got it but C files did not. Zoltan's timer.c is C and, without NO_TIMES, includes <sys/times.h> (and <unistd.h> for sysconf), which do not exist on MinGW -- "fatal error: sys/times.h: No such file or directory" building timer.o. Add -DNO_TIMES to ESMF_CCOMPILECPPFLAGS as well; timer.h's own guard then skips the POSIX includes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ESMF's MOAB makefiles hardcoded -DLINUX on every platform. It is harmless on Linux/Darwin (dlfcn.h/dirent.h exist), but on MinGW it activates Core.cpp's `#ifdef LINUX` block, which includes <dlfcn.h> (and <dirent.h>) -- absent on Windows: "fatal error: dlfcn.h: No such file or directory" building Core.o. LINUX is used in exactly one spot in MOAB (the optional dynamic-reader loader); its WIN32 branches have POSIX #else fallbacks MinGW supports. Guard the define so it is passed on every platform except MinGW, where MOAB then uses those fallbacks. Applied to all seven MOAB subdir makefiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…link) moab/Matrix3.hpp checked `#ifdef WIN32` before ESMF_LAPACK_INTERNAL. MinGW's gcc predefines WIN32, so on Windows MOAB bound its eigen calls to the plain LAPACK names (dgeev_, dsyevd_) instead of the esmf_-prefixed ones. ESMF's internal LAPACK only exposes esmf_dgeev_/esmf_dsyevd_ (DGEEV is #defined to ESMF_DGEEV via ESMF_LapackBlas.inc), so linking ESMF_PrintInfo failed with "undefined reference to `dgeev_'/`dsyevd_'" from OrientedBox.o. Check ESMF_LAPACK_INTERNAL first so the esmf_-prefixed names are used on every platform, matching the non-Windows behavior. The WIN32 external-LAPACK case is retained for builds that are not using ESMF's internal LAPACK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fully-linked shared library (libesmf_fullylinked.dll) loads fine under ctypes, but symbol lookups fail (AttributeError: function 'ESMC_Initialize' not found). Cause: several vendored components decorate symbols with __declspec(dllexport) (yaml-cpp, MOAB/mesquite, MOAB Factory.cpp, verdict, ESMCI_Trace, nlohmann/json). Under MinGW ld, the presence of any explicit export disables auto-export of all other globals, so the undecorated extern "C" ESMC_* / ESMF C API is absent from the DLL export table. Add -Wl,--export-all-symbols to ESMF_SL_LIBOPTS to force all globals to be exported (matching the Unix .so behavior ESMPy's ctypes bindings rely on). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
xylar
force-pushed
the
update-mingw-support
branch
from
July 19, 2026 06:45
d9a8950 to
b1a43e7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes the ESMF build under the modern 64-bit MinGW-w64 / MSYS2 toolchain so that a self-contained,
pip install-able ESMPy wheel can be produced for Windows, mirroring the Linux and macOS wheels enabled by #571. It builds on #571 and should be reviewed and merged after it: the Python loader groundwork for Windows (_ESMF_OS_WINDOWSand the.dllload branch) already landed there, and this PR supplies the C/C++/Fortran and build-system changes needed to actually compile ESMF and its bundled dependencies with MinGW and to load the resulting DLL via ctypes.The wheel-building pipeline that consumes these changes is prototyped at https://github.com/xylar/esmpy-wheels, and the overall PyPI effort is tracked in #256.
Motivation
ESMF's existing Windows build configuration predates a modern 64-bit MSYS2/MinGW-w64 toolchain and no longer builds against it. Producing
libesmf_fullylinked.dllfor a wheel surfaced a series of independent issues: a strict-ISO compiler dialect that hides POSIX CRT names, LLP64 assumptions (Windowslongis 32-bit), collisions withwindows.hmacros, MinGW's symbol-export behavior hiding the ESMC C API from the DLL export table, and MOAB/Zoltan build-rule assumptions that only hold on Unix. Each commit is a small, self-contained fix; they are grouped below by area.Changes
Build system / toolchain (
build_config/MinGW.gfortran.default/build_rules.mk)-std=gnu++NN/-std=gnuNN) instead of strict ISO, so the CRT headers keep exposing the POSIX-named entry points (dup,dup2,open,close, ...) that ESMF's VM code uses.ESMF_MACHINE=x86_64(what 64-bit MSYS2/MinGW reports foruname -m) alongside the legacyi686label.-Wl,--export-all-symbolsto the shared-library link. Parts of the tree explicitly__declspec(dllexport)some symbols, which makes MinGWldstop auto-exporting everything else; that would leave the undecoratedextern "C"ESMC_*C API out of the DLL export table, where ctypes can't find it. Forcing all globals to be exported restores the Unix-.so-like behavior ESMPy expects.-DNO_TIMESfor C compiles too (the vendored Zoltantimer.cis C and includessys/times.h).VM / mpiuni
<io.h>and defineSTDIN/STDOUT/STDERR_FILENOon MinGW, and usedup()instead offcntl(F_DUPFD)insetRedirects.#undefthewindows.hinterfacemacro, which collides with MOAB.intptr_trather thanlong(Windows is LLP64, solongis 32-bit and would truncate 64-bit pointers/handles).Info / Fortran-C interface (LLP64)
C_LONG_LONGfor address arguments to match Clong long int, and stage the default I8 kind inESMF_KIND_I8rather thanC_LONG, since Windowslongis only 32-bit.MOAB (
src/Infrastructure/Mesh/src/Moab/...)-DLINUXon MinGW (it selects Unix-only headers that don't exist on Windows).WIN32branch (including inMatrix3.hpp) so the MinGW link resolves.NO_TIMESdefine through the vendored makefiles.Miscellaneous name collisions
Operationenumerators indmp_diff.hppto avoid the WindowsDELETEmacro.Relationship to other work