You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR #70 (making-wheel-3) builds Linux and macOS wheels via scikit-build-core plus
cibuildwheel. Windows is not covered. This issue tracks what is left to add a
Windows wheel.
Scope: vanilla OpenMC only, no DAGMC, no libMesh, no MPI, no UWUW. Dropping
DAGMC removes almost the entire dependency problem: for a vanilla build the only
external dependency is HDF5 (C + HL). pugixml and fmt are vendored submodules,
PNG is optional, and Catch2 is tests-only. Compare against tools/ci/cibw_before_all_linux.sh, which builds HDF5, netcdf, MOAB, DAGMC,
Embree and double-down.
The port is smaller than expected
A survey of the tree shows upstream already carries a fair amount of MSVC
compatibility, so most of the classic porting work is done:
src/error.cpp and src/progress_bar.cpp are the only POSIX includes
(unistd.h), and both are already #if defined(__unix__) guarded with
non-POSIX fallbacks. is_terminal() simply returns false.
src/source.cpp puts dlopen/dlsym behind HAS_DYNAMIC_LINKING, so
compiled custom sources self-disable rather than breaking the build.
src/mcpl_interface.cpp and src/ncrystal_load.cpp already have complete _WIN32 branches using LoadLibrary and _popen.
src/mesh.cpp has _MSC_VER branches.
UNREACHABLE() in include/openmc/error.h already has a non-GCC fallback.
CMakeLists.txt has if(MSVC) handling, including the H5_BUILT_AS_DYNAMIC_LIB definition and the LNK1149 output-name workaround.
852f927 (_USE_MATH_DEFINES / M_PI) was itself a Windows portability fix.
No HOME or /tmp assumptions in src/ or openmc/. The only hardcoded / paths in the Python package are HDF5 dataset paths, not filesystem paths.
One helpful structural property: the Python binding is ctypes, not pybind11 or
Cython. Nothing crosses a C++ or CPython ABI boundary, only a C ABI. So the DLL
can be built with a different toolchain than CPython was built with, which makes /openmp:llvm, clang-cl and MinGW-w64 all viable options. That matters for the
OpenMP item below.
Upstream fixes that have landed since this survey
Two commits merged upstream on 2026-08-05 take work off the list above.
The survey bullet claiming no alternative tokens outside src/external/ was
wrong. include/openmc/mesh.h carried two and operators, one in each of the
two sanitize_angular_index helpers. MSVC accepts and/or/not only under /permissive-, which CMake does not pass by default, so both sites were a hard
error C2065 in a header included widely across the tree. They are now &&.
Re-grepped include/openmc and src after the merge, excluding src/external/: no alternative operator tokens remain outside comments and
string literals. The bullet is now accurate rather than aspirational.
Nothing stops this regressing, though. format-check.yml only runs
clang-format, which does not rewrite alternative tokens, so a fresh and can
land at any time and will surface only on the next Windows CI run. A grep guard
in that workflow would keep the property from decaying.
src/cell.cpp had two iterator-invalidation bugs in Region::Region. The
DeMorgan complement-removal loop kept using it after expression_.erase(it),
and the simple-cell token cleanup did erase(it); it--, which decrements past begin() when the first element matches. expression_ is a vector<int32_t>,
so both are undefined behaviour. They are now fixed, using erase's return value
and remove_if respectively.
This one bears on checklist item 9 rather than on getting a build green. Cell
region parsing runs for essentially every model, so it sits on the hot path of
the whole Python test suite, and it is exactly the shape of bug that behaves
differently per standard library. MSVC's STL ships checked iterators, and a
non-release Windows build (_ITERATOR_DEBUG_LEVEL defaults to 2 under /MDd)
turns both of these into an assertion abort rather than the silent misbehaviour
a libstdc++ release build happens to get away with. Release wheels build with _ITERATOR_DEBUG_LEVEL 0, so this would not necessarily have shown up in the
wheel itself, but it was a good candidate for a confusing Windows-only failure
during test triage.
Probe results: the vanilla Windows build now works
The probe has run properly and the survey below is wrong in three places. Read
this section first.
A vanilla, no-DAGMC, serial Windows build is complete. The openmp-off leg
produces both artifacts and exports everything openmc.lib needs:
libopenmc.dll 5,698,048
openmc.exe 14,336
0 of 24 required data symbols missing
All required data symbols are exported.
Correction 1: it is 24 in_dll globals, not five
Item 1 below and the old checklist both say five. The real number is 24.
Fourteen of them are behind the _DLLGlobal descriptor in openmc/lib/core.py rather than in a literal in_dll call, which is why a
grep for in_dll missed them: verbosity, event_based, n_particles, gen_per_batch, cmfd_run, entropy_on, n_inactive, max_lost_particles, need_depletion_rx, output_summary, restart_run, run_CE, weight_windows_on and rel_max_lost_particles. The probe now checks all 24
and fails on any unexpected absence.
Worth noting the old probe reported its failures and then exited 0, which is why
it looked green on 2026-08-01 while four symbols were missing.
Correction 2: a .def file is not needed, the discriminator is const
The "watch item" in item 1 guessed that data exports might need a hand-written .def file. They do not. bindexplib emits mutable data but not read-only
data, and nm on a Linux build shows the split exactly: the four flags that
vanished from the Windows export table are the only four in section R, while
every symbol that survived is in B or D.
R DAGMC_ENABLED R UWUW_ENABLED R LIBMESH_ENABLED R STRICT_FP_ENABLED
B n_coord_levels B current_batch B openmc_err_msg B path_statepoint_c ...
So the fix is an OPENMC_API macro (include/openmc/export.h) that resolves to dllexport while building the library, dllimport for anything linking it, and
nothing on POSIX or in a static MSVC build. That also fixed the four unresolved
externals that stopped openmc.exe linking, which were the same problem seen
from the other side: settings::run_mode, settings::solver_type, mpi::master and openmc_err_msg.
Correction 3: OpenMP was never the blocker
Item 4 below treats OpenMP as the main source of friction and expects plain /openmp to reject the 9 collapse sites. Both legs of the 2026-08-01 probe
failed identically, including openmp-off, because the real blocker was two and tokens in mesh.h (openmc-dev#4048). Every other error in that log
was cascade fallout from a header that failed to parse.
With that fixed, /openmp:llvm accepts collapse and atomic capture and
rejects only four things:
seq_cst on atomic, shared_array.h:75 and :80
a non-conforming collapse bound, plot.cpp:1691. The pragma uses pixels()[0]
and pixels()[1] as bounds while width and height locals already exist two
lines above, so this one is nearly free
a reduction on a non-static member (C3028), flat_source_domain.cpp:1078 and random_ray_simulation.cpp:432, which GCC and Clang accept as an extension
So OpenMP is a four-site source fix rather than a toolchain question. Only the
two seq_cst sites need real thought, since dropping the clause weakens
ordering on a lock-free append. That leg is marked experimental in the probe
matrix so its known failure does not show up as a red check.
A pre-existing bug found while inventorying the symbols
settings::run_mode and settings::rel_max_lost_particles are declared in namespace openmc::settings without extern "C", so they are name-mangled,
while openmc/lib looks them up unmangled. Both therefore raise on every
platform, not just Windows:
rel_max_lost_particles has been broken since it was introduced: d763806
renamed it on both the C++ and Python sides to make the names match, but with no extern "C" the symbol never matched under either spelling. Neither attribute
is covered by a test, which is how it survived. This is not a Windows problem and
is fixed separately.
Remaining work
1. Build a DLL instead of a static library (blocker)
CMakeLists.txt currently does:
if(MSVC)
# Use static library (otherwise explicit symbol portings are needed)add_library(libopenmcSTATIC${libopenmc_SOURCES})
openmc/lib/__init__.py calls CDLL(), which can never load a static library,
so no importable openmc.lib is possible as things stand. There are zero __declspec(dllexport) annotations in the tree, but annotating the 204 openmc_*
functions in include/openmc/capi.h by hand is not necessary. The comment's
premise is exactly what WINDOWS_EXPORT_ALL_SYMBOLS exists to avoid:
This touches only the MSVC path, so Linux and macOS output is unchanged.
The existing if(NOT MSVC) guard on OUTPUT_NAME can stay as is: the target
keeps the name libopenmc, producing libopenmc.dll, which the Python glob
already matches.
Watch item: the functions are the easy part. openmc/lib/ reads five
globals via ctypes in_dll: DAGMC_ENABLED, UWUW_ENABLED, LIBMESH_ENABLED, STRICT_FP_ENABLED and n_coord_levels. All five are declared extern "C"
(in dagmc.h, mesh.h, output.h and geometry.h), so they are unmangled,
and CMake's bindexplib should emit them with the DATA keyword in the generated .def. Data exports are the documented weak spot of WINDOWS_EXPORT_ALL_SYMBOLS
though, so this is the most likely place to need a hand-written .def fallback.
Worth testing early with a one-line import openmc.lib.
Windows CMake installs DLLs to bin/ (the RUNTIME destination), so this needs "bin" adding to the list. It is a no-op on POSIX, where nothing matches libopenmc* in bin/.
Only the library file lookup needs this. The lib_path line immediately below it
feeds linking, and the Windows import library (.lib) is an ARCHIVE artifact that
still lands in lib, so lib_path is deliberately left alone: adding bin there
would change openmc.lib_path on Linux for no benefit.
3. A scratch Windows CI job
The real bottleneck is the feedback loop, since Windows cannot be reproduced
locally. A continue-on-error: true job on windows-latest that installs HDF5
via vcpkg and runs cmake --build touches no existing files and answers the
OpenMP question below immediately. Highest value per unit of effort of anything
in this list, and worth doing first alongside item 1.
4. OpenMP version, the main source of friction
The code uses OpenMP features newer than MSVC's default /openmp, which is
frozen at OpenMP 2.0:
collapse (OpenMP 3.0), 9 sites
atomic capture (3.1)
atomic write seq_cst and atomic capture seq_cst (5.0)
MSVC's default /openmp rejects all three. Options, in rough order of
preference:
/openmp:llvm, which is the smallest change if it covers the constructs used
clang-cl
MinGW-w64, which gives full OpenMP support but a different runtime
-DOPENMC_USE_OPENMP=OFF for a serial-only first wheel, as a fallback to get
something shipping while the above is sorted out
Because the binding is ctypes, options 2 and 3 do not create an ABI problem with
CPython. Item 3 above should tell us which of these is needed.
5. Packaging glue
Add a [tool.cibuildwheel.windows] section to pyproject.toml.
Swap auditwheel for delvewheel in repair-wheel-command. delvewheel also
injects the os.add_dll_directory calls that Python 3.8+ needs on Windows.
Ensure openmc.exe lands somewhere openmc/executor.py can find, since it
shells out to openmc via subprocess.
get_extra_libraries() needs no change: its non-darwin branch already
resolves to <site-packages>/openmc.libs, which is delvewheel's default output
directory as well as auditwheel's.
This is best left until items 1 and 3 confirm the DLL builds and exports the five in_dll globals.
6. Fix the generated console entry point
cmake/GenerateScript.cmake writes the openmc console script that ends up in
the wheel's scripts directory. It is POSIX-only in three separate ways, so as
things stand pip install openmc on Windows would place an unrunnable file on
PATH pointing at a target that does not exist:
It starts with a #!/usr/bin/env python3 shebang. Windows does not honour
shebangs, and the installed file is named openmc with no extension, so
nothing can execute it.
It builds the target path by joining ${CMAKE_INSTALL_BINDIR} and the bare
script name, which misses the .exe suffix the installed executable actually
has on Windows.
It dispatches with os.execv. That exists on Windows but does not replace the
process the way it does on POSIX: the parent returns immediately, which breaks
exit codes and console handling for anything that shells out to openmc.
Two candidate fixes, neither large:
On Windows, install openmc.exe directly into SKBUILD_SCRIPTS_DIR and skip
the wrapper entirely. pip already puts that directory on PATH.
Replace the generated script with a real [project.scripts] console entry
point, which gets a proper .exe launcher generated for it on Windows.
This is closely related to the openmc/executor.py checklist item below, since
that module shells out to openmc by name through subprocess.
Risks and unknowns
None of the above has been compiled. This is a static survey of the tree, so
treat the effort estimates as provisional.
The test suite has never been run on Windows. "It builds and imports" is a much
lower bar than "results match", and latent path-handling or regression
tolerance issues will likely only surface once the build works.
Rough estimate: a few days to a first green wheel if OpenMP cooperates, one to
two weeks to something shippable. The long pole is iterating CI on a platform
that cannot be reproduced locally, not the C++ itself.
Checklist
Scratch windows-latest CI job, allowed to fail, to establish a feedback loop (033fa82)
CMakeLists.txt: SHARED plus WINDOWS_EXPORT_ALL_SYMBOLS on the MSVC path (033fa82)
[tool.cibuildwheel.windows] plus delvewheel repair command. Note [tool.cibuildwheel] build is an allowlist of cp3XX-manylinux_x86_64, so cp3XX-win_amd64 has to be added there too or nothing is built
Fix the generated console entry point in cmake/GenerateScript.cmake (shebang, missing .exe, os.execv)
OpenMP, if wanted: 4 sites, of which only the 2 seq_cst ones need care. Not required for a serial first wheel
Run the Python test suite on Windows and triage failures
Dropped from this list: "confirm openmc.exe is discoverable by openmc/executor.py" needs no code change. executor.py calls subprocess.Popen(['openmc', ...]) with no shell=True, and Windows CreateProcess searches PATH and appends .exe to an extensionless name. It
only depends on the console entry point item putting an openmc.exe on PATH, so
it is not separate work.
Context
PR #70 (
making-wheel-3) builds Linux and macOS wheels via scikit-build-core pluscibuildwheel. Windows is not covered. This issue tracks what is left to add a
Windows wheel.
Scope: vanilla OpenMC only, no DAGMC, no libMesh, no MPI, no UWUW. Dropping
DAGMC removes almost the entire dependency problem: for a vanilla build the only
external dependency is HDF5 (C + HL). pugixml and fmt are vendored submodules,
PNG is optional, and Catch2 is tests-only. Compare against
tools/ci/cibw_before_all_linux.sh, which builds HDF5, netcdf, MOAB, DAGMC,Embree and double-down.
The port is smaller than expected
A survey of the tree shows upstream already carries a fair amount of MSVC
compatibility, so most of the classic porting work is done:
src/error.cppandsrc/progress_bar.cppare the only POSIX includes(
unistd.h), and both are already#if defined(__unix__)guarded withnon-POSIX fallbacks.
is_terminal()simply returnsfalse.src/source.cppputsdlopen/dlsymbehindHAS_DYNAMIC_LINKING, socompiled custom sources self-disable rather than breaking the build.
src/mcpl_interface.cppandsrc/ncrystal_load.cppalready have complete_WIN32branches usingLoadLibraryand_popen.src/mesh.cpphas_MSC_VERbranches.UNREACHABLE()ininclude/openmc/error.halready has a non-GCC fallback.CMakeLists.txthasif(MSVC)handling, including theH5_BUILT_AS_DYNAMIC_LIBdefinition and the LNK1149 output-name workaround._USE_MATH_DEFINES/M_PI) was itself a Windows portability fix.(
and/or/not) outsidesrc/external/were claimed clear here too, whichwas wrong. See Use && instead of the alternative token 'and' in mesh.h openmc-dev/openmc#4048 below.
HOMEor/tmpassumptions insrc/oropenmc/. The only hardcoded/paths in the Python package are HDF5 dataset paths, not filesystem paths.One helpful structural property: the Python binding is ctypes, not pybind11 or
Cython. Nothing crosses a C++ or CPython ABI boundary, only a C ABI. So the DLL
can be built with a different toolchain than CPython was built with, which makes
/openmp:llvm, clang-cl and MinGW-w64 all viable options. That matters for theOpenMP item below.
Upstream fixes that have landed since this survey
Two commits merged upstream on 2026-08-05 take work off the list above.
openmc-dev#4048, alternative tokens in
mesh.h(163fdf7)The survey bullet claiming no alternative tokens outside
src/external/waswrong.
include/openmc/mesh.hcarried twoandoperators, one in each of thetwo
sanitize_angular_indexhelpers. MSVC acceptsand/or/notonly under/permissive-, which CMake does not pass by default, so both sites were a harderror C2065 in a header included widely across the tree. They are now
&&.Re-grepped
include/openmcandsrcafter the merge, excludingsrc/external/: no alternative operator tokens remain outside comments andstring literals. The bullet is now accurate rather than aspirational.
Nothing stops this regressing, though.
format-check.ymlonly runsclang-format, which does not rewrite alternative tokens, so a fresh
andcanland at any time and will surface only on the next Windows CI run. A grep guard
in that workflow would keep the property from decaying.
openmc-dev#4047, invalidated iterators in the
Regionconstructor (69de6ba)src/cell.cpphad two iterator-invalidation bugs inRegion::Region. TheDeMorgan complement-removal loop kept using
itafterexpression_.erase(it),and the simple-cell token cleanup did
erase(it); it--, which decrements pastbegin()when the first element matches.expression_is avector<int32_t>,so both are undefined behaviour. They are now fixed, using erase's return value
and
remove_ifrespectively.This one bears on checklist item 9 rather than on getting a build green. Cell
region parsing runs for essentially every model, so it sits on the hot path of
the whole Python test suite, and it is exactly the shape of bug that behaves
differently per standard library. MSVC's STL ships checked iterators, and a
non-release Windows build (
_ITERATOR_DEBUG_LEVELdefaults to 2 under/MDd)turns both of these into an assertion abort rather than the silent misbehaviour
a libstdc++ release build happens to get away with. Release wheels build with
_ITERATOR_DEBUG_LEVEL0, so this would not necessarily have shown up in thewheel itself, but it was a good candidate for a confusing Windows-only failure
during test triage.
Probe results: the vanilla Windows build now works
The probe has run properly and the survey below is wrong in three places. Read
this section first.
A vanilla, no-DAGMC, serial Windows build is complete. The
openmp-offlegproduces both artifacts and exports everything
openmc.libneeds:Correction 1: it is 24 in_dll globals, not five
Item 1 below and the old checklist both say five. The real number is 24.
Fourteen of them are behind the
_DLLGlobaldescriptor inopenmc/lib/core.pyrather than in a literalin_dllcall, which is why agrep for
in_dllmissed them:verbosity,event_based,n_particles,gen_per_batch,cmfd_run,entropy_on,n_inactive,max_lost_particles,need_depletion_rx,output_summary,restart_run,run_CE,weight_windows_onandrel_max_lost_particles. The probe now checks all 24and fails on any unexpected absence.
Worth noting the old probe reported its failures and then exited 0, which is why
it looked green on 2026-08-01 while four symbols were missing.
Correction 2: a
.deffile is not needed, the discriminator isconstThe "watch item" in item 1 guessed that data exports might need a hand-written
.deffile. They do not.bindexplibemits mutable data but not read-onlydata, and
nmon a Linux build shows the split exactly: the four flags thatvanished from the Windows export table are the only four in section
R, whileevery symbol that survived is in
BorD.So the fix is an
OPENMC_APImacro (include/openmc/export.h) that resolves todllexportwhile building the library,dllimportfor anything linking it, andnothing on POSIX or in a static MSVC build. That also fixed the four unresolved
externals that stopped
openmc.exelinking, which were the same problem seenfrom the other side:
settings::run_mode,settings::solver_type,mpi::masterandopenmc_err_msg.Correction 3: OpenMP was never the blocker
Item 4 below treats OpenMP as the main source of friction and expects plain
/openmpto reject the 9collapsesites. Both legs of the 2026-08-01 probefailed identically, including
openmp-off, because the real blocker was twoandtokens inmesh.h(openmc-dev#4048). Every other error in that logwas cascade fallout from a header that failed to parse.
With that fixed,
/openmp:llvmacceptscollapseandatomic captureandrejects only four things:
seq_cstonatomic,shared_array.h:75and:80plot.cpp:1691. The pragma usespixels()[0]and
pixels()[1]as bounds whilewidthandheightlocals already exist twolines above, so this one is nearly free
C3028),flat_source_domain.cpp:1078andrandom_ray_simulation.cpp:432, which GCC and Clang accept as an extensionSo OpenMP is a four-site source fix rather than a toolchain question. Only the
two
seq_cstsites need real thought, since dropping the clause weakensordering on a lock-free append. That leg is marked
experimentalin the probematrix so its known failure does not show up as a red check.
A pre-existing bug found while inventorying the symbols
settings::run_modeandsettings::rel_max_lost_particlesare declared innamespace openmc::settingswithoutextern "C", so they are name-mangled,while
openmc/liblooks them up unmangled. Both therefore raise on everyplatform, not just Windows:
rel_max_lost_particleshas been broken since it was introduced: d763806renamed it on both the C++ and Python sides to make the names match, but with no
extern "C"the symbol never matched under either spelling. Neither attributeis covered by a test, which is how it survived. This is not a Windows problem and
is fixed separately.
Remaining work
1. Build a DLL instead of a static library (blocker)
CMakeLists.txtcurrently does:openmc/lib/__init__.pycallsCDLL(), which can never load a static library,so no importable
openmc.libis possible as things stand. There are zero__declspec(dllexport)annotations in the tree, but annotating the 204openmc_*functions in
include/openmc/capi.hby hand is not necessary. The comment'spremise is exactly what
WINDOWS_EXPORT_ALL_SYMBOLSexists to avoid:This touches only the MSVC path, so Linux and macOS output is unchanged.
The existing
if(NOT MSVC)guard onOUTPUT_NAMEcan stay as is: the targetkeeps the name
libopenmc, producinglibopenmc.dll, which the Python globalready matches.
Watch item: the functions are the easy part.
openmc/lib/reads fiveglobals via ctypes
in_dll:DAGMC_ENABLED,UWUW_ENABLED,LIBMESH_ENABLED,STRICT_FP_ENABLEDandn_coord_levels. All five are declaredextern "C"(in
dagmc.h,mesh.h,output.handgeometry.h), so they are unmangled,and CMake's bindexplib should emit them with the
DATAkeyword in the generated.def. Data exports are the documented weak spot ofWINDOWS_EXPORT_ALL_SYMBOLSthough, so this is the most likely place to need a hand-written
.deffallback.Worth testing early with a one-line
import openmc.lib.2. Add
binto the core library search pathopenmc/__init__.pysearches onlylibandlib64:Windows CMake installs DLLs to
bin/(the RUNTIME destination), so this needs"bin"adding to the list. It is a no-op on POSIX, where nothing matcheslibopenmc*inbin/.Only the library file lookup needs this. The
lib_pathline immediately below itfeeds linking, and the Windows import library (
.lib) is an ARCHIVE artifact thatstill lands in
lib, solib_pathis deliberately left alone: addingbintherewould change
openmc.lib_pathon Linux for no benefit.3. A scratch Windows CI job
The real bottleneck is the feedback loop, since Windows cannot be reproduced
locally. A
continue-on-error: truejob onwindows-latestthat installs HDF5via vcpkg and runs
cmake --buildtouches no existing files and answers theOpenMP question below immediately. Highest value per unit of effort of anything
in this list, and worth doing first alongside item 1.
4. OpenMP version, the main source of friction
The code uses OpenMP features newer than MSVC's default
/openmp, which isfrozen at OpenMP 2.0:
collapse(OpenMP 3.0), 9 sitesatomic capture(3.1)atomic write seq_cstandatomic capture seq_cst(5.0)MSVC's default
/openmprejects all three. Options, in rough order ofpreference:
/openmp:llvm, which is the smallest change if it covers the constructs used-DOPENMC_USE_OPENMP=OFFfor a serial-only first wheel, as a fallback to getsomething shipping while the above is sorted out
Because the binding is ctypes, options 2 and 3 do not create an ABI problem with
CPython. Item 3 above should tell us which of these is needed.
5. Packaging glue
[tool.cibuildwheel.windows]section topyproject.toml.auditwheelfordelvewheelinrepair-wheel-command. delvewheel alsoinjects the
os.add_dll_directorycalls that Python 3.8+ needs on Windows.openmc.exelands somewhereopenmc/executor.pycan find, since itshells out to
openmcvia subprocess.get_extra_libraries()needs no change: its non-darwin branch alreadyresolves to
<site-packages>/openmc.libs, which is delvewheel's default outputdirectory as well as auditwheel's.
This is best left until items 1 and 3 confirm the DLL builds and exports the five
in_dllglobals.6. Fix the generated console entry point
cmake/GenerateScript.cmakewrites theopenmcconsole script that ends up inthe wheel's scripts directory. It is POSIX-only in three separate ways, so as
things stand
pip install openmcon Windows would place an unrunnable file onPATH pointing at a target that does not exist:
#!/usr/bin/env python3shebang. Windows does not honourshebangs, and the installed file is named
openmcwith no extension, sonothing can execute it.
${CMAKE_INSTALL_BINDIR}and the barescript name, which misses the
.exesuffix the installed executable actuallyhas on Windows.
os.execv. That exists on Windows but does not replace theprocess the way it does on POSIX: the parent returns immediately, which breaks
exit codes and console handling for anything that shells out to
openmc.Two candidate fixes, neither large:
openmc.exedirectly intoSKBUILD_SCRIPTS_DIRand skipthe wrapper entirely. pip already puts that directory on PATH.
[project.scripts]console entrypoint, which gets a proper
.exelauncher generated for it on Windows.This is closely related to the
openmc/executor.pychecklist item below, sincethat module shells out to
openmcby name through subprocess.Risks and unknowns
treat the effort estimates as provisional.
lower bar than "results match", and latent path-handling or regression
tolerance issues will likely only surface once the build works.
two weeks to something shippable. The long pole is iterating CI on a platform
that cannot be reproduced locally, not the C++ itself.
Checklist
windows-latestCI job, allowed to fail, to establish a feedback loop (033fa82)CMakeLists.txt: SHARED plusWINDOWS_EXPORT_ALL_SYMBOLSon the MSVC path (033fa82)OPENMC_APIrather than a.deffile (windows: export the data symbols that cross the library boundary #129)openmc.exelinks. The 4 unresolved externals were the same export gap (windows: export the data symbols that cross the library boundary #129)openmc/__init__.py: addbinto the core library search path (033fa82). Written but never exercised until the pip install probe jobrun_modeandrel_max_lost_particlesC linkage, a cross-platform bug (Give run_mode and rel_max_lost_particles C linkage #130, also carried on windows: export the data symbols that cross the library boundary #129)format-check.ymlso Use && instead of the alternative token 'and' in mesh.h openmc-dev/openmc#4048 does not regress (Add CI guard against C++ alternative operator tokens #128)binsearch path (windows: export the data symbols that cross the library boundary #129)[tool.cibuildwheel.windows]plus delvewheel repair command. Note[tool.cibuildwheel] buildis an allowlist ofcp3XX-manylinux_x86_64, socp3XX-win_amd64has to be added there too or nothing is builtcmake/GenerateScript.cmake(shebang, missing.exe,os.execv)seq_cstones need care. Not required for a serial first wheelDropped from this list: "confirm
openmc.exeis discoverable byopenmc/executor.py" needs no code change.executor.pycallssubprocess.Popen(['openmc', ...])with noshell=True, and WindowsCreateProcesssearches PATH and appends.exeto an extensionless name. Itonly depends on the console entry point item putting an
openmc.exeon PATH, soit is not separate work.