UNPKG

@animetosho/parpar

Version:

High performance multi-threaded PAR2 creation library

330 lines (287 loc) 14.5 kB
var os = require('os'); // -- change these variables if desired -- var compileConcurrency = os.cpus().length; var python = process.env.BUILD_PYTHON || null; if(process.env.BUILD_PYTHONPATH) process.env.PATH = process.env.BUILD_PYTHONPATH + (os.platform() == 'win32' ? ';' : ':') + process.env.PATH; // if need to specify a Python path var buildArch = process.env.BUILD_ARCH || os.arch(); // x86, x64, arm, arm64 var buildOs = process.env.BUILD_OS || os.platform(); var nexeBase = process.env.BUILD_DIR || './build'; var nodeVer = process.env.BUILD_NODEVER || '12.22.12'; // v12 is the oldest version with native MSVC 2019 support var staticness = process.env.BUILD_STATIC || (buildOs == 'linux' ? '--partly-static' : '--fully-static'); // OpenCL support requires libdl on Linux var vsSuite = null; // if on Windows, and it's having trouble finding Visual Studio, try set this to, e.g. 'vs2019' or 'vs2017' var disableLTO = !!process.env.BUILD_NO_LTO; var stripM32 = !!process.env.BUILD_STRIP_M32; // downloads can be disabled by editing the 'sourceUrl' line below; source code needs to be placed in `${nexeBase}/${nodeVer}` // fix up arch aliases const archAliases = {amd64: 'x64', i386: 'x86', ia32: 'x86', armhf: 'arm', aarch64: 'arm64'}; if(buildArch in archAliases) buildArch = archAliases[buildArch]; const osAliases = {darwin: 'mac', macos: 'mac', mac: 'mac', win32: 'win', win: 'win', linux: 'linux'}; var nexe = require('nexe'); var path = require('path'); var fs = require('fs'); var browserify = require('browserify'); var pkg = require('../package.json'); const copyRecursiveSync = function(src, dest) { if(fs.statSync(src).isDirectory()) { if(!fs.existsSync(dest)) fs.mkdirSync(dest); fs.readdirSync(src).forEach(function(child) { copyRecursiveSync(path.join(src, child), path.join(dest, child)); }); } else fs.copyFileSync(src, dest); }; // create embeddable help fs.writeFileSync('../bin/help.json', JSON.stringify({ full: fs.readFileSync('../help-full.txt').toString(), short: fs.readFileSync('../help.txt').toString() })); // bundle into a single JS file // TODO: maybe explore copying all files instead, instead of bundling let b = browserify(['../bin/parpar.js'], { debug: false, detectGlobals: true, node: true }); ['../build/Release/parpar_gf.node'].forEach(exclude => { b.exclude(exclude); }); // invoke nexe var configureArgs = [staticness, '--without-dtrace', '--without-etw', '--without-npm', '--with-intl=none', '--without-report', '--without-node-options', '--without-inspector', '--without-siphash', '--dest-cpu=' + buildArch]; if(buildOs in osAliases) configureArgs.push('--dest-os=' + osAliases[buildOs]); var vcbuildArgs = ["nosign", buildArch, "noetw", "intl-none", "release", "static"]; // --v8-lite-mode ? if(parseFloat(nodeVer) >= 8) { configureArgs.push('--without-intl'); vcbuildArgs.push('without-intl'); } if(parseFloat(nodeVer) >= 10) { if(buildOs == 'linux' && !disableLTO) configureArgs.push('--enable-lto'); if(buildOs == 'win32') { if(!disableLTO) { configureArgs.push('--with-ltcg'); vcbuildArgs.push('ltcg'); } vcbuildArgs.push('no-cctest'); } } else { configureArgs.push('--without-perfctr'); vcbuildArgs.push('noperfctr'); } if(vsSuite) vcbuildArgs.push(vsSuite); if(process.env.BUILD_CONFIGURE) configureArgs = configureArgs.concat(process.env.BUILD_CONFIGURE.split(' ')); if(process.env.BUILD_VCBUILD) vcbuildArgs = vcbuildArgs.concat(process.env.BUILD_VCBUILD.split(' ')); var v8gyp = parseFloat(nodeVer) >= 12 ? 'tools/v8_gypfiles/v8.gyp' : (parseFloat(nodeVer) >= 10 ? 'deps/v8/gypfiles/v8.gyp' : 'deps/v8/src/v8.gyp'); nexe.compile({ input: 'blank.js', // we'll overwrite _third_party_main instead name: 'parpar', target: buildOs+'-'+buildArch+'-'+nodeVer, build: true, mangle: false, bundle: false, python: python, flags: [], // runtime flags configure: configureArgs, make: ['-j', compileConcurrency], vcBuild: vcbuildArgs, snapshot: null, // TODO: consider using this temp: nexeBase, rc: { ProductName: pkg.name, FileDescription: pkg.description, FileVersion: pkg.version, ProductVersion: pkg.version, InternalName: 'parpar', CompanyName: 'Anime Tosho' }, //fakeArgv: 'parpar', //sourceUrl: '<disable_download>', loglevel: process.env.BUILD_LOGLEVEL || 'verbose', patches: [ // remove nexe's boot-nexe code + fix argv async (compiler, next) => { var bootFile = 'lib/internal/bootstrap_node.js'; if(parseFloat(nodeVer) >= 12) bootFile = 'lib/internal/bootstrap/pre_execution.js'; else if(parseFloat(nodeVer) >= 10) bootFile = 'lib/internal/bootstrap/node.js'; if(parseFloat(nodeVer) >= 12) { // TODO: is the double'd javascript entry (by nexe) problematic? await compiler.replaceInFileAsync(bootFile, /(initializePolicy|initializeFrozenIntrinsics)\(\);\s*!\(function.+?new Module.+?\}\)\(\);/s, "$1();"); // fix argv await compiler.replaceInFileAsync(bootFile, /patchProcessObject\(expandArgv1\);/, 'patchProcessObject(false); if(!process.send) process.argv.splice(1,0,"parpar");'); } // I don't get the point of the fs patch, so just remove it... await compiler.replaceInFileAsync(bootFile, /if \(true\) \{.+?__nexe_patch\(.+?\}\n/s, ''); return next(); }, // fix for building on Alpine // https://gitlab.alpinelinux.org/alpine/aports/-/issues/8626 async (compiler, next) => { if(parseFloat(nodeVer) >= 12) { await compiler.replaceInFileAsync(v8gyp, /('target_defaults': \{)( 'cflags': \['-U_FORTIFY_SOURCE'\],)?/, "$1 'cflags': ['-U_FORTIFY_SOURCE'],"); } else { await compiler.replaceInFileAsync(v8gyp, /('target_defaults': {'cflags': \['-U_FORTIFY_SOURCE'\]}, )?'targets': \[/, "'target_defaults': {'cflags': ['-U_FORTIFY_SOURCE']}, 'targets': ["); } await compiler.replaceInFileAsync('node.gyp', /('target_name': '(node_mksnapshot|mkcodecache|<\(node_core_target_name\)|<\(node_lib_target_name\))',)( 'cflags': \['-U_FORTIFY_SOURCE'\],)?/g, "$1 'cflags': ['-U_FORTIFY_SOURCE'],"); return next(); }, // increase default UV_THREADPOOL_SIZE to 8 (allows higher --chunk-read-threads) async (compiler, next) => { await compiler.replaceInFileAsync('deps/uv/src/threadpool.c', /uv_thread_t default_threads[\d+];/, "uv_thread_t default_threads[8];"); return next(); }, // add parpar_gf into source list async (compiler, next) => { var bindingsFile; if(parseFloat(nodeVer) >= 12) { await compiler.replaceInFileAsync('node.gyp', /('deps\/histogram\/histogram\.gyp:histogram')(,'deps\/parpar\/binding\.gyp:parpar_gf')?/g, "$1,'deps/parpar/binding.gyp:parpar_gf'"); bindingsFile = 'src/node_binding.cc'; } else if(parseFloat(nodeVer) >= 10) { await compiler.replaceInFileAsync('node.gyp', /('target_name': '<\(node_lib_target_name\)',)('dependencies': \['deps\/parpar\/binding\.gyp:parpar_gf'\], )?/g, "$1'dependencies': ['deps/parpar/binding.gyp:parpar_gf'], "); bindingsFile = 'src/node_internals.h'; } else { await compiler.replaceInFileAsync('node.gyp', /('target_name': '<\(node_lib_target_name\)',[^}]*?'dependencies': \[)('deps\/parpar\/binding\.gyp:parpar_gf', )?/g, "$1'deps/parpar/binding.gyp:parpar_gf', "); bindingsFile = 'src/node_internals.h'; } // also add it as a valid binding await compiler.replaceInFileAsync(bindingsFile, /(V\(async_wrap\))( V\(parpar_gf\))?/, "$1 V(parpar_gf)"); // patch module whitelist if(parseFloat(nodeVer) >= 12) { // avoid nexe's methods to prevent double-writing this to node.gyp const loaderFile = path.join(compiler.src, 'lib/internal/bootstrap/loaders.js'); data = fs.readFileSync(loaderFile).toString(); data = data.replace(/('async_wrap',)( 'parpar_gf',)?/, "$1 'parpar_gf',"); fs.writeFileSync(loaderFile, data); } return next(); }, // copy parpar_gf sources async (compiler, next) => { const dst = path.join(compiler.src, 'deps', 'parpar') + path.sep; const base = '..' + path.sep; if(!fs.existsSync(dst + 'binding.gyp')) { if(!fs.existsSync(dst.slice(0, -1))) fs.mkdirSync(dst.slice(0, -1)); copyRecursiveSync(base + 'gf16', dst + 'gf16'); copyRecursiveSync(base + 'hasher', dst + 'hasher'); copyRecursiveSync(base + 'src', dst + 'src'); fs.copyFileSync(base + 'binding.gyp', dst + 'binding.gyp'); } // patch parpar_gf let data = await compiler.readFileAsync('deps/parpar/src/gf.cc'); data = data.contents.toString(); const internalModuleRegister = (parseFloat(nodeVer) >= 12) ? 'NODE_MODULE_CONTEXT_AWARE_INTERNAL' : 'NODE_BUILTIN_MODULE_CONTEXT_AWARE'; data = data.replace(/NODE_MODULE\(/, '#define NODE_WANT_INTERNALS 1\n#include <node_internals.h>\n' + internalModuleRegister + '('); data = data.replace(/Local<Value> module,\s*void\* priv/, 'Local<Value> module, v8::Local<v8::Context> context, void* priv'); await compiler.setFileContentsAsync('deps/parpar/src/gf.cc', data); data = await compiler.readFileAsync('deps/parpar/binding.gyp'); data = data.contents.toString(); data = data.replace(/"target_name": "parpar_gf",( "type": "static_library",)?/, '"target_name": "parpar_gf", "type": "static_library",'); var includeList = '"../../src", "../v8/include", "../uv/include"'; if(parseFloat(nodeVer) < 12) includeList += ', "../cares/include"'; data = data.replace(/"include_dirs": \[("\.\.\/\.\.\/src"[^\]]+)?"gf16"/, '"include_dirs": [' + includeList + ', "gf16"'); data = data.replace(/"enable_native_tuning%": 1/, '"enable_native_tuning%": 0'); if(staticness == '--fully-static') data = data.replace(/"PARPAR_LIBDL_SUPPORT",?/, ''); await compiler.setFileContentsAsync('deps/parpar/binding.gyp', data); return next(); }, // disable unnecessary executables async (compiler, next) => { await compiler.replaceInFileAsync('node.gyp', /(['"]target_name['"]:\s*['"](cctest|embedtest|fuzz_url|fuzz_env)['"],\s*['"]type['"]:\s*)['"]executable['"]/g, "$1'none'"); return next(); }, // disable exports async (compiler, next) => { await compiler.replaceInFileAsync('src/node.h', /(define (NODE_EXTERN|NODE_MODULE_EXPORT)) __declspec\(dllexport\)/, '$1'); await compiler.replaceInFileAsync('src/node_api.h', /(define (NAPI_EXTERN|NAPI_MODULE_EXPORT)) __declspec\(dllexport\)/, '$1'); await compiler.replaceInFileAsync('src/node_api.h', /__declspec\(dllexport,\s*/g, '__declspec('); await compiler.replaceInFileAsync('src/js_native_api.h', /(define NAPI_EXTERN) __declspec\(dllexport\)/, '$1'); await compiler.replaceInFileAsync('common.gypi', /'BUILDING_(V8|UV)_SHARED=1',/g, ''); await compiler.setFileContentsAsync('deps/zlib/win32/zlib.def', 'EXPORTS'); await compiler.replaceInFileAsync(v8gyp, /'defines':\s*\["BUILDING_V8_BASE_SHARED"\],/g, ''); var data = await compiler.readFileAsync('node.gyp'); data = data.contents.toString(); data = data.replace(/('use_openssl_def%?':) 1,/, "$1 0,"); data = data.replace(/'\/WHOLEARCHIVE:[^']+',/g, ''); data = data.replace(/'-Wl,--whole-archive',.*?'-Wl,--no-whole-archive',/s, ''); await compiler.setFileContentsAsync('node.gyp', data); await compiler.replaceInFileAsync('node.gypi', /'force_load%': 'true',/, "'force_load%': 'false',"); return next(); }, // patch build options async (compiler, next) => { var data = await compiler.readFileAsync('common.gypi'); data = data.contents.toString(); // enable SSE2 as base targeted ISA if(buildArch == 'x86' || buildArch == 'ia32') { data = data.replace(/('EnableIntrinsicFunctions':\s*'true',)(\s*)('FavorSizeOrSpeed':)/, "$1$2'EnableEnhancedInstructionSet': '2',$2$3"); data = data.replace(/('cflags': \[)(\s*'-O3')/, "$1 '-msse2',$2"); } // MSVC - disable debug info data = data.replace(/'GenerateDebugInformation': 'true',/, "'GenerateDebugInformation': 'false',\n'AdditionalOptions': ['/emittoolversioninfo:no'],"); await compiler.setFileContentsAsync('common.gypi', data); return next(); }, // strip debug symbols async (compiler, next) => { await compiler.replaceInFileAsync('node.gyp', /('target_name': '<\(node_core_target_name\)',)( 'ldflags': \['-s'\],)?/g, "$1 'ldflags': ['-s'],"); return next(); }, // strip icon async (compiler, next) => { await compiler.replaceInFileAsync('src/res/node.rc', /1 ICON node\.ico/, ''); return next(); }, // fix for NodeJS 12 on MSVC 2019 x86 // note that MSVC 2019 is needed for GFNI support async (compiler, next) => { if(parseFloat(nodeVer) >= 12 && parseFloat(nodeVer) < 13 && buildOs == 'win32' && (buildArch == 'x86' || buildArch == 'ia32')) { // for whatever reason, building Node 12 using 2019 build tools results in a horribly broken executable, but works fine in 2017 // Node's own Windows builds seem to be using 2017 for Node 12.x var data = await compiler.readFileAsync('vcbuild.bat'); data = data.contents.toString(); data = data.replace('GYP_MSVS_VERSION=2019', 'GYP_MSVS_VERSION=2017'); // seems to be required, even if no MSI is built data = data.replace('PLATFORM_TOOLSET=v142', 'PLATFORM_TOOLSET=v141'); await compiler.setFileContentsAsync('vcbuild.bat', data); } return next(); }, // if '-m32' flag should be stripped async (compiler, next) => { if(stripM32) await compiler.replaceInFileAsync(parseFloat(nodeVer) >= 12 ? 'tools/v8_gypfiles/toolchain.gypi' : 'deps/v8/gypfiles/toolchain.gypi', /'-m32'/, ''); return next(); }, // set _third_party_main async (compiler, next) => { return new Promise((resolve, reject) => { b.bundle(async (err, buf) => { if(err) return reject(err); // patch require line buf = buf.toString().replace(/require\('[^'"]*\/([0-9a-z_]+)\.node'\)/g, "process.binding('$1')"); // for MD5 check buf = buf.replace(/\/\*{{!include_in_executable!([^]*?)}}\*\//g, '$1'); await compiler.replaceInFileAsync('lib/_third_party_main.js', /^/, buf); resolve(); }); }); } ], }).then(() => { console.log('done'); fs.unlinkSync('../bin/help.json'); // append MD5 hash for self-check let binary = 'parpar'; if(buildOs == 'win32') binary += '.exe'; const md5 = require('crypto').createHash('md5').update(fs.readFileSync(binary)).digest(); fs.appendFileSync(binary, Buffer.concat([Buffer.from('\0<!parpar#md5~>='), md5])); // paxmark -m parpar // xz -9e -z --x86 --lzma2 -c parpar > parpar-v0.4.0-linux-static-x64.xz });