#!/usr/bin/env node /** * Build script for the SF native Rust addon. * * Usage: * node native/scripts/build.js # release build * node native/scripts/build.js --dev # debug build * * Runs `cargo build` in the engine crate directory and copies the resulting * shared library to `native/addon/` with a `.node` extension so Node.js * can load it via `require()`. */ import { execSync } from "node:child_process"; import * as fs from "node:fs"; import * as path from "node:path"; const nativeRoot = path.resolve(import.meta.dirname, ".."); const engineDir = path.join(nativeRoot, "crates", "engine"); const addonDir = path.join(nativeRoot, "addon"); const isDev = process.argv.includes("--dev"); const profile = isDev ? "debug" : "release"; const cargoArgs = ["build"]; if (!isDev) cargoArgs.push("--release"); function shellValue(command) { try { return ( execSync(command, { stdio: ["ignore", "pipe", "ignore"], env: process.env, }) .toString() .trim() || undefined ); } catch { return undefined; } } function getZlibLibDir() { return ( process.env.ZLIB_LIB_DIR || shellValue("pkg-config --variable=libdir zlib") ); } function getCargoEnvironment() { const zlibLibDir = getZlibLibDir(); const defaultRustFlags = process.env.RUSTFLAGS || "-C target-cpu=native"; const rustFlags = zlibLibDir && !defaultRustFlags.includes(zlibLibDir) ? `${defaultRustFlags} -L native=${zlibLibDir}` : defaultRustFlags; const libraryPath = [zlibLibDir, process.env.LIBRARY_PATH] .filter(Boolean) .join(path.delimiter); return { ...process.env, // Optimize for native CPU when building locally RUSTFLAGS: rustFlags, ...(libraryPath ? { LIBRARY_PATH: libraryPath } : {}), }; } function getCargoTargetDirectory() { if (process.env.CARGO_TARGET_DIR) { return path.resolve(process.env.CARGO_TARGET_DIR); } const metadataRaw = execSync("cargo metadata --format-version 1 --no-deps", { cwd: engineDir, stdio: ["ignore", "pipe", "inherit"], env: getCargoEnvironment(), }).toString(); const metadata = JSON.parse(metadataRaw); if ( typeof metadata.target_directory !== "string" || metadata.target_directory.length === 0 ) { throw new Error("cargo metadata did not return a target_directory"); } return path.resolve(metadata.target_directory); } console.log(`Building forge-engine (${profile})...`); try { execSync(`cargo ${cargoArgs.join(" ")}`, { cwd: engineDir, stdio: "inherit", env: getCargoEnvironment(), }); } catch { process.exit(1); } // Locate the built library using Cargo's actual target directory. Under Nix this // is often redirected to a shared cache path rather than native/target. const cargoTargetRoot = getCargoTargetDirectory(); const targetDir = path.join(cargoTargetRoot, profile); const platformTag = `${process.platform}-${process.arch}`; const libraryNames = { darwin: "libforge_engine.dylib", linux: "libforge_engine.so", win32: "forge_engine.dll", }; const libName = libraryNames[process.platform]; if (!libName) { console.error(`Unsupported platform: ${process.platform}`); process.exit(1); } const sourcePath = path.join(targetDir, libName); if (!fs.existsSync(sourcePath)) { console.error(`Built library not found at: ${sourcePath}`); process.exit(1); } fs.mkdirSync(addonDir, { recursive: true }); const destFilename = isDev ? "forge_engine.dev.node" : `forge_engine.${platformTag}.node`; const destPath = path.join(addonDir, destFilename); fs.copyFileSync(sourcePath, destPath); console.log(`Installed: ${destPath}`); // Also copy into the npm platform package so `require('@singularity-forge/engine-')` // works when the package is symlinked into node_modules during local development. if (!isDev) { const platformPackageMap = { "darwin-arm64": "darwin-arm64", "darwin-x64": "darwin-x64", "linux-x64": "linux-x64-gnu", "linux-arm64": "linux-arm64-gnu", "win32-x64": "win32-x64-msvc", }; const npmSuffix = platformPackageMap[platformTag]; if (npmSuffix) { const npmPackageDir = path.join(nativeRoot, "npm", npmSuffix); const npmDest = path.join(npmPackageDir, "forge_engine.node"); if (fs.existsSync(npmPackageDir)) { fs.copyFileSync(sourcePath, npmDest); console.log(`Installed npm package binary: ${npmDest}`); } } } console.log("Build complete.");