feat: establish reproducible FreeCAD web compatibility baseline
This commit is contained in:
@@ -1,23 +1,43 @@
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const localOracle = resolve(root, '.cache/freecad/install-native/bin/FreeCADCmd')
|
||||
const candidates = process.env.FREECAD_CMD ? [process.env.FREECAD_CMD] : [localOracle, 'FreeCADCmd', 'freecadcmd']
|
||||
const profile = process.env.FREECAD_ORACLE_PROFILE || 'headless'
|
||||
if (!['headless', 'desktop'].includes(profile)) throw new Error(`Unknown FreeCAD oracle profile: ${profile}`)
|
||||
const localOracle = resolve(root, profile === 'desktop' ? '.cache/freecad/install-desktop/bin/FreeCAD' : '.cache/freecad/install-native/bin/FreeCADCmd')
|
||||
const fallbackOracle = profile === 'desktop' ? ['FreeCAD', 'freecad'] : ['FreeCADCmd', 'freecadcmd']
|
||||
const candidates = process.env.FREECAD_CMD ? [process.env.FREECAD_CMD] : [localOracle, ...fallbackOracle]
|
||||
let command = null
|
||||
for (const candidate of candidates) {
|
||||
const probe = spawnSync(candidate, ['--version'], { encoding: 'utf8', timeout: 15000 })
|
||||
const probe = spawnSync(candidate, profile === 'desktop' ? ['--console', '--version'] : ['--version'], { encoding: 'utf8', timeout: 15000 })
|
||||
if (!probe.error || probe.error.code !== 'ENOENT') { command = candidate; break }
|
||||
}
|
||||
if (!command) throw new Error('FreeCADCmd 1.1.1 is unavailable. Set FREECAD_CMD to the locked oracle executable.')
|
||||
|
||||
const execution = spawnSync(command, [resolve(root, 'scripts/freecad-reference-probe.py')], {
|
||||
// Desktop mode must stay in the normal GUI run mode so the command-line Python
|
||||
// file is processed by the Qt application; --console switches to a blocking
|
||||
// stdin loop and prevents a deterministic probe exit.
|
||||
const sysroot = resolve(root, '.cache/freecad/sysroot')
|
||||
const commandArgs = profile === 'desktop'
|
||||
? ['--python-path', resolve(sysroot, 'usr/lib/python3/dist-packages'), resolve(root, 'scripts/freecad-reference-probe.py')]
|
||||
: [resolve(root, 'scripts/freecad-reference-probe.py')]
|
||||
const runtimeEnv = profile === 'desktop'
|
||||
? {
|
||||
...process.env,
|
||||
PYTHONPATH: `${resolve(sysroot, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
|
||||
LD_LIBRARY_PATH: `${resolve(sysroot, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
|
||||
MATPLOTLIBRC: resolve(sysroot, 'usr/share/matplotlib/mpl-data/matplotlibrc'),
|
||||
MPLBACKEND: 'Agg',
|
||||
}
|
||||
: process.env
|
||||
const execution = spawnSync(command, commandArgs, {
|
||||
cwd: root,
|
||||
encoding: 'utf8',
|
||||
timeout: 120000,
|
||||
maxBuffer: 20 * 1024 * 1024,
|
||||
env: { ...process.env },
|
||||
env: runtimeEnv,
|
||||
})
|
||||
const output = `${execution.stdout || ''}\n${execution.stderr || ''}`
|
||||
const resultLine = output.split(/\r?\n/).find((line) => line.startsWith('FREECAD_REFERENCE_RESULT='))
|
||||
@@ -26,4 +46,12 @@ if (execution.error || execution.status !== 0 || !resultLine) {
|
||||
}
|
||||
const result = JSON.parse(resultLine.slice('FREECAD_REFERENCE_RESULT='.length))
|
||||
if (result.freecadVersion !== '1.1.1') throw new Error(`Expected FreeCAD 1.1.1, received ${result.freecadVersion}`)
|
||||
const moduleStatuses = new Set(['not-built', 'compiled-importable', 'compiled-import-failure', 'gui-only-unprobeable'])
|
||||
if (!Array.isArray(result.modules) || result.modules.length !== 34 || result.moduleCount !== 34) throw new Error('Reference probe must report the 34 configured FreeCAD modules.')
|
||||
for (const module of result.modules) {
|
||||
if (typeof module.name !== 'string' || typeof module.compiled !== 'boolean' || typeof module.importable !== 'boolean' || typeof module.guiRequired !== 'boolean' || typeof module.guiAvailable !== 'boolean' || !moduleStatuses.has(module.runtimeStatus)) {
|
||||
throw new Error(`Reference probe returned an invalid module status for ${module.name || '<unknown>'}.`)
|
||||
}
|
||||
}
|
||||
await writeFile(resolve(root, `.cache/freecad/reference-${profile}.json`), `${JSON.stringify(result, null, 2)}\n`)
|
||||
console.log(JSON.stringify({ command, result }, null, 2))
|
||||
|
||||
Reference in New Issue
Block a user