1{
2 lib,
3 stdenv,
4 fetchurl,
5 buildPythonPackage,
6 fetchFromGitHub,
7 pythonOlder,
8 cmake,
9 sundials,
10 lapack,
11 attrs,
12 lark,
13 lxml,
14 rpclib,
15 msgpack,
16 numpy,
17 scipy,
18 pytz,
19 dask,
20 requests,
21 matplotlib,
22 pyqtgraph,
23 notebook,
24 plotly,
25 hatchling,
26 pyside6,
27 jinja2,
28 flask,
29 dash,
30 dash-bootstrap-components,
31 qt6,
32 fmpy,
33 runCommand,
34 enableRemoting ? true,
35 versionCheckHook,
36 fmi-reference-fmus,
37 replaceVars,
38}:
39buildPythonPackage rec {
40 pname = "fmpy";
41 version = "0.3.23";
42 disabled = pythonOlder "3.10";
43 pyproject = true;
44
45 # Bumping version? Make sure to look through the commit history for
46 # bumped native/build_cvode.py pins (see above).
47 src = fetchFromGitHub {
48 owner = "CATIA-Systems";
49 repo = "FMPy";
50 rev = "v${version}";
51 fetchSubmodules = true;
52 hash = "sha256-4jBYOymurGCbjT0WQjIRNsztwOXBYVTGLdc4kNSTOZw=";
53 };
54
55 nativeBuildInputs = [
56 cmake
57 ];
58
59 build-system = [
60 hatchling
61 ];
62
63 dependencies = [
64 pyqtgraph
65 pyside6
66 attrs
67 lark
68 lxml
69 msgpack
70 numpy
71 scipy
72 pytz
73 dask
74 requests
75 matplotlib
76 pyqtgraph
77 notebook
78 plotly
79 rpclib
80 fmpy.passthru.cvode
81 pyside6
82 jinja2
83 flask
84 dash
85 dash-bootstrap-components
86 ];
87
88 dontUseCmakeConfigure = true;
89 dontUseCmakeBuildDir = true;
90
91 patches = [
92 (replaceVars ./0001-gui-override-Qt6-libexec-path.patch {
93 qt6libexec = "${qt6.qtbase}/libexec/";
94 })
95 (replaceVars ./0002-sundials-override-shared-object-paths.patch {
96 inherit (fmpy.passthru) cvode;
97 })
98 # Upstream does not accept pull requests for unknown legal
99 # reasons, see:
100 # <https://github.com/CATIA-Systems/FMPy/blob/v0.3.23/docs/contributing.md?plain=1#L60-L63>.
101 # Thus a fix is vendored here until patched by maintainer. C.f.
102 # <https://github.com/CATIA-Systems/FMPy/pull/762>
103 ./0003-remoting-client-create-lockfile-with-explicit-r-w.patch
104 ];
105
106 # Make forced includes of other systems' artifacts optional in order
107 # to pass build (otherwise vendored upstream from CI)
108 postPatch = ''
109 sed --in-place 's/force-include/source/g' pyproject.toml
110 '';
111
112 # Don't run upstream build scripts as they are too specialized.
113 # cvode is already built, so we only need to build native binaries.
114 # We run these cmake builds and then run the standard
115 # buildPythonPackage phases.
116 preBuild =
117 ''
118 cmakeFlags="-S native/src -B native/src/build -D CVODE_INSTALL_DIR=${passthru.cvode}"
119 cmakeConfigurePhase
120 cmake --build native/src/build --config Release
121 ''
122 + lib.optionalString (enableRemoting && stdenv.isLinux) ''
123 # reimplementation of native/build_remoting.py
124 cmakeFlags="-S native/remoting -B remoting/linux64 -D RPCLIB=${rpclib}"
125 cmakeConfigurePhase
126 cmake --build remoting/linux64 --config Release
127 ''
128 # C.f. upstream build-wheel CI job
129 + ''
130 python native/copy_sources.py
131
132 # reimplementation of native/compile_resources.py
133 pushd src/
134 python -c "from fmpy.gui import compile_resources; compile_resources()"
135 popd
136 '';
137
138 pythonImportsCheck = [
139 "fmpy"
140 "fmpy.cross_check"
141 "fmpy.cswrapper"
142 "fmpy.examples"
143 "fmpy.fmucontainer"
144 "fmpy.logging"
145 "fmpy.gui"
146 "fmpy.gui.generated"
147 "fmpy.ssp"
148 "fmpy.sundials"
149 ];
150
151 nativeInstallCheckInputs = [
152 versionCheckHook
153 ];
154
155 passthru = {
156 # From sundials, build only the CVODE solver. C.f.
157 # src/native/build_cvode.py
158 cvode =
159 (sundials.overrideAttrs (prev: {
160 # hash copied from native/build_cvode.py
161 version = "5.3.0";
162 src = fetchurl {
163 url = "https://github.com/LLNL/sundials/releases/download/v5.3.0/sundials-5.3.0.tar.gz";
164 sha256 = "88dff7e11a366853d8afd5de05bf197a8129a804d9d4461fb64297f1ef89bca7";
165 };
166
167 cmakeFlags =
168 prev.cmakeFlags
169 ++ lib.mapAttrsToList (option: enable: lib.cmakeBool option enable) {
170 # only build the CVODE solver
171 BUILD_CVODE = true;
172
173 BUILD_CVODES = false;
174 BUILD_ARKODE = false;
175 BUILD_IDA = false;
176 BUILD_IDAS = false;
177 BUILD_KINSOL = false;
178 };
179
180 # FMPy searches for sundials without the "lib"-prefix; strip it
181 # and symlink the so-files into existance.
182 postFixup = ''
183 pushd $out/lib
184 for so in *.so; do
185 ln --verbose --symbolic $so ''${so#lib}
186 done
187 popd
188 '';
189 })).override
190 {
191 lapackSupport = false;
192 lapack.isILP64 = stdenv.hostPlatform.is64bit;
193 blas = lapack;
194 kluSupport = false;
195 };
196
197 # Simulate reference FMUs from
198 # <https://github.com/modelica/Reference-FMUs>.
199 #
200 # Just check that the execution passes; don't verify any numerical
201 # results, but save them in case of future or manual check use.
202 tests.simulate-reference-fmus = runCommand "${pname}-simulate-reference-fmus" { } ''
203 mkdir $out
204 # NB(find ! -name): Clocks.fmu is marked TODO upstream and is of a
205 # FMI type that FMPy doesn't support currently (ModelExchange)
206 for fmu in $(find ${fmi-reference-fmus}/*.fmu ! -name "Clocks.fmu"); do
207 name=$(basename $fmu)
208 echo "--- START $name ---"
209 ${fmpy}/bin/fmpy simulate $fmu \
210 --fmi-logging \
211 --output-file $out/$name.csv \
212 | tee $out/$name.out
213 done
214 '';
215 };
216
217 meta = {
218 # A logging.dylib is built but is not packaged correctly so as to
219 # be found. C.f.
220 # <https://logs.ofborg.org/?key=nixos/nixpkgs.397658&attempt_id=9d7cb742-db51-4d9e-99ca-49425d87d14d>
221 broken = stdenv.hostPlatform.isDarwin;
222 description = "Simulate Functional Mockup Units (FMUs) in Python";
223 homepage = "https://github.com/CATIA-Systems/FMPy";
224 license = lib.licenses.bsd2;
225 maintainers = with lib.maintainers; [ tmplt ];
226 # Supported platforms are not exhaustively and explicitly stated,
227 # but inferred from the artifacts that are vendored in upstream CI
228 # builds. C.f.
229 # <https://github.com/CATIA-Systems/FMPy/blob/v0.3.23/pyproject.toml?plain=1#L71-L112>
230 platforms = lib.platforms.x86_64 ++ [ "i686-windows" ];
231 };
232}