UNPKG

soem-node

Version:

Bindings Node-API pour SOEM (Simple Open EtherCAT Master)

262 lines (229 loc) 9.54 kB
#!/usr/bin/env node const { spawnSync } = require('node:child_process'); const path = require('node:path'); const fs = require('node:fs'); const os = require('node:os'); console.log('[soem-node] Post-install setup...'); // Check and initialize SOEM submodule if needed const soemPath = path.join(__dirname, '..', 'external', 'soem'); const soemCMake = path.join(soemPath, 'CMakeLists.txt'); // If the native addon is already present (packaged), try to load it. // If it loads, skip heavy work. If it fails to load (ABI/arch mismatch), attempt a rebuild. const packagedBinary = path.join(__dirname, '..', 'build', 'Release', 'soem_addon.node'); const skipBuildEnv = process.env.SOEM_SKIP_BUILD === '1' || process.env.SOEM_SKIP_BUILD === 'true'; if (fs.existsSync(packagedBinary)) { let canUseBinary = false; try { // Try to load the binary directly to validate ABI/arch compatibility require(packagedBinary); canUseBinary = true; } catch (e) { console.warn('[soem-node] Existing binary found but failed to load, will attempt rebuild.'); console.warn('[soem-node] Error:', e && e.message); } if (canUseBinary) { console.log('[soem-node] Native addon already present and loadable, skipping postinstall build.'); console.log('[soem-node] Binary path:', packagedBinary); process.exit(0); } else if (skipBuildEnv) { console.warn('[soem-node] Build skipped due to SOEM_SKIP_BUILD env var; the addon may not work until rebuilt.'); process.exit(0); } } // Ensure external directory exists before any git operations fs.mkdirSync(path.join(__dirname, '..', 'external'), { recursive: true }); if (!fs.existsSync(soemCMake)) { console.log('[soem-node] SOEM submodule not found, initializing...'); // Try to initialize submodule (non-destructive). Do not deinit or remove package contents. // We run 'git submodule update' if possible, otherwise we clone into a temporary directory and copy files. const gitRes = spawnSync('git', ['submodule', 'update', '--init', '--recursive', '--force'], { stdio: 'inherit', cwd: path.join(__dirname, '..') }); if (gitRes.status !== 0) { console.log('[soem-node] Git submodule init failed, cloning SOEM to a temporary directory...'); // Create temp dir and clone there to avoid touching package files directly let tmpDir = null; try { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'soem-')); } catch (e) { tmpDir = path.join(os.tmpdir(), 'soem-clone'); try { fs.mkdirSync(tmpDir, { recursive: true }); } catch (__) { /* ignore */ } } const cloneRes = spawnSync('git', [ 'clone', '--depth=1', '--branch=master', 'https://github.com/OpenEtherCATsociety/SOEM.git', tmpDir ], { stdio: 'inherit' }); if (cloneRes.status !== 0) { console.error('[soem-node] Failed to clone SOEM into temporary directory'); try { const dbg = [ '[soem-node] Failed to clone SOEM', 'clone exit code: ' + cloneRes.status, 'clone error: ' + (cloneRes.error ? cloneRes.error.message : 'none') ].join('\n'); fs.writeFileSync(path.join(__dirname, '..', 'BUILD-FAILED.txt'), dbg, { encoding: 'utf8' }); console.error('[soem-node] Wrote BUILD-FAILED.txt with debug info. You can initialize the dependency manually.'); } catch (e) { console.error('[soem-node] Could not write BUILD-FAILED.txt:', e && e.message); } process.exit(0); } // Copy from tmpDir to soemPath only if target does not already exist, or if missing files try { // Ensure external directory exists fs.mkdirSync(path.dirname(soemPath), { recursive: true }); // If target doesn't exist, move the clone into place; otherwise copy missing files if (!fs.existsSync(soemPath) || fs.readdirSync(soemPath).length === 0) { fs.mkdirSync(soemPath, { recursive: true }); // move by renaming when possible try { fs.renameSync(tmpDir, soemPath); tmpDir = null; // renamed, no need to delete } catch (e) { // fallback to copy } } // recursive copy helper const copyRecursive = (src, dest) => { const entries = fs.readdirSync(src, { withFileTypes: true }); fs.mkdirSync(dest, { recursive: true }); for (const ent of entries) { const s = path.join(src, ent.name); const d = path.join(dest, ent.name); if (ent.isDirectory()) { copyRecursive(s, d); } else if (ent.isSymbolicLink()) { try { const link = fs.readlinkSync(s); try { fs.symlinkSync(link, d); } catch (_) { /* ignore */ } } catch (_) { /* ignore */ } } else { try { fs.copyFileSync(s, d); } catch (_) { /* ignore */ } } } }; if (tmpDir) { copyRecursive(tmpDir, soemPath); } } catch (e) { console.error('[soem-node] Error copying SOEM into package:', e && e.message); try { fs.writeFileSync(path.join(__dirname, '..', 'BUILD-FAILED.txt'), '[soem-node] Error copying SOEM into package: ' + (e && e.message), { encoding: 'utf8' }); } catch (_) { /* ignore */ } // continue without failing install } finally { // cleanup tmpDir if present if (tmpDir && fs.existsSync(tmpDir)) { try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (_) { /* ignore */ } } } } // Verify SOEM is now available if (!fs.existsSync(soemCMake)) { console.error('[soem-node] SOEM initialization failed - CMakeLists.txt not found'); try { fs.writeFileSync(path.join(__dirname, '..', 'BUILD-FAILED.txt'), '[soem-node] SOEM initialization failed - CMakeLists.txt not found', { encoding: 'utf8' }); console.error('[soem-node] Wrote BUILD-FAILED.txt with debug info.'); } catch (e) { console.error('[soem-node] Could not write BUILD-FAILED.txt:', e && e.message); } process.exit(0); } console.log('[soem-node] SOEM successfully initialized'); } console.log('[soem-node] Building native addon with node-gyp...'); // Détection Electron: npm_config_runtime = 'electron' et/ou npm_config_target = version const npmRuntime = process.env.npm_config_runtime || ''; const npmTarget = process.env.npm_config_target || process.env.ELECTRON_VERSION || ''; const isElectron = npmRuntime.toLowerCase() === 'electron' || !!process.versions.electron; const sharedArgs = ['--verbose']; const npmArch = process.env.npm_config_arch; const npmPlatform = process.env.npm_config_platform; if (npmArch) sharedArgs.push(`--arch=${npmArch}`); if (npmPlatform) sharedArgs.push(`--platform=${npmPlatform}`); if (isElectron) { if (npmTarget) { sharedArgs.push(`--target=${npmTarget}`); } sharedArgs.push('--runtime=electron', '--dist-url=https://electronjs.org/headers'); } const runNodeGyp = (phase, extraArgs = []) => { const args = [phase, ...sharedArgs, ...extraArgs]; const isWin = process.platform === 'win32'; const localBinary = path.join( __dirname, '..', 'node_modules', '.bin', isWin ? 'node-gyp.cmd' : 'node-gyp' ); const spawnOpts = { stdio: 'inherit', encoding: 'utf8', shell: isWin }; let result; if (fs.existsSync(localBinary)) { result = spawnSync(localBinary, args, spawnOpts); if (result.status === 0) { return result; } if (result.error && result.error.code !== 'ENOENT') { return result; } } const globalCmd = isWin ? 'node-gyp.cmd' : 'node-gyp'; result = spawnSync(globalCmd, args, spawnOpts); if (result.status === 0) { return result; } if (!result.error || result.error.code !== 'ENOENT') { return result; } // Fallback to npx if direct binary failed result = spawnSync('npx', ['node-gyp', ...args], { stdio: 'inherit', encoding: 'utf8' }); return result; }; const handleFailure = (phase, result) => { console.error(`\n[soem-node] node-gyp ${phase} failed. Ensure Python and a C/C++ toolchain are installed.`); const debugLines = [ '[soem-node] Debug information:', `- Phase: ${phase}`, '- Platform: ' + process.platform, '- Node version: ' + process.version, '- Working directory: ' + process.cwd(), '- SOEM path exists: ' + fs.existsSync(soemPath), '- CMakeLists.txt exists: ' + fs.existsSync(soemCMake), '- Exit code: ' + result.status, '- Signal: ' + result.signal, ]; if (result.error) { debugLines.push('- Error: ' + result.error.message); debugLines.push('- Error code: ' + result.error.code); } debugLines.push('\n[soem-node] Installation did not complete native build. You can run `npm run build` in the package root or follow the README for manual build steps.'); debugLines.forEach(l => console.error(l)); try { fs.writeFileSync(path.join(__dirname, '..', 'BUILD-FAILED.txt'), debugLines.join('\n'), { encoding: 'utf8' }); console.error('[soem-node] Wrote BUILD-FAILED.txt with debug info.'); } catch (e) { console.error('[soem-node] Could not write BUILD-FAILED.txt:', e && e.message); } process.exit(0); }; const configureResult = runNodeGyp('configure'); if (configureResult.status !== 0) { handleFailure('configure', configureResult); } const buildResult = runNodeGyp('build'); if (buildResult.status !== 0) { handleFailure('build', buildResult); } console.log('[soem-node] Build completed successfully!');