Skip to content

Commit 43bc930

Browse files
authored
swmm-toolkit: Add version 0.17.0 (#3043)
* Port swmm-toolkit 0.17.0 to riscv64 PySWMM's SWIG/CFFI wrapper over EPA SWMM (OpenWaterAnalytics fork, MIT-licensed, vendored as a git submodule). scikit-build-core + CMake + SWIG, single abi3 wheel built on the cp39 floor (py-api = "cp39" in pyproject.toml) and retested on cp312/cp313/cp314, mirroring upstream's own build_wheel.yml CIBW_SKIP of free-threaded variants. Two patches: lower the swig build requirement to 4.3.1 (the version this registry already hosts for riscv64; $action has worked since SWIG 2001, so the project's own .i files are unaffected) and bundle the vendored swmm-solver submodule's MIT LICENSE, which upstream's own license-files list omits. * swmm-toolkit: pin swig to <4.4.0, not just >=4.3.1 PyPI started shipping a native riscv64 wheel for swig 4.5.1, so the unbounded floor let pip pick that over our registry's 4.3.1, and SWIG 4.5's removed PyInt_*/PyString_* macros broke the generated wrapper.
1 parent 03cfeaa commit 43bc930

4 files changed

Lines changed: 196 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# SPDX-FileCopyrightText: 2026 The RISE Project
2+
# SPDX-License-Identifier: MIT
3+
---
4+
# Based on the `build_wheels` job of
5+
# https://github.com/pyswmm/swmm-python/blob/v0.17.0/.github/workflows/build_wheel.yml
6+
name: Build swmm-toolkit wheels (riscv64)
7+
8+
on:
9+
workflow_dispatch:
10+
inputs:
11+
version:
12+
description: 'Version glob to (re)build; empty builds every version of docs/packages/swmm-toolkit.yaml not released yet'
13+
required: false
14+
default: ''
15+
pull_request:
16+
branches: [main]
17+
paths:
18+
- '.github/workflows/build-swmm-toolkit.yml'
19+
- 'docs/packages/swmm-toolkit.yaml'
20+
- 'patches/swmm-toolkit/**'
21+
push:
22+
branches: [main]
23+
paths:
24+
- '.github/workflows/build-swmm-toolkit.yml'
25+
- 'docs/packages/swmm-toolkit.yaml'
26+
- 'patches/swmm-toolkit/**'
27+
28+
concurrency:
29+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
30+
cancel-in-progress: true
31+
32+
permissions:
33+
contents: read # to fetch code (actions/checkout)
34+
35+
env:
36+
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
37+
38+
jobs:
39+
setup:
40+
uses: $/.github/workflows/_setup.yml
41+
with:
42+
package: swmm-toolkit
43+
version: ${{ inputs.version }}
44+
45+
build_wheels:
46+
needs: [setup]
47+
if: needs.setup.outputs.versions != '[]'
48+
name: Build swmm-toolkit ${{ matrix.version }} cp39-abi3-manylinux_riscv64
49+
runs-on: ubuntu-24.04-riscv
50+
timeout-minutes: 90
51+
strategy:
52+
fail-fast: false
53+
matrix:
54+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
55+
56+
env:
57+
SWMM_TOOLKIT_VERSION: ${{ matrix.version }}
58+
59+
steps:
60+
- name: Checkout pyswmm/swmm-python v${{ env.SWMM_TOOLKIT_VERSION }}
61+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
62+
with:
63+
repository: pyswmm/swmm-python
64+
ref: v${{ env.SWMM_TOOLKIT_VERSION }}
65+
submodules: recursive
66+
persist-credentials: false
67+
68+
- name: Checkout python-wheels
69+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
70+
with:
71+
path: python-wheels
72+
persist-credentials: false
73+
74+
# swig>=4.4.0 has no riscv64 build anywhere; swmm-toolkit's own .i files
75+
# only need $action, which works on the 4.3.1 we do have (see the patch).
76+
# license-files never names the statically-linked swmm-solver submodule's
77+
# own MIT LICENSE.
78+
- name: Patch swmm-toolkit source
79+
run: git apply python-wheels/patches/swmm-toolkit/${{ env.SWMM_TOOLKIT_VERSION }}/00*.patch
80+
81+
- name: Build wheels
82+
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
83+
with:
84+
package-dir: swmm-toolkit
85+
output-dir: wheelhouse/
86+
env:
87+
CIBW_ARCHS: riscv64
88+
CIBW_BUILD: cp39-manylinux_riscv64 cp312-manylinux_riscv64 cp313-manylinux_riscv64 cp314-manylinux_riscv64
89+
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
90+
CIBW_BEFORE_BUILD_LINUX: rm -f $(which swig) && rm -f $(which swig4.0)
91+
CIBW_ENVIRONMENT: PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
92+
CIBW_BEFORE_TEST: pip install -r {package}/test-requirements.txt
93+
CIBW_TEST_COMMAND: >-
94+
python -c "import importlib.metadata as m; l = sorted(str(p).split('licenses/', 1)[1] for p in m.files('swmm-toolkit') if 'licenses/' in str(p)); assert 'swmm-solver/LICENSE' in l, l"
95+
&& pytest {package}/tests
96+
97+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
98+
with:
99+
name: swmm-toolkit-${{ env.SWMM_TOOLKIT_VERSION }}-cp39-abi3-manylinux_riscv64
100+
path: wheelhouse/*.whl
101+
if-no-files-found: error
102+
103+
publish:
104+
name: Publish swmm-toolkit ${{ matrix.version }}
105+
needs: [setup, build_wheels]
106+
if: needs.setup.outputs.versions != '[]'
107+
strategy:
108+
fail-fast: false
109+
matrix:
110+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
111+
permissions:
112+
contents: write
113+
pull-requests: write
114+
uses: $/.github/workflows/_publish-wheel.yml
115+
secrets:
116+
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
117+
with:
118+
artifact-pattern: swmm-toolkit-${{ matrix.version }}-*-manylinux_riscv64

‎docs/packages/swmm-toolkit.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package-name: swmm-toolkit
2+
source-code: https://github.com/pyswmm/swmm-python
3+
license: CC0-1.0 AND (MIT OR Apache-2.0)
4+
versions:
5+
- version: 0.17.0
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Tue, 13 Oct 2026 10:53:44 +0000
4+
Subject: [PATCH] pin the swig build dependency to 4.3.1 for riscv64
5+
6+
Upstream-Status: Inappropriate [riscv64 build portability]
7+
8+
pyproject.toml pins swig>=4.4.0 only because that is the version upstream
9+
happened to be on when it replaced the removed $function exception
10+
variable with $action (SWIG CHANGES, 2025-06-09: "$action (added in
11+
2001) ... has exactly the same value"). $action has worked since SWIG
12+
2001, so the project's own .i files (already migrated to $action in this
13+
release) build identically against an older SWIG that still recognizes it.
14+
15+
pypi.riseproject.dev hosts swig 4.3.1 for riscv64. An unbounded floor of
16+
>=4.3.1 is not enough: PyPI itself now publishes a native riscv64 wheel
17+
for swig 4.5.1 (swig-4.5.1-py3-none-manylinux_2_31_riscv64.whl), and
18+
since CIBW_ENVIRONMENT only adds our registry via PIP_EXTRA_INDEX_URL
19+
(PyPI stays the default index), pip picks the newest version satisfying
20+
the constraint across both indexes -- 4.5.1, not our 4.3.1. SWIG 4.5
21+
dropped the PyInt_*/PyString_* Python-2 compatibility macros that
22+
outputPYTHON_wrap.c (SWIG-generated, not hand-written) still calls, so
23+
the build fails with "implicit declaration of function 'PyInt_FromLong'".
24+
An explicit upper bound keeps pip on the 4.3.1 we can actually supply.
25+
26+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
27+
---
28+
swmm-toolkit/pyproject.toml | 2 +-
29+
1 file changed, 1 insertion(+), 1 deletion(-)
30+
31+
diff --git a/swmm-toolkit/pyproject.toml b/swmm-toolkit/pyproject.toml
32+
index 564d84a..48c313b 100644
33+
--- a/swmm-toolkit/pyproject.toml
34+
+++ b/swmm-toolkit/pyproject.toml
35+
@@ -1,5 +1,5 @@
36+
[build-system]
37+
-requires = ["swig>=4.4.0", "scikit-build-core>=0.4.3"]
38+
+requires = ["swig>=4.3.1,<4.4.0", "scikit-build-core>=0.4.3"]
39+
build-backend = "scikit_build_core.build"
40+
41+
[project]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
2+
From: Ludovic Henry <git@ludovic.dev>
3+
Date: Tue, 13 Oct 2026 10:53:51 +0000
4+
Subject: [PATCH] bundle the vendored swmm-solver LICENSE in the wheel
5+
6+
Upstream-Status: To upstream [not yet submitted; the same gap exists in every swmm-toolkit wheel on PyPI, so this needs a maintainer discussion rather than a drive-by PR]
7+
8+
The wheel statically links the swmm-solver git submodule
9+
(OpenWaterAnalytics/Stormwater-Management-Model, MIT), but license-files
10+
never lists its LICENSE file, so it never reaches
11+
*.dist-info/licenses/ and the built wheel ships only swmm-toolkit's own
12+
CC0/openmp notices. Add it so the MIT notice travels with the binary that
13+
statically embeds its code.
14+
15+
Signed-off-by: Ludovic Henry <git@ludovic.dev>
16+
---
17+
swmm-toolkit/pyproject.toml | 2 +-
18+
1 file changed, 1 insertion(+), 1 deletion(-)
19+
20+
diff --git a/swmm-toolkit/pyproject.toml b/swmm-toolkit/pyproject.toml
21+
index 48c313b..e93562d 100644
22+
--- a/swmm-toolkit/pyproject.toml
23+
+++ b/swmm-toolkit/pyproject.toml
24+
@@ -8,7 +8,7 @@ version = "0.17.0"
25+
description = "PySWMM SWMM Python Toolkit"
26+
readme = { file = "README.md", content-type = "text/markdown" }
27+
license = "CC0-1.0 AND (MIT OR Apache-2.0)"
28+
-license-files = ["LICEN[CS]E*", "AUTHORS", "extern/openmp.LICENSE"]
29+
+license-files = ["LICEN[CS]E*", "AUTHORS", "extern/openmp.LICENSE", "swmm-solver/LICENSE"]
30+
authors = [{ name = "See AUTHORS" }]
31+
maintainers = [{ email = "bemcdonnell@gmail.com" }]
32+
keywords = ["swmm5", "swmm", "stormwater", "hydraulics", "hydrology"]

0 commit comments

Comments
 (0)