@skairipaapps/liboqs-node
Version:
Node.js bindings for liboqs.
794 lines (671 loc) • 33.5 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { spawnSync } from 'child_process';
import rimraf from 'rimraf';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Configuration
const liboqsDir = path.join(__dirname, '..', 'deps', 'liboqs');
const buildDir = path.join(liboqsDir, 'build');
/**
* Patches x86-specific source files to skip them on ARM builds
* by wrapping the entire file content in #if guards
*/
function patchX86SpecificFiles(isArmBuild) {
if (!isArmBuild) return;
console.log('Patching x86-specific source files for ARM build...');
// Files that contain x86-specific code
const filesToPatch = [
{
path: path.join(liboqsDir, 'src', 'common', 'aes', 'aes128_ni.c'),
guard: `
// This file has been patched to exclude x86-specific code on ARM
#if !(defined(__ARM_ARCH) || defined(DISABLE_X86_INTRIN) || defined(__aarch64__) || defined(OQS_SKIP_X86_SPECIFIC))
// Original content will be here
#else
// Provide ARM-compatible stubs
#include "aes.h"
#include <stdint.h>
// Simple implementation for ARM architectures that don't support x86 intrinsics
void AES128_ECB_ni(const uint8_t *key, const uint8_t *in, uint8_t *out) {
// Fallback to non-x86 implementation
AES128_ECB(key, in, out);
}
#endif
`
},
{
path: path.join(liboqsDir, 'src', 'common', 'aes', 'aes256_ni.c'),
guard: `
// This file has been patched to exclude x86-specific code on ARM
#if !(defined(__ARM_ARCH) || defined(DISABLE_X86_INTRIN) || defined(__aarch64__) || defined(OQS_SKIP_X86_SPECIFIC))
// Original content will be here
#else
// Provide ARM-compatible stubs
#include "aes.h"
#include <stdint.h>
// Simple implementation for ARM architectures that don't support x86 intrinsics
void AES256_ECB_ni(const uint8_t *key, const uint8_t *in, uint8_t *out) {
// Fallback to non-x86 implementation
AES256_ECB(key, in, out);
}
#endif
`
}
];
for (const file of filesToPatch) {
if (!fs.existsSync(file.path)) {
console.log(`File not found: ${file.path}`);
continue;
}
let content = fs.readFileSync(file.path, 'utf8');
// Only patch if not already patched
if (!content.includes('__ARM_ARCH') && !content.includes('DISABLE_X86_INTRIN')) {
console.log(`Patching ${path.basename(file.path)} to skip x86-specific code on ARM...`);
// Split the guard template and insert the original content
const [guardStart, guardEnd] = file.guard.split('// Original content will be here');
content = guardStart + content + guardEnd;
fs.writeFileSync(file.path, content);
console.log(`✅ Successfully patched ${path.basename(file.path)}`);
} else {
console.log(`File ${path.basename(file.path)} already patched, skipping.`);
}
}
// Also patch CMakeLists.txt to handle x86-specific files properly
const cmakeListsPath = path.join(liboqsDir, 'src', 'common', 'CMakeLists.txt');
if (fs.existsSync(cmakeListsPath)) {
let cmakeContent = fs.readFileSync(cmakeListsPath, 'utf8');
// Only patch if not already patched
if (!cmakeContent.includes('DISABLE_X86_INTRIN') && !cmakeContent.includes('OQS_SKIP_X86_SPECIFIC')) {
console.log('Patching CMakeLists.txt to improve ARM compatibility...');
// More comprehensive modifications to the CMake file
let modified = false;
// 1. Find the section that includes AES NI files and make it conditional
const aesNiIncludePattern = /set\(AES_IMPL\s+\${AES_IMPL}\s+aes\/aes128_ni\.c\)/;
if (aesNiIncludePattern.test(cmakeContent)) {
cmakeContent = cmakeContent.replace(
aesNiIncludePattern,
'if(NOT DEFINED OQS_SKIP_X86_SPECIFIC AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|aarch64.*)")\n $&\n else()\n message(STATUS "Skipping x86-specific AES files on ${CMAKE_SYSTEM_PROCESSOR}")\n endif()'
);
modified = true;
}
// 2. Find the part that sets source properties with x86 flags
const setSourcePropsRegex = /set_source_files_properties\(aes\/aes\d+_ni\.c\s+PROPERTIES\s+COMPILE_FLAGS\s+"-maes\s+-mssse3"\)/g;
if (setSourcePropsRegex.test(cmakeContent)) {
// Wrap the property setting in a condition
cmakeContent = cmakeContent.replace(
setSourcePropsRegex,
'if(NOT DEFINED OQS_SKIP_X86_SPECIFIC AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|aarch64.*)")\n $&\n endif()'
);
modified = true;
}
// 3. Add a definition for ARM architecture
if (!cmakeContent.includes('OQS_SYSTEM_ARM')) {
const targetDefPattern = /target_compile_definitions\(common PRIVATE (.*)\)/;
if (targetDefPattern.test(cmakeContent)) {
cmakeContent = cmakeContent.replace(
targetDefPattern,
'if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|aarch64.*)")\n target_compile_definitions(common PRIVATE OQS_SYSTEM_ARM OQS_SKIP_X86_SPECIFIC)\nelse()\n $&\nendif()'
);
modified = true;
}
}
if (modified) {
fs.writeFileSync(cmakeListsPath, cmakeContent);
console.log('✅ Successfully patched CMakeLists.txt');
} else {
console.log('Did not find expected patterns in CMakeLists.txt, manual patching may be required');
}
} else {
console.log('Did not find x86-specific flags in CMakeLists.txt, or format is different than expected');
}
} else {
console.log('CMakeLists.txt not found, skipping.');
}
// Also create empty stub files as fallbacks for x86-specific headers
const x86Headers = [
{ name: 'wmmintrin.h', content: '// Empty stub for ARM compatibility\n#ifndef WMMINTRIN_H\n#define WMMINTRIN_H\n// This is a stub header for ARM builds\n#endif\n' },
{ name: 'tmmintrin.h', content: '// Empty stub for ARM compatibility\n#ifndef TMMINTRIN_H\n#define TMMINTRIN_H\n// This is a stub header for ARM builds\n#endif\n' },
{ name: 'emmintrin.h', content: '// Empty stub for ARM compatibility\n#ifndef EMMINTRIN_H\n#define EMMINTRIN_H\n// This is a stub header for ARM builds\n#endif\n' }
];
const includeDir = path.join(buildDir, 'include', 'stubs');
fs.mkdirSync(includeDir, { recursive: true });
x86Headers.forEach(header => {
const headerPath = path.join(includeDir, header.name);
try {
fs.writeFileSync(headerPath, header.content);
console.log(`Created stub header: ${headerPath}`);
} catch (error) {
console.error(`Failed to create stub header: ${headerPath}`, error);
}
});
// Add include path for stub headers
process.env.CFLAGS = `${process.env.CFLAGS || ''} -I${includeDir}`;
process.env.CXXFLAGS = `${process.env.CXXFLAGS || ''} -I${includeDir}`;
console.log(`Added stub headers include path to CFLAGS/CXXFLAGS`);
}
console.log('Starting liboqs build process... (syntax fixed)');
try {
// Step 1: Change to the liboqs directory
console.log(`Changing to directory: ${liboqsDir}`);
process.chdir(liboqsDir);
// Step 2: Remove the build directory if it exists
if (fs.existsSync(buildDir)) {
console.log('Removing existing build directory...');
rimraf.sync(buildDir);
}
// Step 3: Create a new build directory
console.log('Creating build directory...');
fs.mkdirSync(buildDir, { recursive: true });
// Step 4: Change to the build directory
console.log('Changing to build directory...');
process.chdir(buildDir);
// Step 5: Detect platform and run the appropriate build commands
if (process.platform === 'win32') {
console.log('Detected Windows platform - using Visual Studio generator');
const cmakeArgs = [
'-DBUILD_SHARED_LIBS=OFF',
'-DCMAKE_BUILD_TYPE=Release',
'-DOQS_BUILD_ONLY_LIB=ON',
'-DOQS_USE_OPENSSL=ON',
'-DOQS_DIST_BUILD=ON',
'-G',
'Visual Studio 17 2022',
'..'
];
console.log('Running CMake with args:', cmakeArgs.join(' '));
const cmakeResult = spawnSync('cmake', cmakeArgs, {
encoding: 'utf-8',
shell: true,
});
if (cmakeResult.error) {
console.error('cmake error:', cmakeResult.error);
}
console.log('cmake stdout:', cmakeResult.stdout);
console.error('cmake stderr:', cmakeResult.stderr);
if (cmakeResult.status !== 0) {
throw new Error(`cmake exited with code ${cmakeResult.status}`);
}
console.log('Running MSBuild via cmake --build...');
const buildResult = spawnSync('cmake', ['--build', '.', '--config', 'Release'], {
encoding: 'utf-8',
shell: true,
});
if (buildResult.error) {
console.error('Build error:', buildResult.error);
}
console.log('Build stdout:', buildResult.stdout);
console.error('Build stderr:', buildResult.stderr);
if (buildResult.status !== 0) {
throw new Error(`Build exited with code ${buildResult.status}`);
}
} else {
console.log('Detected Unix-like platform - using Ninja generator');
// Enhanced architecture detection with prioritization for cross-compilation
// Check for ARM-specific environment variables that might indicate cross-compilation
const npmConfigArch = process.env.npm_config_arch;
const ccCompiler = process.env.CC;
// Prioritize compiler and npm_config_arch over process.arch
// Detect ARM compilers
const isArmCompiler = ccCompiler && (
ccCompiler.includes('arm-linux') ||
ccCompiler.includes('armv7') ||
ccCompiler.includes('armhf')
);
// Detect ARM64/aarch64 compilers
const isAarch64Compiler = ccCompiler && (
ccCompiler.includes('aarch64')
);
// Detect npm target architecture
const isArmTarget = npmConfigArch === 'arm' || npmConfigArch === 'armhf';
const isAarch64Target = npmConfigArch === 'arm64';
// Detect native architecture
const isArmLinux = process.platform === 'linux' && process.arch.startsWith('arm');
const isAarch64 = process.arch === 'arm64' || process.arch === 'aarch64';
// Cross-compilation check (building on one architecture for another)
const isCrossCompiling = (isArmCompiler || isAarch64Compiler || isArmTarget || isAarch64Target) &&
process.arch !== 'arm' && process.arch !== 'arm64' && process.arch !== 'aarch64';
// Combined architecture detection - prioritize compiler and npm_config_arch over process.arch
const isArmArchitecture = isArmCompiler || isArmTarget || isArmLinux || isAarch64Compiler || isAarch64Target || isAarch64;
// Determine if we're specifically targeting ARM64/aarch64
const isTargetingAarch64 = isAarch64Compiler || isAarch64Target || (!isCrossCompiling && isAarch64);
// Only consider x86_64 if we're not cross-compiling to ARM
const isX86_64 = !isArmArchitecture;
// Detailed system information logging for debugging
console.log('==================== Build System Information ====================');
console.log(`Platform: ${process.platform}, Architecture: ${process.arch}`);
console.log(`Node.js version: ${process.version}`);
console.log(`Environment: npm_config_arch=${npmConfigArch || 'not set'}, CC=${ccCompiler || 'not set'}`);
console.log(`Detected compiler for ARM: ${isArmCompiler ? 'Yes' : 'No'}, for ARM64: ${isAarch64Compiler ? 'Yes' : 'No'}`);
console.log(`Detected npm target for ARM: ${isArmTarget ? 'Yes' : 'No'}, for ARM64: ${isAarch64Target ? 'Yes' : 'No'}`);
console.log(`Cross-compilation: ${isCrossCompiling ? 'Yes' : 'No'}`);
console.log(`Final architecture decision: ${isArmArchitecture ? 'ARM' : 'non-ARM'}, ARM64/aarch64: ${isTargetingAarch64 ? 'Yes' : 'No'}`);
console.log('==================================================================');
// Force ARM architecture when cross-compiling with ARM tools
const forceArmArchitecture = isArmCompiler || isArmTarget;
const explicitCrossCompile = isArmCompiler && process.arch === 'x64';
// Set up basic cmake args
const cmakeArgs = [];
// For ARM builds, add early flags to completely exclude x86-specific files
if (isArmArchitecture) {
// Force CMake to skip x86 files entirely
cmakeArgs.push('-DOQS_SKIP_X86_FILES=ON');
cmakeArgs.push('-DCMAKE_DISABLE_FIND_PACKAGE_X86=ON');
// Add compiler definitions before any other flags
const skipX86Defines = [
'-DDISABLE_X86_INTRIN',
'-DOQS_NO_AES_NI=1',
'-DOQS_SKIP_X86_SPECIFIC=1',
'-D__ARM_CROSS_COMPILE=1'
].join(' ');
// Set environment variables to control compiler behavior
process.env.CFLAGS = `${skipX86Defines} ${process.env.CFLAGS || ''}`;
process.env.CXXFLAGS = `${skipX86Defines} ${process.env.CXXFLAGS || ''}`;
console.log(`Setting early compiler flags for ARM builds:`);
console.log(`CFLAGS: ${process.env.CFLAGS}`);
console.log(`CXXFLAGS: ${process.env.CXXFLAGS}`);
}
// Check if we're building for Bun (need shared libraries for FFI)
const buildShared = process.env.BUILD_SHARED === 'true' || process.argv.includes('--shared');
// Add standard CMake args
cmakeArgs.push(
buildShared ? '-DBUILD_SHARED_LIBS=ON' : '-DBUILD_SHARED_LIBS=OFF',
'-DCMAKE_BUILD_TYPE=Release',
'-DOQS_BUILD_ONLY_LIB=ON',
'-DOQS_USE_OPENSSL=ON',
'-DOQS_DIST_BUILD=ON',
'-GNinja'
);
if (buildShared) {
console.log('Building shared libraries for FFI compatibility...');
}
// Add cross-compilation setup if needed
if (explicitCrossCompile) {
console.log('Setting up explicit cross-compilation for ARM...');
cmakeArgs.push('-DCMAKE_CROSSCOMPILING=ON');
cmakeArgs.push('-DCMAKE_SYSTEM_NAME=Linux');
cmakeArgs.push('-DCMAKE_SYSTEM_VERSION=1');
// Set explicit compilers if using ARM compiler
if (ccCompiler && ccCompiler.includes('arm-linux-gnueabihf')) {
cmakeArgs.push('-DCMAKE_C_COMPILER=arm-linux-gnueabihf-gcc');
cmakeArgs.push('-DCMAKE_CXX_COMPILER=arm-linux-gnueabihf-g++');
}
}
if (isArmArchitecture) {
console.log('============== Configuring for ARM Architecture ==============');
// Explicitly disable ALL x86-specific optimizations and features
cmakeArgs.push('-DOQS_DIST_X86_64_BUILD=OFF');
cmakeArgs.push('-DOQS_USE_AES_INSTRUCTIONS=OFF');
cmakeArgs.push('-DOQS_DISABLE_X86=ON');
// Additional x86 disabling to ensure no x86 instructions are used
cmakeArgs.push('-DOQS_SPEED_USE_AES_NI=OFF');
cmakeArgs.push('-DOQS_USE_X86_64_BUILD=OFF');
cmakeArgs.push('-DOQS_ENABLE_AES_NI=OFF');
cmakeArgs.push('-DOQS_ENABLE_SSSE3=OFF');
cmakeArgs.push('-DOQS_ENABLE_SSE2=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX2=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX512=OFF');
cmakeArgs.push('-DOQS_ENABLE_PCLMULQDQ=OFF');
// Force ARM build configuration
if (forceArmArchitecture) {
cmakeArgs.push('-DOQS_ARM_BUILD=ON');
}
// Set the system processor explicitly to prevent auto-detection issues
if (isTargetingAarch64) {
console.log('Setting system processor to aarch64');
cmakeArgs.push('-DCMAKE_SYSTEM_PROCESSOR=aarch64');
// Add ARM64-specific CMake options
cmakeArgs.push('-DOQS_DIST_ARM64_BUILD=ON');
cmakeArgs.push('-DARMV8=ON');
} else {
console.log('Setting system processor to arm');
cmakeArgs.push('-DCMAKE_SYSTEM_PROCESSOR=arm');
}
// Add compile definitions to skip x86-specific files
if (explicitCrossCompile || isArmCompiler) {
const armDefines = [
'-DDISABLE_X86_INTRIN',
'-DOQS_NO_AES_NI',
'-DOQS_SKIP_X86_SPECIFIC=1',
'-D__ARM_CROSS_COMPILE=1'
].join(' ');
// Add to both C and CXX flags
process.env.CFLAGS = `${process.env.CFLAGS || ''} ${armDefines}`;
process.env.CXXFLAGS = `${process.env.CXXFLAGS || ''} ${armDefines}`;
console.log('Setting ARM-specific environment variables:');
console.log(`CFLAGS: ${process.env.CFLAGS}`);
console.log(`CXXFLAGS: ${process.env.CXXFLAGS}`);
}
// Explicitly disable any compiler flags that might cause issues on ARM
cmakeArgs.push('-DCMAKE_C_FLAGS_INIT=-DDISABLE_X86_INTRIN');
cmakeArgs.push('-DCMAKE_CXX_FLAGS_INIT=-DDISABLE_X86_INTRIN');
// Patch x86-specific source files to skip them on ARM builds
patchX86SpecificFiles(true);
// For ARM64, enable ARM-specific optimizations
if (isTargetingAarch64) {
console.log('ARM64 architecture detected - enabling ARM64-specific optimizations');
cmakeArgs.push('-DOQS_DIST_ARM64_V8_BUILD=ON');
cmakeArgs.push('-DOQS_USE_ARM_AES_INSTRUCTIONS=ON');
// ARM64-specific compiler flags
if (process.platform === 'darwin') {
// macOS/Apple Silicon specific flags
console.log('Configuring for Apple Silicon (ARM64)');
cmakeArgs.push('-DCMAKE_C_FLAGS=-arch arm64');
cmakeArgs.push('-DCMAKE_CXX_FLAGS=-arch arm64');
} else if (isAarch64Compiler) {
// For cross-compilation with ARM64 compiler
console.log('Configuring for cross-compilation with ARM64 compiler');
// Explicitly add ARM64-specific CMake configuration
cmakeArgs.push('-DOQS_DIST_ARM64_V8_BUILD=ON');
// Check if we're using aarch64-linux-gnu-gcc
const isAarch64LinuxGnu = ccCompiler && ccCompiler.includes('aarch64-linux-gnu');
if (isAarch64LinuxGnu) {
console.log('Detected aarch64-linux-gnu compiler - using specific flags');
// Set environment variables to override any conflicting flags
if (!process.env.CFLAGS) {
process.env.CFLAGS = '-fPIC -D__ARM_FEATURE_CRYPTO -DDISABLE_X86_INTRIN';
console.log('Setting CFLAGS environment variable:', process.env.CFLAGS);
}
if (!process.env.CXXFLAGS) {
process.env.CXXFLAGS = '-fPIC -D__ARM_FEATURE_CRYPTO -DDISABLE_X86_INTRIN';
console.log('Setting CXXFLAGS environment variable:', process.env.CXXFLAGS);
}
// Simple ARM64 flags for aarch64-linux-gnu-gcc
// IMPORTANT: Avoid using any architecture-specific flags like -march
// that might cause conflicts with the compiler
const aarch64LinuxFlags = [
'-fPIC',
'-D__ARM_FEATURE_CRYPTO',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${aarch64LinuxFlags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${aarch64LinuxFlags}`);
} else {
// Generic ARM64 flags for other compilers
const aarch64Flags = [
'-fPIC',
'-D__DISABLE_AES__',
'-D__DISABLE_SSSE3__',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${aarch64Flags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${aarch64Flags}`);
}
} else if (isArmCompiler) {
// For cross-compilation with ARM compiler
console.log('Configuring for cross-compilation with ARM compiler');
const armCrossFlags = [
'-fPIC',
'-D__DISABLE_AES__',
'-D__DISABLE_SSSE3__',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${armCrossFlags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${armCrossFlags}`);
} else {
// Linux ARM64 specific flags - explicitly avoid any x86 instructions
console.log('Configuring for Linux ARM64');
// The +crypto suffix enables hardware crypto extensions if available
// Note: DO NOT use -mfpu=neon or -fno-integrated-as with ARM64
const arm64Flags = [
'-march=armv8-a+crypto',
'-fPIC',
'-D__ARM_FEATURE_CRYPTO',
// Explicitly disable x86 instructions in compiler flags
'-D__DISABLE_AES__',
'-D__DISABLE_SSSE3__',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${arm64Flags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${arm64Flags}`);
}
} else {
// Non-ARM64 ARM architecture (e.g., armv7)
console.log('ARM (non-ARM64) architecture detected');
// Only use ARMv7-specific flags if we're specifically using an ARMv7 compiler
if (isArmCompiler && !isAarch64Compiler) {
console.log('Using ARMv7-specific compiler flags');
const armv7Flags = [
'-march=armv7-a',
'-fPIC',
'-mfpu=neon',
// Explicitly disable x86 instructions in compiler flags
'-D__DISABLE_AES__',
'-D__DISABLE_SSSE3__',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${armv7Flags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${armv7Flags}`);
} else {
// Generic ARM flags without architecture-specific options
const genericArmFlags = [
'-fPIC',
'-D__DISABLE_AES__',
'-D__DISABLE_SSSE3__',
'-DDISABLE_X86_INTRIN'
].join(' ');
cmakeArgs.push(`-DCMAKE_C_FLAGS=${genericArmFlags}`);
cmakeArgs.push(`-DCMAKE_CXX_FLAGS=${genericArmFlags}`);
}
}
console.log('==================================================================');
} else {
// For x86/x64 platforms
console.log('============= Configuring for x86/x64 Architecture =============');
cmakeArgs.push('-DOQS_DIST_X86_64_BUILD=ON');
// Check if we're in a container environment that might not support AVX2
const isContainer = process.env.CI === 'true' && process.env.ACT === 'true';
const disableAesNi = process.env.CMAKE_FLAGS && process.env.CMAKE_FLAGS.includes('DOQS_ENABLE_AESNI=OFF');
// Also check for explicit CMAKE_FLAGS from environment
if (process.env.CMAKE_FLAGS) {
console.log('Found CMAKE_FLAGS environment variable:', process.env.CMAKE_FLAGS);
// Parse CMAKE_FLAGS and add each flag
const flags = process.env.CMAKE_FLAGS.split(' ').filter(flag => flag.trim().length > 0);
flags.forEach(flag => {
if (flag.startsWith('-D')) {
cmakeArgs.push(flag);
console.log('Added CMake flag from environment:', flag);
}
});
}
if (isContainer || disableAesNi) {
console.log('Container or restricted environment detected - disabling advanced CPU instructions');
cmakeArgs.push('-DOQS_USE_AES_INSTRUCTIONS=OFF');
cmakeArgs.push('-DOQS_ENABLE_AESNI=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX2=OFF');
cmakeArgs.push('-DOQS_ENABLE_AVX512=OFF');
cmakeArgs.push('-DOQS_ENABLE_SSSE3=OFF');
cmakeArgs.push('-DOQS_ENABLE_SSE2=OFF');
cmakeArgs.push('-DOQS_ENABLE_PCLMULQDQ=OFF');
} else if (!process.env.CMAKE_FLAGS || !process.env.CMAKE_FLAGS.includes('DOQS_USE_AES_INSTRUCTIONS')) {
cmakeArgs.push('-DOQS_USE_AES_INSTRUCTIONS=ON');
}
cmakeArgs.push('-DCMAKE_SYSTEM_PROCESSOR=x86_64');
console.log('==================================================================');
}
// Add any additional CMAKE_FLAGS that weren't already handled
if (process.env.CMAKE_FLAGS) {
console.log('Processing additional CMAKE_FLAGS environment variable:', process.env.CMAKE_FLAGS);
const flags = process.env.CMAKE_FLAGS.split(' ').filter(flag => flag.trim().length > 0);
flags.forEach(flag => {
if (flag.startsWith('-D') && !cmakeArgs.includes(flag)) {
cmakeArgs.push(flag);
console.log('Added additional CMake flag from environment:', flag);
}
});
}
cmakeArgs.push('..');
// Print the complete CMake command for debugging
console.log('Running CMake with args:', cmakeArgs.join(' '));
try {
// Add special handling for ARM64/aarch64 builds
// Add special handling for ARM builds
if (isArmArchitecture) {
if (isTargetingAarch64) {
console.log('⚠️ Using ARM64/aarch64 build configuration');
console.log('⚠️ This will explicitly disable ARMv7-specific flags that are incompatible with aarch64');
// Check compiler compatibility
if (ccCompiler && ccCompiler.includes('aarch64')) {
console.log('✅ Detected aarch64 compiler: ' + ccCompiler);
// For cross-compilation with aarch64-linux-gnu-gcc
if (ccCompiler.includes('aarch64-linux-gnu')) {
console.log('⚠️ Cross-compiling with aarch64-linux-gnu-gcc - using minimal flags');
// Handle cross-compilation environment
cmakeArgs.push('-DCMAKE_CROSSCOMPILING=ON');
// Remove any potentially problematic flags from the command line
for (let i = 0; i < cmakeArgs.length; i++) {
if (cmakeArgs[i].includes('-march=') ||
cmakeArgs[i].includes('-mfpu=') ||
cmakeArgs[i].includes('-fno-integrated-as')) {
console.log(`⚠️ Removing potentially problematic flag: ${cmakeArgs[i]}`);
cmakeArgs.splice(i, 1);
i--;
}
}
}
} else if (ccCompiler) {
console.log('⚠️ Warning: Using non-aarch64 compiler for ARM64 build: ' + ccCompiler);
}
} else if (isArmCompiler) {
console.log('⚠️ Cross-compiling for ARM with compiler: ' + ccCompiler);
// Create a simple preprocessor header to exclude x86-specific code
const skipX86Header = path.join(buildDir, 'skip_x86.h');
console.log(`Creating helper header at ${skipX86Header} to skip x86-specific code`);
const headerContent = `
#ifndef SKIP_X86_H
#define SKIP_X86_H
// This header is automatically generated to skip x86-specific code during ARM builds
#define DISABLE_X86_INTRIN
#define OQS_NO_AES_NI
#define OQS_SKIP_X86_SPECIFIC 1
#endif // SKIP_X86_H
`;
try {
fs.writeFileSync(skipX86Header, headerContent);
console.log('Successfully created helper header');
// Add the directory containing the header to include path
cmakeArgs.push(`-DCMAKE_C_FLAGS=-I${buildDir} -DOQS_NO_AES_NI=1 -DOQS_SKIP_X86_SPECIFIC=1`);
} catch (error) {
console.error('Failed to create helper header:', error);
}
}
}
const cmakeResult = spawnSync('cmake', cmakeArgs, {
stdio: 'inherit',
shell: false,
});
if (cmakeResult.error) {
console.error('CMake error details:', cmakeResult.error);
throw new Error(`Failed to run cmake: ${cmakeResult.error.message}`);
}
if (cmakeResult.status !== 0) {
throw new Error(`cmake process exited with code ${cmakeResult.status}`);
}
} catch (error) {
console.error('CMake configuration failed. See error details above.');
console.error('This might be due to architecture-specific flags incompatibility.');
// Create a patch to fix CMakeLists.txt if we're on ARM and encounter issues
if (isArmArchitecture) {
console.error('\nTrying to check if we can fix the issue automatically...');
try {
// Look for common files that might need patching
const commonCMakeListsPath = path.join(liboqsDir, 'src', 'common', 'CMakeLists.txt');
if (fs.existsSync(commonCMakeListsPath)) {
console.log('Found src/common/CMakeLists.txt file, checking for x86-specific flags...');
// If needed, we could implement automatic patching of the file here
console.log('Manual patching may be required. See error messages for instructions.');
}
} catch (checkError) {
console.error('Failed to check for possible fixes:', checkError.message);
}
}
throw error;
}
// Run ninja to build
console.log('Running ninja to build liboqs...');
try {
const ninjaResult = spawnSync('ninja', [], {
stdio: 'inherit',
shell: false,
});
if (ninjaResult.error) {
console.error('Ninja build error details:', ninjaResult.error);
throw new Error(`Failed to run ninja: ${ninjaResult.error.message}`);
}
if (ninjaResult.status !== 0) {
console.error('Ninja build failed. This might be due to compiler flag incompatibilities.');
// If on ARM, provide a helpful message about the likely cause
if (isArmArchitecture) {
console.error('\nERROR: The build likely failed because of architecture-specific issues.');
if (isTargetingAarch64) {
console.error('\nFor ARM64/aarch64 compilation:');
console.error('1. Ensure you are not using ARMv7-specific flags with aarch64 compiler');
console.error('2. Do NOT use -mfpu=neon or -fno-integrated-as with aarch64-linux-gnu-gcc');
console.error('3. Use only ARM64-compatible flags');
console.error('4. Check if your compiler is properly installed and supports the target architecture');
console.error('\nTo override compiler flags, you can try:');
console.error('export CFLAGS="-fPIC -DOQS_NO_AES_NI=1 -DOQS_SKIP_X86_SPECIFIC=1"');
console.error('export CXXFLAGS="-fPIC -DOQS_NO_AES_NI=1 -DOQS_SKIP_X86_SPECIFIC=1"');
console.error('npm run liboqs:build');
} else {
console.error('\nFor ARMv7 compilation:');
console.error('The compiler error usually involves flags like -maes or -mssse3 which are x86-specific.\n');
console.error('Try one of these options:');
console.error('\nOption 1: Modify the source code to skip x86-specific files:');
console.error('1. Edit deps/liboqs/src/common/aes/aes128_ni.c');
console.error('2. Add at the beginning of the file:');
console.error('#if !defined(DISABLE_X86_INTRIN) && !defined(__ARM_ARCH)');
console.error('3. Add at the end of the file: #endif');
console.error('\nOption 2: Set environment variables to skip x86 code:');
console.error('export CFLAGS="-DOQS_NO_AES_NI=1 -DOQS_SKIP_X86_SPECIFIC=1"');
console.error('export CXXFLAGS="-DOQS_NO_AES_NI=1 -DOQS_SKIP_X86_SPECIFIC=1"');
}
console.error('\nAlternatively, you can try using a pre-built binary for your platform if available.\n');
}
throw new Error(`ninja process exited with code ${ninjaResult.status}`);
}
} catch (error) {
console.error('Build failed. See error details above.');
throw error;
}
}
console.log('liboqs build completed successfully');
// Create symlink from liboqs.a to liboqs-internal.a for Node.js compatibility
console.log('Creating symlink for Node.js compatibility...');
try {
const libDir = path.join(buildDir, 'lib');
const targetFile = path.join(libDir, 'liboqs-internal.a');
const symlinkFile = path.join(libDir, 'liboqs.a');
// Check if the target file exists
if (fs.existsSync(targetFile)) {
// Remove existing symlink if it exists
if (fs.existsSync(symlinkFile)) {
console.log('Removing existing liboqs.a symlink...');
fs.unlinkSync(symlinkFile);
}
// Create the symlink
fs.symlinkSync('liboqs-internal.a', symlinkFile);
console.log('✅ Successfully created symlink: liboqs.a -> liboqs-internal.a');
// Verify the symlink was created
if (fs.existsSync(symlinkFile)) {
console.log('✅ Symlink verification passed');
} else {
console.warn('⚠️ Warning: Symlink creation may have failed - file not found after creation');
}
} else {
console.warn('⚠️ Warning: liboqs-internal.a not found, skipping symlink creation');
console.log(`Expected file location: ${targetFile}`);
}
} catch (symlinkError) {
console.warn('⚠️ Warning: Failed to create symlink for liboqs.a:', symlinkError.message);
console.log('You may need to manually create the symlink if Node.js build fails');
}
} catch (error) {
console.error('Error building liboqs:', error.message);
console.error('Please check build configuration and try again.');
process.exit(1);
}