Skip to content

Modernize build#407

Closed
efredriksson wants to merge 16 commits into
modelon-community:masterfrom
efredriksson:dev-ef-modernize-build
Closed

Modernize build#407
efredriksson wants to merge 16 commits into
modelon-community:masterfrom
efredriksson:dev-ef-modernize-build

Conversation

@efredriksson
Copy link
Copy Markdown

No description provided.

efredriksson and others added 16 commits May 17, 2026 11:57
First step of the meson-python migration. Adds pyproject.toml with
meson-python as build-backend, plus a minimal meson.build that installs
the pure-Python tree (src/pyfmi -> pyfmi, src/common -> pyfmi/common).

Cython extensions are not yet declared in meson; the legacy setup.py
continues to build them when invoked directly. This is the C1
checkpoint per the modernization plan -- pip-install via mesonpy at
this commit yields a wheel without compiled extensions on purpose.

Build requires include assimulo-testing>=3.9.0b1 so build isolation
resolves Assimulo's .pxd files in later commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Flattens the source layout so the entire installable package lives
under src/pyfmi/. Removes the pyfmi.common package_dir mapping from
setup.py and the second install_subdir call from meson.build -- a
single install_subdir('src/pyfmi') now produces the same on-disk
layout as before. Import paths (pyfmi.common.*) are unchanged.

Mirrors the simplification done in the Assimulo modernization fork
(git mv src assimulo).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wires the 15 Cython extension modules into meson.build:

- 14 plain modules (fmi_base, fmi, fmi1, fmi2, fmi3, fmi_util,
  fmi_extended, fmi_coupled, util, test_util, plus the four
  assimulo_interface variants in src/pyfmi/simulation/).
- master.pyx with a WITH_OPENMP cython compile-time env and optional
  -fopenmp link/compile args, driven by the new -Dopenmp meson option.

FMIL is located via the new -Dfmil_prefix meson option, falling back to
the FMIL_HOME environment variable for parity with setup.py. The lib
name is configurable via -Dfmil_name (default fmilib_shared) for the
static-link case.

NumPy include is resolved at configure time via `numpy.get_include()`,
mirroring Assimulo's pattern. Likewise the assimulo package parent dir
is added to Cython's include path so `cimport assimulo.problem`
resolves at build time (assimulo-testing is in build-system.requires
so it is present during build isolation).

NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION is set globally via
common_c_args, replacing the per-.pyx `# distutils:` headers
functionally (the per-file headers remain harmless since meson
ignores them).

install_rpath/build_rpath point at the FMIL libdir for non-wheel Linux
installs. Wheel-time bundling (with $ORIGIN rpath) lands in C4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds '$ORIGIN' to install_rpath alongside the configured FMIL libdir.
Source installs continue to resolve libfmilib_shared.so via the libdir
entry. Wheel installs have FMIL repaired into the package by
auditwheel (Linux) and delvewheel (Windows); '$ORIGIN' lets ld.so find
the bundled copy next to the extension. Setup.py's manual shutil.copy2
of FMIL into src/pyfmi/ is therefore no longer needed -- the wheel
repair tools handle bundling on both platforms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Drops the svnversion+version.txt write path from setup.py and the
file-read fallback in pyfmi/__init__.py. The version is now resolved
via importlib.metadata.version("PyFMI") (or "pyfmi-testing" during the
beta-name window), matching the source of truth in pyproject.toml.

Also adds an install_data step to meson.build so LICENSE and CHANGELOG
land inside the installed pyfmi/ package -- previously handled by
shutil.copy2 + package_data in setup.py.

__revision__ is kept as a constant "unknown" for backward compat with
any user code that imports it; PyFMI moved off subversion long ago so
the value was already practically constant.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End of Phase 1. The meson-python backend is now the sole build entry
point; pyproject.toml declares everything previously expressed in
setup.cfg (dependencies, python_requires) and the Cython extension
graph lives entirely in meson.build.

MANIFEST.in is removed too -- meson-python's sdist builder uses git
ls-files, making the manifest redundant.

Build options previously surfaced as setup.py CLI flags are now meson
options (-Dfmil_prefix, -Dfmil_name, -Dopenmp) or built-in meson
features (-Dbuildtype=debug for the old --debug flag). The more
obscure flags (--force-32bit, --copy-libgcc, --no-msvcr,
--extra-c-flags) are dropped: 32-bit targets are out of scope for the
modernization, MinGW DLL bundling is handled by delvewheel in CI, and
ad-hoc CFLAGS can be passed via meson's standard CFLAGS env or
-Dc_args.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removes the source builds of SuperLU, SUNDIALS, and Assimulo from the
dev image: the assimulo-testing wheel pulled in via pip build
isolation already embeds SuperLU + SUNDIALS statically and dynamically
links libopenblas only. The system therefore needs just libopenblas
and gfortran at runtime.

Drops the pre-install of Cython/numpy/scipy/matplotlib too -- pip
build isolation resolves the build-time numpy/Cython from
pyproject.toml, and the wheel install pulls in runtime numpy / scipy /
matplotlib / assimulo-testing automatically.

Keeps the FMI Library 3.0.4 build into /usr (still a system dep,
detected by meson via -Dfmil_prefix=/usr).

The venv is created without --system-site-packages now that runtime
deps come from pip; this keeps the image self-contained and matches
how CI-built wheels will resolve dependencies on end-user systems.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the old 'python setup.py build_ext install --fmil-home=/usr'
incantation with a pip install that passes the FMIL prefix to meson
via setup-args. Pip resolves build-system.requires from pyproject.toml
in isolation, then invokes meson-python with the meson option set.

Also drops the SETUPTOOLS_JFLAG variable (parallel build threading is
now handled by ninja, which uses all cores by default) and switches
.venv creation away from --system-site-packages (the image no longer
preinstalls numpy/scipy/matplotlib system-wide).

Test target gains an explicit tests/ path so we exercise the suite at
its canonical location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Adds workflow_dispatch trigger so the smoke workflow can be rerun
  manually.
- Adds a concurrency group keyed on workflow+ref so superseded runs
  cancel themselves; PR pushes no longer queue stale builds.
- Renames the workflow + job for clarity; keeps the canonical Py3.11
  Docker smoke as the single linux job.
- Adds a comment pointing readers at wheels.yml + cibuildwheel for the
  multi-Python matrix (cp311/312/313) per the modernization plan.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Targets cp311/312/313 on Linux and Windows; skips 32-bit and musllinux.
Linux uses a pre-built pyfmi-manylinux Docker image (created from
Dockerfile.manylinux in the next commit) that has FMIL 3.0.4 installed
in /usr, so each wheel build can find it via -Dfmil_prefix=/usr.
Windows expects FMIL to be built into C:/deps by the GHA workflow
(tools/wheels/build_dependencies_windows.sh) before cibuildwheel runs.

Wheels are repaired with auditwheel on Linux and the project-local
delvewheel wrapper script (tools/wheels/repair_windows.ps1) on
Windows.

Each built wheel is exercised by 'pytest {project}/tests' so a wheel
that imports but fails at runtime is caught in CI rather than after
publish.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a build-only docker image that compiles FMI Library 3.0.4 into
/usr on top of the quay.io/pypa/manylinux_2_28_x86_64 base. cibuildwheel
references this image via manylinux-x86_64-image = "pyfmi-manylinux"
in pyproject.toml, so each cp311/312/313 wheel build can locate FMIL
through -Dfmil_prefix=/usr (already configured in C11).

tools/wheels/build_dependencies.sh is the single source of truth for
the FMIL build; the manylinux image bakes it in at image-build time.
Mirrors the assimulo-manylinux pattern but drops SuperLU/SUNDIALS,
which the assimulo-testing wheel already embeds statically.

Adds the build-manylinux-image Makefile target so it can be
rebuilt locally with the same incantation CI uses.

Verified locally: 'make build-manylinux-image' produces FMIL 3.0.4 at
/usr/lib64/libfmilib_shared.so, and 'cibuildwheel --only
cp311-manylinux_x86_64' against this image yields a 6.9 MB manylinux
wheel that runs all 935 tests successfully (15 skipped, 0 failed).
The repaired wheel bundles libfmilib_shared into pyfmi.libs/ via
auditwheel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
build_dependencies_windows.sh compiles FMI Library 3.0.4 into C:/deps
using the MSYS2 UCRT64 toolchain (gcc + ninja + cmake). The wheels.yml
workflow (C14) runs this script in an msys2 shell before invoking
cibuildwheel, so the windows wheel build can find FMIL via
-Dfmil_prefix=C:/deps (already configured in pyproject.toml).

repair_windows.ps1 wraps delvewheel to bundle fmilib_shared.dll plus
the MSYS2 UCRT64 runtime DLLs into each wheel; cibuildwheel invokes
it via repair-wheel-command. Mirrors the assimulo-testing
delvewheel wrapper pattern.

Untested locally (no Windows host available); validation lands when
the wheels.yml workflow is dispatched against a windows-2022 runner.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Builds cp311/312/313 wheels on Linux (manylinux_2_28_x86_64 via
pre-built pyfmi-manylinux image) and Windows (MSYS2 UCRT64 toolchain).
Each platform invokes cibuildwheel with the configuration in
pyproject.toml (C11), which runs the test suite against the
installed wheel before signing off.

Triggers:
  - push / pull_request: build wheels on every change to catch
    regressions before merge; no publish.
  - workflow_dispatch with publish=true on a v* tag: download
    artifacts, upload to PyPI via OIDC Trusted Publisher.

PyPI publishing uses the standard pypa/gh-action-pypi-publish action,
configured for the PyFMI project. The beta-rename step
(pyfmi -> pyfmi-testing) lands in C15 as a separate workflow input
so reviewers can opt into it without rewriting this baseline.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a 'test_release' workflow_dispatch input that gates a sed-based
rewrite of the project name from 'PyFMI' to 'pyfmi-testing' in both
pyproject.toml and meson.build. Runs only when publishing
(publish=true && test_release=true), mirroring the assimulo-testing
pattern.

This lets us publish a parallel-installable beta to PyPI under
'pyfmi-testing' while the upstream PyFMI release stream is unaffected.
The sed step is intended to be removed once the modernization branch
is merged into modelon-community/PyFMI and released under the
canonical name.

Verified sed patterns locally against the current pyproject.toml line
13 and meson.build line 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
distutils was removed in Python 3.12 (PEP 632), so the wheel build's
post-install test step failed during collection of test_log.py when
running on cp312/cp313:

    pyfmi/common/log/parser.py:24: ModuleNotFoundError: No module named 'distutils'
        from distutils.util import strtobool

The only call site (parse_value) is guarded by a regex that limits
input to the literals True/true/False/false, so strtobool's broader
truthy/falsy mapping is overkill. Inline a direct string compare
instead.

Verified end-to-end: 'cibuildwheel --only cp312-manylinux_x86_64'
produces a wheel and its post-install pytest run completes with 937
passed / 13 skipped, same as the cp311 Docker smoke build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors the workflow used in Assimulo so contributors get a reproducible
dev venv regardless of when they clone. .venv is rebuilt whenever the
lockfile changes; make compile-deps regenerates the lockfile from
pyproject.toml.
@efredriksson efredriksson force-pushed the dev-ef-modernize-build branch from 2c11b1f to 722591b Compare May 17, 2026 10:08
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