Files
Web_FreeCAD_Bitbybit/scripts/check-freecad-desktop-oracle.mjs

93 lines
5.8 KiB
JavaScript

import { createHash } from 'node:crypto'
import { access, readFile, stat } from 'node:fs/promises'
import { execFileSync } from 'node:child_process'
import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const readJson = async (relative) => JSON.parse(await readFile(resolve(root, relative), 'utf8'))
const fail = (message) => { throw new Error(`FreeCAD desktop oracle: ${message}`) }
const config = await readJson('config/freecad-desktop-oracle.json')
const toolchain = await readJson('config/freecad-toolchain.json')
const source = resolve(root, '.cache/freecad/FreeCAD')
const build = resolve(root, config.buildDirectory)
const executable = resolve(root, `${config.installDirectory}/bin/FreeCAD`)
const probeReport = resolve(root, '.cache/freecad/reference-desktop.json')
const sourceRevision = execFileSync('git', ['-C', source, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim()
if (sourceRevision !== toolchain.source.commit) fail(`source revision ${sourceRevision} does not match ${toolchain.source.commit}.`)
const cache = await readFile(resolve(build, 'CMakeCache.txt'), 'utf8').catch(() => fail('CMakeCache.txt is missing; configure the desktop profile first.'))
const cacheValue = (name) => {
const line = cache.split(/\r?\n/).find((entry) => entry.startsWith(`${name}:`))
return line?.slice(line.indexOf('=') + 1)
}
const expectedOn = {
AddonManager: 'BUILD_ADDONMGR', Assembly: 'BUILD_ASSEMBLY', BIM: 'BUILD_BIM', CAM: 'BUILD_CAM',
Draft: 'BUILD_DRAFT', Fem: 'BUILD_FEM', Help: 'BUILD_HELP', Idf: 'BUILD_IDF', Import: 'BUILD_IMPORT',
Inspection: 'BUILD_INSPECTION', Material: 'BUILD_MATERIAL', Measure: 'BUILD_MEASURE', Mesh: 'BUILD_MESH',
MeshPart: 'BUILD_MESH_PART', OpenSCAD: 'BUILD_OPENSCAD', Part: 'BUILD_PART', PartDesign: 'BUILD_PART_DESIGN',
Plot: 'BUILD_PLOT', Points: 'BUILD_POINTS', ReverseEngineering: 'BUILD_REVERSEENGINEERING', Robot: 'BUILD_ROBOT',
Show: 'BUILD_SHOW', Sketcher: 'BUILD_SKETCHER', Spreadsheet: 'BUILD_SPREADSHEET', Start: 'BUILD_START',
Surface: 'BUILD_SURFACE', TechDraw: 'BUILD_TECHDRAW', Test: 'BUILD_TEST', Tux: 'BUILD_TUX', Web: 'BUILD_WEB',
}
const expectedOff = {
Cloud: 'BUILD_CLOUD', JtReader: 'BUILD_JTREADER', Sandbox: 'BUILD_SANDBOX',
}
if (cacheValue('BUILD_GUI') !== 'ON') fail('BUILD_GUI is not ON.')
for (const [module, key] of Object.entries(expectedOn)) if (cacheValue(key) !== 'ON') fail(`${module} requires ${key}=ON; got ${cacheValue(key) ?? '<missing>'}.`)
for (const [module, key] of Object.entries(expectedOff)) if (cacheValue(key) !== 'OFF') fail(`${module} requires ${key}=OFF; got ${cacheValue(key) ?? '<missing>'}.`)
await access(executable).catch(() => fail(`installed executable is missing: ${executable}`))
const versionOutput = execFileSync(executable, ['--console', '--version'], {
cwd: root,
encoding: 'utf8',
env: {
...process.env,
PYTHONPATH: `${resolve(root, config.sysrootDirectory, 'usr/lib/python3/dist-packages')}${process.env.PYTHONPATH ? `:${process.env.PYTHONPATH}` : ''}`,
LD_LIBRARY_PATH: `${resolve(root, config.sysrootDirectory, 'usr/lib/x86_64-linux-gnu')}${process.env.LD_LIBRARY_PATH ? `:${process.env.LD_LIBRARY_PATH}` : ''}`,
},
}).trim()
if (!versionOutput.includes('FreeCAD 1.1.1')) fail(`version output does not report FreeCAD 1.1.1: ${versionOutput}`)
const bytes = await readFile(executable)
const artifact = {
path: `${config.installDirectory}/bin/FreeCAD`,
sizeBytes: (await stat(executable)).size,
sha256: createHash('sha256').update(bytes).digest('hex'),
reportedVersion: versionOutput.split(/\r?\n/)[0],
}
if (config.artifact?.sha256 && config.artifact.sha256 !== artifact.sha256) fail(`artifact checksum mismatch: expected ${config.artifact.sha256}, got ${artifact.sha256}.`)
const report = await readJson('.cache/freecad/reference-desktop.json').catch(() => null)
if (!report) fail('34-module desktop probe report is missing; run probe:freecad-reference with FREECAD_ORACLE_PROFILE=desktop.')
if (report.baselineId !== config.baselineId || report.freecadVersion !== '1.1.1' || report.guiUp !== true || report.moduleCount !== 34 || report.modules?.length !== 34) {
fail('desktop probe report is not a GUI-up 34-module FreeCAD 1.1.1 report.')
}
const invalid = report.modules.filter((module) => !['compiled-importable', 'compiled-import-failure', 'gui-only-unprobeable', 'not-built'].includes(module.runtimeStatus))
if (invalid.length) fail(`probe contains invalid module statuses: ${invalid.map((module) => module.name).join(', ')}`)
const expectedObjects = ['Part::Box', 'Part::Cylinder', 'Part::Sphere', 'Part::Cone', 'Part::Feature', 'PartDesign::Feature', 'Sketcher::SketchObject']
if (!Array.isArray(report.objects) || report.objects.length !== expectedObjects.length) fail('desktop probe must include the seven core object fixtures.')
for (const typeId of expectedObjects) {
const object = report.objects.find((candidate) => candidate.runtimeTypeId === typeId && candidate.typeId === typeId)
if (!object?.available || !Array.isArray(object.properties)) fail(`runtime object fixture ${typeId} is missing or unavailable.`)
}
const propertyFlags = report.objects.flatMap((object) => object.properties || []).map((property) => property.status || [])
if (!propertyFlags.some((status) => status.includes('Hidden')) || !propertyFlags.some((status) => status.includes('Output'))) {
fail('runtime property fixtures must include Hidden and Output status flags.')
}
console.log(JSON.stringify({
status: 'freecad-desktop-oracle-pass',
baselineId: config.baselineId,
sourceRevision,
buildDirectory: config.buildDirectory,
installDirectory: config.installDirectory,
artifact,
probe: {
path: '.cache/freecad/reference-desktop.json',
moduleCount: report.moduleCount,
guiUp: report.guiUp,
moduleStatusSummary: report.moduleStatusSummary,
objectFixtureCount: report.objects.length,
},
}, null, 2))