11// SPDX-License-Identifier: AGPL-3.0-or-later
22
3+ import { initBltWasm } from "@modelscript/compiler" ;
34import { ArenaSimulator } from "@modelscript/compiler/simulator" ;
45import { Context } from "@modelscript/core" ;
56import {
@@ -9,11 +10,9 @@ import {
910 FMI2_TYPES_PLATFORM_H ,
1011 buildFmuArchive ,
1112 compileToWasm ,
12- createZip ,
1313 generateFmu ,
1414 generateFmuCSources ,
1515 generateFmuWasmSource ,
16- serializeArenaToJson ,
1716} from "@modelscript/fmi" ;
1817import Modelica from "@modelscript/modelica/parser" ;
1918import { execSync } from "node:child_process" ;
@@ -146,6 +145,8 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
146145 return ;
147146 }
148147
148+ await initBltWasm ( ) ;
149+
149150 // Prepare simulator to get state variable info
150151 const simulator = new ArenaSimulator ( arena ) ;
151152 simulator . prepare ( ) ;
@@ -200,6 +201,8 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
200201 }
201202
202203 // ── Full FMU archive mode ──
204+ const fmiVersionStr =
205+ String ( args [ "fmi-version" ] ) === "3" ? "3" : String ( args [ "fmi-version" ] ) === "2" ? "2" : "both" ;
203206 const archiveOptions : FmuArchiveOptions = {
204207 modelIdentifier,
205208 description : args . description ,
@@ -210,7 +213,7 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
210213 fmuType,
211214 includeSources : args . source !== false ,
212215 includeModelJson : true ,
213- fmiVersion : args [ "fmi-version" ] as "2" | "3" | "both" ,
216+ fmiVersion : fmiVersionStr ,
214217 } ;
215218
216219 profiler . start ( "codegen" ) ;
@@ -251,29 +254,41 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
251254 fs . mkdirSync ( srcDir , { recursive : true } ) ;
252255 fs . writeFileSync ( path . join ( srcDir , `${ modelIdentifier } _model.h` ) , sources . modelH ) ;
253256 fs . writeFileSync ( path . join ( srcDir , `${ modelIdentifier } _model.c` ) , sources . modelC ) ;
254- fs . writeFileSync ( path . join ( srcDir , "fmi2Functions.c" ) , sources . fmi2FunctionsC ) ;
257+ const fmi2c = path . join ( srcDir , "fmi2Functions.c" ) ;
258+ const fmi3c = path . join ( srcDir , "fmi3Functions.c" ) ;
259+ if ( archiveOptions . fmiVersion === "2" || archiveOptions . fmiVersion === "both" ) {
260+ fs . writeFileSync ( fmi2c , sources . fmi2FunctionsC ) ;
261+ }
262+ if ( archiveOptions . fmiVersion === "3" || archiveOptions . fmiVersion === "both" ) {
263+ fs . writeFileSync ( fmi3c , sources . fmi3FunctionsC ) ;
264+ }
255265
256266 // Write FMI headers from archive
257267 const encoder = new TextEncoder ( ) ;
258268 for ( const [ name , content ] of Object . entries ( FMI_HEADERS ) ) {
259269 fs . writeFileSync ( path . join ( srcDir , name ) , encoder . encode ( content ) ) ;
260270 }
261271
262- // Detect platform
263- const arch = os . arch ( ) === "x64" ? "64" : "32" ;
264- const plat =
272+ const arch2 = os . arch ( ) === "x64" ? "64" : "32" ;
273+ const plat2 =
265274 process . platform === "win32"
266- ? `win${ arch } `
275+ ? `win${ arch2 } `
267276 : process . platform === "darwin"
268- ? `darwin${ arch } `
269- : `linux${ arch } ` ;
277+ ? `darwin${ arch2 } `
278+ : `linux${ arch2 } ` ;
279+ const arch3 = os . arch ( ) === "x64" ? "x86_64" : os . arch ( ) === "arm64" ? "aarch64" : "x86" ;
280+ const plat3 =
281+ process . platform === "win32"
282+ ? `${ arch3 } -windows`
283+ : process . platform === "darwin"
284+ ? `${ arch3 } -darwin`
285+ : `${ arch3 } -linux` ;
270286 const ext = process . platform === "win32" ? ".dll" : process . platform === "darwin" ? ".dylib" : ".so" ;
271287
272288 // Compile
273289 const cc = process . env . CC ?? "gcc" ;
274- const binDir = path . join ( tmpDir , "binaries" , plat ) ;
275- fs . mkdirSync ( binDir , { recursive : true } ) ;
276- const sharedLib = path . join ( binDir , `${ modelIdentifier } ${ ext } ` ) ;
290+ // Just build to temp dir first
291+ const sharedLib = path . join ( tmpDir , `${ modelIdentifier } ${ ext } ` ) ;
277292
278293 const ccCmd = [
279294 cc ,
@@ -284,16 +299,20 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
284299 "-Wextra" ,
285300 `-I${ srcDir } ` ,
286301 path . join ( srcDir , `${ modelIdentifier } _model.c` ) ,
287- path . join ( srcDir , "fmi2Functions.c" ) ,
302+ archiveOptions . fmiVersion === "2" || archiveOptions . fmiVersion === "both" ? fmi2c : "" ,
303+ archiveOptions . fmiVersion === "3" || archiveOptions . fmiVersion === "both" ? fmi3c : "" ,
288304 "-o" ,
289305 sharedLib ,
290306 "-lm" ,
291- ] . join ( " " ) ;
307+ "-pthread" ,
308+ ]
309+ . filter ( Boolean )
310+ . join ( " " ) ;
292311
293312 console . log ( `Compiling with: ${ ccCmd } ` ) ;
294313 try {
295314 execSync ( ccCmd , { stdio : "pipe" , timeout : 60000 } ) ;
296- console . log ( ` Compiled: binaries/ ${ plat } / ${ modelIdentifier } ${ ext } ` ) ;
315+ console . log ( ` Compiled native binary ` ) ;
297316 } catch ( compileErr ) {
298317 const msg =
299318 compileErr instanceof Error && "stderr" in compileErr
@@ -306,50 +325,27 @@ export const Fmu: CommandModule<{}, FmuArgs> = {
306325 return ;
307326 }
308327
309- // Rebuild archive with the compiled binary
310- const finalEntries = new Map < string , Uint8Array > ( ) ;
311-
312- // Actually, it's easier to use the resourceFiles map for the native binary
313- if ( ! archiveOptions . resourceFiles ) archiveOptions . resourceFiles = new Map ( ) ;
314- // FMI standard expects native binaries in binaries/PLATFORM/
315- // So we hack it in via resourceFiles by using a relative path that breaks out of resources/
316- // Wait, fmu-archive adds "resources/" prefix. So we can't use resourceFiles.
317-
318- // Let's just re-implement the entry copying like in export-fmu.ts
319- finalEntries . set ( "modelDescription.xml" , encoder . encode ( result . fmuResult . modelDescriptionXml ) ) ;
320-
321- if ( archiveOptions . fmiVersion === "3" || archiveOptions . fmiVersion === "both" ) {
322- // Add terminalsAndIcons if present
323- // We can't easily get fmi3Result without calling generateFmi3.
324- // A cleaner way is to just call buildFmuArchive again but modify it to accept native binaries.
325- }
326-
327- // Since we're replacing export-fmu, let's copy its logic directly.
328- finalEntries . set ( "modelDescription.xml" , encoder . encode ( result . fmuResult . modelDescriptionXml ) ) ;
329- if ( archiveOptions . includeSources !== false ) {
330- finalEntries . set ( `sources/${ modelIdentifier } _model.h` , encoder . encode ( sources . modelH ) ) ;
331- finalEntries . set ( `sources/${ modelIdentifier } _model.c` , encoder . encode ( sources . modelC ) ) ;
332- finalEntries . set ( "sources/fmi2Functions.c" , encoder . encode ( sources . fmi2FunctionsC ) ) ;
333- finalEntries . set ( "sources/CMakeLists.txt" , encoder . encode ( sources . cmakeLists ) ) ;
334- for ( const [ name , content ] of Object . entries ( FMI_HEADERS ) ) {
335- finalEntries . set ( `sources/${ name } ` , encoder . encode ( content ) ) ;
336- }
328+ const binData = fs . readFileSync ( sharedLib ) ;
329+ archiveOptions . nativeBinaries = [ ] ;
330+ if ( archiveOptions . fmiVersion === "2" || archiveOptions . fmiVersion === "both" ) {
331+ archiveOptions . nativeBinaries . push ( { platform : plat2 , ext, binary : binData } ) ;
337332 }
338- if ( archiveOptions . includeModelJson !== false ) {
339- finalEntries . set (
340- "resources/model.json" ,
341- encoder . encode ( JSON . stringify ( serializeArenaToJson ( arena ) , null , 2 ) ) ,
342- ) ;
333+ if ( archiveOptions . fmiVersion === "3" || archiveOptions . fmiVersion === "both" ) {
334+ archiveOptions . nativeBinaries . push ( { platform : plat3 , ext, binary : binData } ) ;
343335 }
344336
345- if ( archiveOptions . wasmBinary && archiveOptions . wasmJsGlue ) {
346- finalEntries . set ( `binaries/wasm32/${ modelIdentifier } .wasm` , archiveOptions . wasmBinary ) ;
347- finalEntries . set ( `binaries/wasm32/${ modelIdentifier } .js` , encoder . encode ( archiveOptions . wasmJsGlue ) ) ;
348- }
337+ console . log ( "FMI VERSION: " , archiveOptions . fmiVersion ) ;
338+ console . log (
339+ "NATIVE BINARIES: " ,
340+ archiveOptions . nativeBinaries . map ( ( b ) => b . platform ) ,
341+ ) ;
349342
350- finalEntries . set ( `binaries/${ plat } /${ modelIdentifier } ${ ext } ` , fs . readFileSync ( sharedLib ) ) ;
343+ // Re-build archive to include native binaries properly
344+ profiler . start ( "codegen" ) ;
345+ result = buildFmuArchive ( arena , archiveOptions , stateVars ) ;
346+ profiler . end ( "codegen" ) ;
351347
352- fs . writeFileSync ( outputPath , createZip ( finalEntries ) ) ;
348+ fs . writeFileSync ( outputPath , result . archive ) ;
353349 } finally {
354350 fs . rmSync ( tmpDir , { recursive : true , force : true } ) ;
355351 profiler . end ( "compilation_c" ) ;
0 commit comments