Repository navigation
232 lines (208 loc) · 9.73 KB
/
Copy pathbuild-casadi.yml
File metadata and controls
232 lines (208 loc) · 9.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# Mirrors the Linux core of upstream's .github/workflows/binaries.yml `build_flags`
# (WITH_IPOPT/WITH_MUMPS/WITH_QPOASES + threading/GIL flags, used unmodified for every
# platform upstream ships). Upstream cross-compiles from dockcross images it maintains
# at ghcr.io/jgillis/<target> (no riscv64 target exists there); this workflow instead
# builds natively in the riscv64 manylinux image with the same flags. It also follows
# upstream's own single-wheel-per-release-line approach: casadi builds one abi3 wheel
# (WITH_PYTHON_LIMITED_API) against its oldest supported interpreter and tests it on
# every newer one, rather than a per-interpreter matrix.
name: Build casadi wheels (riscv64)
on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/casadi.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-casadi.yml'
- 'docs/packages/casadi.yaml'
- 'patches/casadi/**'
push:
branches: [main]
paths:
- '.github/workflows/build-casadi.yml'
- 'docs/packages/casadi.yaml'
- 'patches/casadi/**'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64
jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: casadi
version: ${{ inputs.version }}
build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
name: Build casadi ${{ matrix.version }} cp312-abi3-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
# 24h: the 12h timeout was hit mid-suite (still passing tests, not hung) on the
# contended riscv64 runner - see gotcha 307's overload-dispatch deselections for
# why the suite itself runs long here.
timeout-minutes: 1440
env:
CASADI_VERSION: ${{ matrix.version }}
steps:
- name: Checkout casadi/casadi ${{ env.CASADI_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: casadi/casadi
ref: ${{ env.CASADI_VERSION }}
path: casadi
submodules: recursive
persist-credentials: false
- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false
# Only some versions need patching, so a missing directory is not an error.
- name: Patch casadi source
run: |
patches="$PWD/python-wheels/patches/casadi/$CASADI_VERSION"
if [ -d "$patches" ]; then
git -C casadi apply "$patches"/*.patch
fi
- name: Build casadi core and cp312-abi3 wheel
run: |
mkdir -p output
docker run -i --rm \
-v "$PWD/casadi:/casadi" \
-v "$PWD/output:/output" \
-e CASADI_VERSION \
"$MANYLINUX_RISCV64_IMAGE" bash -s <<'CASADI_BUILD_EOF'
#!/usr/bin/env bash
set -euxo pipefail
CASADI_VERSION="${CASADI_VERSION:?must be set, e.g. 3.8.0}"
PY=/opt/python/cp312-cp312/bin/python
# swig ships preinstalled at /usr/local/bin in this image; only these are missing.
# wget: CasADi's metis-external ExternalProject_Add patch step shells out to wget
# (not curl) to fetch a patch file; the image only has curl (gotcha 289).
dnf install -y --setopt=install_weak_deps=False openblas-devel lapack-devel blas-devel wget
"$PY" -m pip install -U --no-cache-dir --extra-index-url https://pypi.riseproject.dev/simple/ \
--only-binary=numpy numpy
cd /casadi
# Unix Makefiles, not Ninja: CasADi's ExternalProject_Add calls (ipopt, mumps,
# metis) don't declare BYPRODUCTS, so Ninja's stricter DAG check fails with
# "missing and no known rule to make it" on their externally-built .so files.
#
# CMAKE_POSITION_INDEPENDENT_CODE=ON: CasADi's own top-level CMakeLists.txt only
# adds -fPIC to CMAKE_C/CXX_FLAGS when CMAKE_SYSTEM_PROCESSOR is x86_64 or aarch64
# (its "-fPIC" section), so on riscv64 its vendored casadi-sundials static library
# builds without -fPIC and fails to link into libcasadi_sundials_common.so /
# libcasadi_rootfinder_kinsol.so ("relocation R_RISCV_JAL ... recompile with
# -fPIC"). Setting this CMake variable forces PIC on every target regardless of
# that arch allowlist (gotcha 294).
cmake -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/tmp/install \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DWITH_SELFCONTAINED=ON -DWITH_BUILD_REQUIRED=ON -DWITH_THREAD=ON -DWITH_THREADSAFE_SYMBOLICS=ON \
-DWITH_PYTHON_GIL_RELEASE=ON \
-DWITH_PYTHON=ON -DPython3_EXECUTABLE="$PY" -DPYTHON_EXECUTABLE="$PY" \
-DWITH_PYTHON_LIMITED_API=ON -DPYTHON_LIMITED_API_TARGET=3.12 \
-DWITH_IPOPT=ON -DWITH_BUILD_IPOPT=ON \
-DWITH_MUMPS=ON -DWITH_BUILD_MUMPS=ON -DWITH_BUILD_METIS=ON \
-DWITH_LAPACK=ON -DWITH_QPOASES=ON \
-H.
cmake --build build --target install -j "$(nproc)"
wheel=$("$PY" misc/create_wheel_local.py "$CASADI_VERSION" 312 linux 64 manylinux_2_39-riscv64 /tmp/install --abi3)
mv "$wheel" /output/
"$PY" -m pip install -q abi3audit
"$PY" -m abi3audit --strict --report "/output/$wheel"
ls -la /output
CASADI_BUILD_EOF
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: casadi-${{ env.CASADI_VERSION }}-cp312-abi3-manylinux_riscv64
path: output/*.whl
if-no-files-found: error
- name: Test casadi wheel
run: |
docker run -i --rm \
-v "$PWD/casadi:/casadi" \
-v "$PWD/output:/output" \
"$MANYLINUX_RISCV64_IMAGE" bash -s <<'CASADI_TEST_EOF'
#!/usr/bin/env bash
set -euxo pipefail
# abi3: same wheel installs on every interpreter its tag claims (upstream's own
# test-python job reuses one build across cp311/cp313/cp314 the same way).
for tag in cp312-cp312 cp313-cp313 cp314-cp314; do
PY="/opt/python/$tag/bin/python"
"$PY" -m pip install --extra-index-url https://pypi.riseproject.dev/simple/ \
--only-binary=numpy,scipy,pandas numpy scipy pandas looseversion
"$PY" -m pip install /output/casadi-*.whl
# Deselect tests that depend on bindings generated by CasADi's own patched
# SWIG fork (jaeandersson/swig@98c1840, built into ghcr.io/casadi/ci-swig by
# upstream's misc/Dockerfile.swig) rather than the stock SWIG this image
# ships: -stubs (.pyi) generation and a %feature("customdoc") codegen path
# that changes overload-dispatch/operator-NotImplemented fallback and merges
# base-class option docs into derived docstrings. Confirmed by diffing the
# fork against swig/swig and reading upstream's own doc/error-message output;
# none of these are riscv64 codegen bugs (gotcha 307). test_callable_powerindex
# and test_structure hit the same overload-dispatch fallback: tools/structure.py's
# applyCallableIndex() calls vertcat(r) first and only retries as vertcat(*r) on
# a caught NotImplementedError; the ci-swig fork raises NotImplementedError on an
# overload-match failure there, stock SWIG raises a plain TypeError that isn't
# caught.
(cd /casadi/test/python && "$PY" - <<'PYEOF'
import sys
import unittest
import alltests
DESELECTED = {
"pyright_stubs.TypingTests.test_stubs_installed",
"typemaps.typemaptests.test_issue4268",
"typemaps.typemaptests.test_OUTPUT",
"nlp.NLPtests.test_simple_bounds_detect",
"sparsity.Sparsitytests.test_get_ccs",
"matrix.Matrixtests.test_mul3_issue_1465",
"mx.MXtests.test_extract",
"misc.Misctests.test_doc",
"tools.Toolstests.test_callable_powerindex",
"tools.Toolstests.test_structure",
}
def prune(suite):
kept = unittest.TestSuite()
for t in suite:
if isinstance(t, unittest.TestSuite):
sub = prune(t)
if sub.countTestCases():
kept.addTest(sub)
elif t.id() not in DESELECTED:
kept.addTest(t)
return kept
result = unittest.TextTestRunner(verbosity=2).run(prune(alltests.build_suite()))
sys.exit(0 if result.wasSuccessful() else 1)
PYEOF
)
done
CASADI_TEST_EOF
publish:
name: Publish casadi ${{ matrix.version }}
needs: [setup, build_wheels]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
secrets:
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
with:
artifact-pattern: casadi-${{ matrix.version }}-*-manylinux_riscv64