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 cmakeFlags="-S native/src -B native/src/build -D CVODE_INSTALL_DIR=${passthru.cvode}"
118 cmakeConfigurePhase
119 cmake --build native/src/build --config Release
120 ''
121 + lib.optionalString (enableRemoting && stdenv.hostPlatform.isLinux) ''
122 # reimplementation of native/build_remoting.py
123 cmakeFlags="-S native/remoting -B remoting/linux64 -D RPCLIB=${rpclib}"
124 cmakeConfigurePhase
125 cmake --build remoting/linux64 --config Release
126 ''
127 # C.f. upstream build-wheel CI job
128 + ''
129 python native/copy_sources.py
130
131 # reimplementation of native/compile_resources.py
132 pushd src/
133 python -c "from fmpy.gui import compile_resources; compile_resources()"
134 popd
135 '';
136
137 pythonImportsCheck = [
138 "fmpy"
139 "fmpy.cross_check"
140 "fmpy.cswrapper"
141 "fmpy.examples"
142 "fmpy.fmucontainer"
143 "fmpy.logging"
144 "fmpy.gui"
145 "fmpy.gui.generated"
146 "fmpy.ssp"
147 "fmpy.sundials"
148 ];
149
150 nativeInstallCheckInputs = [
151 versionCheckHook
152 ];
153
154 passthru = {
155 # From sundials, build only the CVODE solver. C.f.
156 # src/native/build_cvode.py
157 cvode =
158 (sundials.overrideAttrs (prev: {
159 # hash copied from native/build_cvode.py
160 version = "5.3.0";
161 src = fetchurl {
162 url = "https://github.com/LLNL/sundials/releases/download/v5.3.0/sundials-5.3.0.tar.gz";
163 sha256 = "88dff7e11a366853d8afd5de05bf197a8129a804d9d4461fb64297f1ef89bca7";
164 };
165
166 cmakeFlags =
167 prev.cmakeFlags
168 ++ lib.mapAttrsToList (option: enable: lib.cmakeBool option enable) {
169 # only build the CVODE solver
170 BUILD_CVODE = true;
171
172 BUILD_CVODES = false;
173 BUILD_ARKODE = false;
174 BUILD_IDA = false;
175 BUILD_IDAS = false;
176 BUILD_KINSOL = false;
177 };
178
179 # FMPy searches for sundials without the "lib"-prefix; strip it
180 # and symlink the so-files into existence.
181 postFixup = ''
182 pushd $out/lib
183 for so in *.so; do
184 ln --verbose --symbolic $so ''${so#lib}
185 done
186 popd
187 '';
188 })).override
189 {
190 lapackSupport = false;
191 lapack.isILP64 = stdenv.hostPlatform.is64bit;
192 blas = lapack;
193 kluSupport = false;
194 };
195
196 # Simulate reference FMUs from
197 # <https://github.com/modelica/Reference-FMUs>.
198 #
199 # Just check that the execution passes; don't verify any numerical
200 # results, but save them in case of future or manual check use.
201 tests.simulate-reference-fmus = runCommand "${pname}-simulate-reference-fmus" { } ''
202 mkdir $out
203 # NB(find ! -name): Clocks.fmu is marked TODO upstream and is of a
204 # FMI type that FMPy doesn't support currently (ModelExchange)
205 for fmu in $(find ${fmi-reference-fmus}/*.fmu ! -name "Clocks.fmu"); do
206 name=$(basename $fmu)
207 echo "--- START $name ---"
208 ${fmpy}/bin/fmpy simulate $fmu \
209 --fmi-logging \
210 --output-file $out/$name.csv \
211 | tee $out/$name.out
212 done
213 '';
214 };
215
216 meta = {
217 # A logging.dylib is built but is not packaged correctly so as to
218 # be found. C.f.
219 # <https://logs.ofborg.org/?key=nixos/nixpkgs.397658&attempt_id=9d7cb742-db51-4d9e-99ca-49425d87d14d>
220 broken = stdenv.hostPlatform.isDarwin;
221 description = "Simulate Functional Mockup Units (FMUs) in Python";
222 homepage = "https://github.com/CATIA-Systems/FMPy";
223 license = lib.licenses.bsd2;
224 maintainers = with lib.maintainers; [ tmplt ];
225 # Supported platforms are not exhaustively and explicitly stated,
226 # but inferred from the artifacts that are vendored in upstream CI
227 # builds. C.f.
228 # <https://github.com/CATIA-Systems/FMPy/blob/v0.3.23/pyproject.toml?plain=1#L71-L112>
229 platforms = lib.platforms.x86_64 ++ [ "i686-windows" ];
230 };
231}