UNPKG

emscripten-build

Version:

Build a C++ WebAssembly program inside your Node.js packaging workflow.

1,503 lines (1,208 loc) 49.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var emsdk = require('emscripten-sdk-npm'); var path = require('path'); var getInstalledPathCJS = require('get-installed-path'); var os = require('os'); var fs = require('fs'); var which = require('which'); var resolvePath = require('resolve-path'); var mergeWith = require('lodash.mergewith'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n['default'] = e; return Object.freeze(n); } var emsdk__default = /*#__PURE__*/_interopDefaultLegacy(emsdk); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var getInstalledPathCJS__default = /*#__PURE__*/_interopDefaultLegacy(getInstalledPathCJS); var os__default = /*#__PURE__*/_interopDefaultLegacy(os); var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var which__default = /*#__PURE__*/_interopDefaultLegacy(which); var resolvePath__default = /*#__PURE__*/_interopDefaultLegacy(resolvePath); var mergeWith__default = /*#__PURE__*/_interopDefaultLegacy(mergeWith); // activate.js let alwaysUpdate = false, neverUpdate = false; let hasUpdated = false; let _active = null; async function InstallEmSDK(version = 'latest') { // Retrieve the latest tags from git. // Never update if specified, otherwise update once per runtime // unless alwaysUpdate is true. if (!neverUpdate && (alwaysUpdate || !hasUpdated)) { hasUpdated = true; await emsdk__default['default'].checkout(); await emsdk__default['default'].update(); } // Check if the requested EMSDK version is currently on disk. Only // one version is "installed" at a time, and no other versions are cached. if (!emsdk__default['default'].getInstalled(version)) { await emsdk__default['default'].install(version, true); // Also activate upon install, as this writes files to set up // the environment scripts. // // Activation is only necessary upon install. Presuming the environment // scripts aren't modified by user, subsequent calls need only // invoke the `emsdk_env` script -- see emsdk.run(). await emsdk__default['default'].activate(version); } } async function ActivateEmSDK(version = 'latest') { if (_active === version && !alwaysUpdate) return; // Update, install, and activate if the requested version is not on-disk. await InstallEmSDK(version); _active = version; } /** * Always check server for Emscripten SDK updates for the current runtime session. */ function ForceEmSDKUpdates() { alwaysUpdate = true; neverUpdate = false; } /** * Never check server for Emscripten SDK updates for the current runtime session. */ function DisableEmSDKUpdates() { alwaysUpdate = false; neverUpdate = true; } /** * Resets forcing/disabling of Emscripten SDK updates. */ function ResetEmSDKUpdates() { alwaysUpdate = false; neverUpdate = false; } class Bootstrap { constructor(workingSettings) { this.settings = workingSettings; this.configCommand = null; this.configSubCommand = null; this.makeCommand = null; this.makeSubCommand = null; } //////////////////////////////////////////////////////////////////////// // Config validation //////////////////////////////////////////////////////////////////////// _validateEmsdkSettings() { if (!('emsdkVersion' in this.settings) || !this.settings.emsdkVersion) this.settings.emsdkVersion = 'latest'; } _validateDefinitionSettings(stepSettings) { // "macros" is an alias for "definitions" if ('macros' in stepSettings) { if ('definitions' in stepSettings) throw new RangeError('"macros" and "definitions" cannot both exist in your selected settings.'); // Mutate the settings object and reassign "macros" to "definitions" delete Object.assign(stepSettings, {['definitions']: stepSettings['macros'] })['macros']; } if (!('definitions' in stepSettings)) stepSettings.definitions = []; // Convert objects to [ [ key, val ], [ key, val ], ... ] if (stepSettings.definitions instanceof Map) stepSettings.definitions = [...stepSettings.definitions.entries()]; else if (!Array.isArray(stepSettings.definitions)) { if (typeof stepSettings.definitions !== 'object') throw new RangeError(`Invalid "definitions" in your selected settings: ${stepSettings.definitions}`); stepSettings.definitions = Object.entries(stepSettings.definitions); } // Enforce two-element array members stepSettings.definitions = stepSettings.definitions.map(elem => { let result; // Presume that non-array elements are macros without a value if (!Array.isArray(elem)) result = [ elem, null ]; switch (elem.length) { case 0: throw new RangeError('"definitions" in your selected settings includes an empty element.'); case 1: // Presume this is a macro without a value result = elem.concat(null); break; case 2: result = elem; break; default: throw new RangeError(`"definitions" in your selected settings contains an element with greater than two values: ${elem}`); } // Check invalid definition keys if (!(typeof result[0] === 'string')) throw new RangeError(`"definitions" has an invalid key, must be string: ${result[0]}`); // Mutate element key tby trimming result[0] = result[0].trim(); if (!result[0]) throw new RangeError(`"definitions" has an empty key, must be non-empty after trimming: ${result}`); // Check invalid value objects if (typeof result[1] === 'object' && result[1] !== null) { // Presume this is a macro without a value if (!Object.keys(result[1]).length) result[1] = null; else if ('type' in result[1] && !('value' in result[1])) throw new RangeError(`definitions has a value where "type" is specified but "value" is not: ${result[0]}, ${result[1]}`); } return result; }); } //////////////////////////////////////////////////////////////////////// // Implementations //////////////////////////////////////////////////////////////////////// async _configure() { throw new Error('Bootstrap::_configure() not implemented by the subclass.') } async _build() { throw new Error('Bootstrap::_build() not implemented by the subclass.') } async _clean() { throw new Error('Bootstrap::_clean() not implemented by the subclass.') } async _install() { throw new Error('Bootstrap::_install() not implemented by the subclass.') } async _reconfigure() { await this._clean(); await this._configure(); } async _rebuild() { await this._clean(); await this._build(); } async _compile() { try { await this._build(); } catch (e) { console.log("Build has been failed, trying to do a full rebuild."); await this._rebuild(); } } //////////////////////////////////////////////////////////////////////// // Binding Helpers //////////////////////////////////////////////////////////////////////// async __preCommand() { // emsdkVersion defaults to 'latest' and is guaranteed to be // in this.settings await ActivateEmSDK(this.settings.emsdkVersion); } async _bindCommand(impl, ...args) { await this.__preCommand(); await impl.call(this, ...args); return this; } async _bindConfigCommand(impl, ...args) { throw new Error("Bootstrap::_bindConfigCommand not implemented by the subclass!") } async _bindMakeCommand(impl, ...args) { throw new Error("Bootstrap::_bindMakeCommand not implemented by the subclass!") } async _bindConfigMakeCommand(impl, ...args) { throw new Error("Bootstrap::_bindConfigMakeCommand not implemented by the subclass!") } //////////////////////////////////////////////////////////////////////// // Bindings //////////////////////////////////////////////////////////////////////// // Must be implemented by subclass async configure() { return this._bindConfigCommand(this._configure); } // Must be implemented by subclass async build() { return this._bindMakeCommand(this._build); } // Alias for build. Subclasses should not implement this. async make() { return this.build(); } // Must be implemented by subclass async clean() { return this._bindMakeCommand(this._clean); } // Must be implemented by subclass async install() { return this._bindMakeCommand(this._install); } // Subclasses should not define these meta-commands. async reconfigure() { return this._bindConfigCommand(this._reconfigure); } async rebuild() { return this._bindConfigMakeCommand(this._rebuild); } async compile() { return this._bindConfigMakeCommand(this._compile); } // Special command available to all bootstrap chains. // Subclasses should not define this. async run(command, args, opts = {}) { await this.__preCommand(); await emsdk__default['default'].run(command, args, opts); return this; } } // environment.js const getInstalledPath = getInstalledPathCJS__default['default'].getInstalledPath; async function _tryImport(packageName) { try { let { default: mod } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(packageName)); }); return mod; } catch (e) { // Presume package is not installed console.error(`The ${packageName} package is not installed!`); throw e; } } //////////////////////////////////////////////////////////////////////// // CMake //////////////////////////////////////////////////////////////////////// let _CMakeExists = false; let cMakeCommand = 'cmake'; async function checkCMakeInstalled() { if (_CMakeExists) return true; // Check if CMake is installed let cmake; try { cmake = await _tryImport('@devappd/cmake-binaries'); } catch (e) { // doesn't exist, try which try { cmakeCommand = await which__default['default']('cmake'); _CMakeExists = true; return true; } catch (e) { // presume not in PATH _CMakeExists = false; return false; } } // Check node_modules if (!cmake.exists()) { // Attempt to install cmake.install(true); if (!cmake.exists()) throw new Error('CMake was not found! Try running ' + '`npm explore @devappd/cmake-binaries -- npm run install -- --force`'); } // While we're here, populate the CMake command cMakeCommand = cmake.getCommand(); // If successful, persist this check per runtime session _CMakeExists = true; return true; } //////////////////////////////////////////////////////////////////////// // Ninja //////////////////////////////////////////////////////////////////////// let _NinjaExists = false; let ninjaCommand = 'ninja'; async function checkNinjaInstalled() { if (_NinjaExists) return true; // Check if ninja is in path try { ninjaCommand = await which__default['default']('ninja'); _NinjaExists = true; return true; } catch (e) { // fall through, presume not in PATH } // No results? Rely on `ninja-binaries` package let ninjaPath; try { // Prefer local node_modules ninjaPath = await getInstalledPath('@devappd/ninja-binaries-npm', { local: true }); } catch (e) { try { // Try global node_modules ninjaPath = await getInstalledPath('@devappd/ninja-binaries-npm'); } catch (e) { throw new Error('ninja was not found! To resolve, you may install `npm install -g @devappd/ninja-binaries-npm`.') } } if (os__default['default'].platform() === 'win32') ninjaPath = path__default['default'].join(ninjaPath, 'binaries', 'ninja-win.exe'); else if (os__default['default'].platform() === 'darwin') ninjaPath = path__default['default'].join(ninjaPath, 'binaries', 'ninja-mac'); else if (os__default['default'].platform() === 'linux' || os__default['default'].platform() === 'cygwin') ninjaPath = path__default['default'].join(ninjaPath, 'binaries', 'ninja-linux'); else throw new Error(`ninja was not found for your platform (${os__default['default'].platform()}!`); if (!fs__default['default'].existsSync(ninjaPath)) throw new Error('ninja was not found!'); // While we're here, populate the ninja command ninjaCommand = ninjaPath; // If successful, persist this check per runtime session _NinjaExists = true; } //////////////////////////////////////////////////////////////////////// // Make //////////////////////////////////////////////////////////////////////// let _MakeExists = false; let makeCommand = 'make'; async function checkMakeInstalled() { if (_MakeExists) return true; // Check if Make is in path try { makeCommand = await which__default['default']('make'); _MakeExists = true; return true; } catch (e) { // fall through, presume not in PATH } // If we're Windows, also check for mingw32-make try { makeCommand = await which__default['default']('mingw32-make'); _MakeExists = true; return true; } catch (e) { // fall through, presume not in PATH } throw new Error('Make was not found!'); } // https://github.com/tapjs/libtap/pull/21/files function MainScript(defaultName) { if (typeof repl !== 'undefined' || '_eval' in process) { return defaultName } return process.argv[1] || defaultName } function MainModuleDir() { let mainScript = MainScript('.'); if (mainScript === '.') return path__default['default'].resolve('.'); else return path__default['default'].resolve(path__default['default'].dirname(mainScript)); } // https://stackoverflow.com/a/53530146 function IsDir(path) { try { var stat = fs__default['default'].lstatSync(path); return stat.isDirectory(); } catch (e) { // lstatSync throws an error if path doesn't exist return false; } } function IsFile(path) { try { var stat = fs__default['default'].lstatSync(path); return stat.isFile(); } catch (e) { // lstatSync throws an error if path doesn't exist return false; } } function TryResolvePath(relativePath, rootPath) { if (!rootPath) return path__default['default'].resolve(relativePath); try { return resolvePath__default['default'](rootPath, relativePath); } catch (e) { // Malicious or invalid. Just pass the path as-is. return relativePath; } } class CMake extends Bootstrap { constructor(workingSettings) { super(workingSettings); this.configCommand = 'emcmake'; this.configSubCommand = 'cmake'; this.__validateSettings(); } //////////////////////////////////////////////////////////////////////// // Config validation //////////////////////////////////////////////////////////////////////// __validateSettings() { this.__validateConfigureSettings(); this.__validateBuildSettings(); this.__validateCleanSettings(); this.__validateInstallSettings(); this._validateEmsdkSettings(); } __validateConfigureSettings() { if (!('configure' in this.settings) || !('path' in this.settings.configure)) throw new RangeError('Configure settings must have configure.path set to your source directory (which contains CMakeLists.txt).'); else this.settings.configure.path = TryResolvePath(this.settings.configure.path, this.settings.configPath); if (!this.settings.configure.generator) this.settings.configure.generator = 'Ninja'; if (!this.settings.configure.type) this.settings.configure.type = 'Release'; this._validateDefinitionSettings(this.settings.configure); if (!this.settings.configure.arguments) this.settings.configure.arguments = []; else if (!Array.isArray(this.settings.configure.arguments)) this.settings.configure.arguments = [this.settings.configure.arguments]; } __validateMakeSettings(stepKey, targetName = null, defaultPath = null) { if (!(stepKey in this.settings)) this.settings[stepKey] = {}; if (defaultPath && !this.settings[stepKey].path) this.settings[stepKey].path = defaultPath; if (this.settings[stepKey].path) this.settings[stepKey].path = TryResolvePath(this.settings[stepKey].path, this.settings.configPath); if (!this.settings[stepKey].target) this.settings[stepKey].target = targetName; if (!this.settings[stepKey].arguments) this.settings[stepKey].arguments = []; else if (!Array.isArray(this.settings[stepKey].arguments)) this.settings[stepKey].arguments = [this.settings[stepKey].arguments]; } __validateBuildSettings() { this.__validateMakeSettings('build', null, './build'); } __validateCleanSettings() { this.__validateMakeSettings('clean', 'clean'); } __validateInstallSettings() { this.__validateMakeSettings('install', 'install', './dist'); if (this.settings.install.binaryPath) this.settings.install.binaryPath = TryResolvePath(this.settings.install.binaryPath, this.settings.configPath); if (this.settings.install.libraryPath) this.settings.install.libraryPath = TryResolvePath(this.settings.install.libraryPath, this.settings.configPath); if (this.settings.install.includePath) this.settings.install.includePath = TryResolvePath(this.settings.install.includePath, this.settings.configPath); } //////////////////////////////////////////////////////////////////////// // Implementation helpers //////////////////////////////////////////////////////////////////////// async __ensureConfigure() { if(!fs__default['default'].existsSync(path__default['default'].join(this.settings.build.path, "CMakeCache.txt"))) { await this._bindConfigCommand(this._configure); // make sure to reload the make variables after configuring await this.__determineMake(!!this.settings.configure.generator); } } __buildConfigureArguments() { let args = [ `"${this.settings.configure.path}"`, '-G', this.settings.configure.generator, `-DCMAKE_BUILD_TYPE="${this.settings.configure.type}"`, `-DCMAKE_INSTALL_PREFIX="${this.settings.install.path}"`, ]; if (this.settings.install.binaryPath) args.push(`-DCMAKE_INSTALL_BINDIR="${this.settings.install.binaryPath}"`); if (this.settings.install.libraryPath) args.push(`-DCMAKE_INSTALL_LIBDIR="${this.settings.install.libraryPath}"`); if (this.settings.install.includePath) args.push(`-DCMAKE_INSTALL_INCLUDEDIR="${this.settings.install.includePath}"`); args.push(...this.settings.configure.arguments); return args; } __buildDefinitions(definitions) { const prefix = '-D'; let args = []; for (let [key, val] of definitions) { let argString = `${prefix}${key}`; if (typeof val === 'object' && val !== null) { if ( 'type' in val) argString = argString.concat(`:${val.type}`); // if this object conforms to our format ({type:'...', value: '...'}), then convert to string // else, stringify the entire object on routine end if ('value' in val) val = val.value; } if (typeof val === 'string') val = val.replace('"', '\"'); // interpret "true"/"false" as "ON"/"OFF" if (val === true) val = "ON"; else if (val === false) val = "OFF"; else if (val === null) val = ""; // stringify everything else args.push(`"${argString.concat(`=${val}`)}"`); } return args; } // Populate this.makeCommand async __determineMake(fromCache = false) { // Populate this.makeCommand instead of this.makeSubCommand // because we call these executables directly // instead of thru an emsdk script. if (fromCache && !IsDir(this.settings.build.path)) // Nothing to do return; let generator = this.settings.configure.generator.toLowerCase(); // Ninja let hasNinja = fromCache ? fs__default['default'].existsSync(path__default['default'].join(this.settings.build.path, 'build.ninja')) : generator === 'ninja'; if (hasNinja) { await checkNinjaInstalled(); this.makeCommand = ninjaCommand; this.makeSubCommand = null; return; } // Makefiles // test for 'Unix Makefiles', 'MinGW Makefiles', etc. let hasMake = fromCache ? fs__default['default'].existsSync(path__default['default'].join(this.settings.build.path, 'Makefile')) : generator.includes('makefiles'); if (hasMake) { await checkMakeInstalled(); this.makeCommand = makeCommand; this.makeSubCommand = null; return; } } //////////////////////////////////////////////////////////////////////// // Implementations //////////////////////////////////////////////////////////////////////// async _configure() { await this.__determineMake(); let defs = this.__buildDefinitions(this.settings.configure.definitions); let args = this.__buildConfigureArguments(); if (this.makeCommand) args = args.concat([`-DCMAKE_MAKE_PROGRAM="${this.makeCommand}"`]); await emsdk__default['default'].run(this.configCommand, [`"${this.configSubCommand}"`, ...args, ...defs], {cwd: this.settings.build.path, shell: (process.platform === 'win32')} ); } async __make(stepSettings) { // Make sure everything's configured before building. await this.__ensureConfigure(); // build args let args; if (stepSettings.target) args = [stepSettings.target, ...stepSettings.arguments]; else args = [...stepSettings.arguments]; // note we do not use this.makeSubCommand because // we call the makeCommand directly instead of thru // an emsdk script. // // Make is called on the build path specifically. await emsdk__default['default'].run(this.makeCommand, args, {cwd: this.settings.build.path, shell: (process.platform === 'win32')} ); } async _build() { await this.__make(this.settings.build); } async _clean() { await this.__make(this.settings.clean); } async _install() { await this.__make(this.settings.install); } //////////////////////////////////////////////////////////////////////// // Binding Helpers //////////////////////////////////////////////////////////////////////// __ensureBuildDirExists() { fs__default['default'].mkdirSync(this.settings.build.path, { recursive: true }); } async _bindConfigCommand(impl, ...args) { // Throws error if CMake is not installed. await checkCMakeInstalled(); this.configSubCommand = cMakeCommand; this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } async _bindMakeCommand(impl, ...args) { // Throws error if build command is not found. await this.__determineMake(!!this.settings.configure.generator); this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } async _bindConfigMakeCommand(impl, ...args) { // Check both config and build commands await checkCMakeInstalled(); this.configSubCommand = cMakeCommand; await this.__determineMake(!!this.settings.configure.generator); this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } } class Make extends Bootstrap { constructor(workingSettings) { super(workingSettings); this.makeCommand = 'emmake'; this.makeSubCommand = makeCommand; this.__validateSettings(); } //////////////////////////////////////////////////////////////////////// // Config validation //////////////////////////////////////////////////////////////////////// __validateSettings() { this.__validateBuildSettings(); this.__validateCleanSettings(); this.__validateInstallSettings(); this._validateEmsdkSettings(); } __validateMakeSettings(stepKey, targetName = null) { if (!(stepKey in this.settings)) this.settings[stepKey] = {}; if (!('target' in this.settings[stepKey])) this.settings[stepKey].target = targetName; this._validateDefinitionSettings(this.settings[stepKey]); if (!this.settings[stepKey].arguments) this.settings[stepKey].arguments = []; else if (!Array.isArray(this.settings[stepKey].arguments)) this.settings[stepKey].arguments = [this.settings[stepKey].arguments]; } __validateBuildSettings() { if (!('build' in this.settings) || !('path' in this.settings.build)) throw new RangeError('Build settings must have build.path set to your source directory (which contains Makefile).'); else this.settings.build.path = TryResolvePath(this.settings.build.path, this.settings.configPath); this.__validateMakeSettings('build', null); } __validateCleanSettings() { this.__validateMakeSettings('clean', 'clean'); } __validateInstallSettings() { this.__validateMakeSettings('install', 'install'); } //////////////////////////////////////////////////////////////////////// // Implementation Helpers //////////////////////////////////////////////////////////////////////// __buildDefinitions(definitions) { const prefix = ''; let args = []; for (let [key, val] of definitions) { let argString = `${prefix}${key}`; if (typeof val === 'object' && val !== null) { // if this object conforms to our format ({type:'...', value: '...'}), then convert to string // else, stringify the entire object on routine end if ('value' in val) val = val.value; } if (typeof val === 'string') val = val.replace('"', '\"'); // interpret "true"/"false" as "1"/"0" if (val === true) val = "1"; else if (val === false) val = "0"; // "null" means push the key without a definition if (val === null) args.push(`"${argString}"`); // stringify everything else else args.push(`"${argString.concat(`=${val}`)}"`); } return args; } //////////////////////////////////////////////////////////////////////// // Implementations //////////////////////////////////////////////////////////////////////// async _configure() { // Nothing to do, make is not configurable } async __make(stepSettings) { // build args let defs = this.__buildDefinitions(stepSettings.definitions); let args; if (stepSettings.target) args = [this.makeSubCommand, stepSettings.target, ...stepSettings.arguments, ...defs]; else args = [this.makeSubCommand, ...stepSettings.arguments, ...defs]; // Make is called on the "build" path specifically. await emsdk__default['default'].run(this.makeCommand, args, {cwd: this.settings.build.path, shell: (process.platform === 'win32')} ); } async _build() { await this.__make(this.settings.build); } async _clean() { await this.__make(this.settings.clean); } async _install() { await this.__make(this.settings.install); } //////////////////////////////////////////////////////////////////////// // Binding Helpers //////////////////////////////////////////////////////////////////////// async _bindConfigCommand(impl, ...args) { // Nothing to do return this._bindCommand(impl, ...args); } async _bindMakeCommand(impl, ...args) { // Throws error if Make is not installed. await checkMakeInstalled(); this.makeSubCommand = makeCommand; return this._bindCommand(impl, ...args); } async _bindConfigMakeCommand(impl, ...args) { // Throws error if Make is not installed. await checkMakeInstalled(); this.makeSubCommand = makeCommand; return this._bindCommand(impl, ...args); } } class Autotools extends Bootstrap { constructor(workingSettings) { super(workingSettings); this.configCommand = 'emconfigure'; this.configSubCommand = 'configure'; this.makeCommand = 'emmake'; this.makeSubCommand = makeCommand; this.__validateSettings(); } //////////////////////////////////////////////////////////////////////// // Config validation //////////////////////////////////////////////////////////////////////// __validateSettings() { this.__validateConfigureSettings(); this.__validateBuildSettings(); this.__validateCleanSettings(); this.__validateInstallSettings(); this._validateEmsdkSettings(); } __validateConfigureSettings() { if (!('configure' in this.settings) || !('path' in this.settings.configure)) throw new RangeError('Configure settings must have configure.path set to your source directory (which contains ./configure).'); else this.settings.configure.path = TryResolvePath(this.settings.configure.path, this.settings.configPath); if (!this.settings.configure.arguments) this.settings.configure.arguments = []; else if (!Array.isArray(this.settings.configure.arguments)) this.settings.configure.arguments = [this.settings.configure.arguments]; } __validateMakeSettings(stepKey, targetName = null, defaultPath = null) { if (!(stepKey in this.settings)) this.settings[stepKey] = {}; if (defaultPath && !this.settings[stepKey].path) this.settings[stepKey].path = defaultPath; if (this.settings[stepKey].path) this.settings[stepKey].path = TryResolvePath(this.settings[stepKey].path, this.settings.configPath); if (!this.settings[stepKey].target) this.settings[stepKey].target = targetName; if (!this.settings[stepKey].arguments) this.settings[stepKey].arguments = []; else if (!Array.isArray(this.settings[stepKey].arguments)) this.settings[stepKey].arguments = [this.settings[stepKey].arguments]; } __validateBuildSettings() { this.__validateMakeSettings('build', null, './build'); } __validateCleanSettings() { this.__validateMakeSettings('clean', 'clean'); } __validateInstallSettings() { this.__validateMakeSettings('install', 'install', './dist'); if (this.settings.install.binaryPath) this.settings.install.binaryPath = TryResolvePath(this.settings.install.binaryPath, this.settings.configPath); if (this.settings.install.libraryPath) this.settings.install.libraryPath = TryResolvePath(this.settings.install.libraryPath, this.settings.configPath); if (this.settings.install.includePath) this.settings.install.includePath = TryResolvePath(this.settings.install.includePath, this.settings.configPath); } //////////////////////////////////////////////////////////////////////// // Implementation Helpers //////////////////////////////////////////////////////////////////////// async __ensureConfigure() { if(!fs__default['default'].existsSync(path__default['default'].join(this.settings.build.path, "Makefile"))) await this._bindConfigCommand(this._configure); } __buildConfigureArguments() { let args = [ `--prefix="${this.settings.install.path}"`, ]; if (this.settings.install.binaryPath) args.push(`--bindir="${this.settings.install.binaryPath}"`); if (this.settings.install.libraryPath) args.push(`--libdir="${this.settings.install.libraryPath}"`); if (this.settings.install.includePath) args.push(`--includedir="${this.settings.install.includePath}"`); args.push(...this.settings.configure.arguments); return args; } //////////////////////////////////////////////////////////////////////// // Implementations //////////////////////////////////////////////////////////////////////// async _configure() { let args = this.__buildConfigureArguments(); let configSubCommand = path__default['default'].join(this.settings.configure.path, this.configSubCommand); await emsdk__default['default'].run(this.configCommand, [`"${configSubCommand}"`, ...args], {cwd: this.settings.build.path, shell: (process.platform === 'win32')} ); } async __make(stepSettings) { // Make sure everything's configured before building. await this.__ensureConfigure(); // build args let args; if (stepSettings.target) args = [this.makeSubCommand, stepSettings.target, ...stepSettings.arguments]; else args = [this.makeSubCommand, ...stepSettings.arguments]; // Make is invoked on the "build" path specifically. await emsdk__default['default'].run(this.makeCommand, args, {cwd: this.settings.build.path, shell: (process.platform === 'win32')} ); } async _build() { await this.__make(this.settings.build); } async _clean() { await this.__make(this.settings.clean); } async _install() { await this.__make(this.settings.install); } //////////////////////////////////////////////////////////////////////// // Binding Helpers //////////////////////////////////////////////////////////////////////// __ensureBuildDirExists() { fs__default['default'].mkdirSync(this.settings.build.path, { recursive: true }); } async _bindConfigCommand(impl, ...args) { this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } async _bindMakeCommand(impl, ...args) { // Throws error if Make is not installed. await checkMakeInstalled(); this.makeSubCommand = makeCommand; this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } async _bindConfigMakeCommand(impl, ...args) { // Throws error if Make is not installed. await checkMakeInstalled(); this.makeSubCommand = makeCommand; this.__ensureBuildDirExists(); return this._bindCommand(impl, ...args); } } function _constructMasterConfig(buildFilePath) { // buildFilePath is assumed to be an exact path to CMakeLists.txt/configure/Makefile let testPath = buildFilePath.toLowerCase(); let config = {}; if (testPath.includes('cmake')) config.type = 'cmake'; else if (testPath.includes('configure')) config.type = 'autotools'; else if (testPath.includes('makefile')) config.type = 'make'; else throw new Error(`Unknown build file type: ${buildFilePath}`); let buildFileDir = path__default['default'].dirname(buildFilePath); switch (config.type) { case 'make': config.build = { path: buildFileDir }; break; default: config.configure = { path: buildFileDir }; break; } // Mimic traditional build tools where CWD stands for the staging area // where builds are cached. config.configPath = process.cwd(); // We use the _retrieved key to mark for retrieval later return {_retrieved: config, default: '_retrieved'}; } async function _retrieveMasterConfigFile(filePath, configLocator) { // "default" refers to ECMAScript import property let masterConfig = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(filePath)); })).default; masterConfig['_configPath'] = path__default['default'].dirname(filePath); // Test: Is configLocator a valid key? if (typeof masterConfig[configLocator] === 'object') masterConfig.default = configLocator; // mark for retrieval later else if (!('default' in masterConfig)) { // If there's only one config inside, set it as default let configKeys = Object.keys(masterConfig).filter(key => masterConfig[key] instanceof Object); if (configKeys.length === 1) masterConfig.default = configKeys[0]; } return masterConfig; } async function _getMasterConfig(configLocator) { // configLocator can be one of: // 1. The name of a config listed in `emscripten.settings.js` // 2. A path to a folder that contains `emscripten.settings.js`, `CMakeLists.txt`, `./configure`, or `Makefile` // 3. A path directly to one of the above files // 4. A config object // If null, don't search if (configLocator === null) return {}; const mainScriptDir = MainModuleDir(); let dirSearchSet; let testPath = path__default['default'].resolve(configLocator); if (IsDir(testPath)) // if testPath is a valid dir, then search only this path // When configLocator is a dir, we only resolve it to CWD. dirSearchSet = [testPath]; else // Search both module root (an approximation) and CWD // because we can't reliably get the main module. We do this to // research the config/build files in both locations. dirSearchSet = [mainScriptDir, process.cwd()]; for (let dirPath of dirSearchSet) { let locatorTestPath = TryResolvePath(configLocator, dirPath); // Define our file search let fileSearchSet; if (IsFile(locatorTestPath)) fileSearchSet = [locatorTestPath]; else fileSearchSet = ['emscripten.settings.js', 'CMakeLists.txt', 'configure', 'Makefile', 'makefile'] .map(val => path__default['default'].join(dirPath, val)); // Search for build/config files for (let filePath of fileSearchSet) { if (fs__default['default'].existsSync(filePath)) { if (filePath.includes('emscripten.settings.js')) return await _retrieveMasterConfigFile(filePath, configLocator); else return _constructMasterConfig(filePath); } } } throw new RangeError(`Could not find config from locator: ${configLocator}`); } /** * Get config to pass to Bootstrap object. * @param {string} [configLocator] - A name to a config listed in `emscripten.settings.js`, or a path to a folder containing CMake/Autotools/Makefile configs, or a configuration object. * @param {object} [settingsFragment] - An object fragment to merge to the selected config. Not valid if `configLocator` is an object. */ async function GetWorkingConfig(a, b) { // Master config format: // // { // emsdk: "/path/to/emsdk", // emsdkVersion: "latest", // default: "named_config", // named_config: { // type: "cmake", // /* ... */ // }, // other_named_config: { /* ... */ } // } // // Our goal is to get the correct base config (e.g., above "named_config") // and merge any changes from settingsFragment. // Parse arguments let configLocator = null; let settingsFragment = {}; let args = Array.from(arguments).filter(el => (typeof el !== 'undefined' && el !== null)); switch (args.length) { case 0: // No args means search <cwd> for config/build file configLocator = process.cwd(); break; case 1: if (typeof args[0] === 'string') configLocator = args[0] || (configLocator || process.cwd()); else // Don't default configLocator to process.cwd(); the intent // is to use settingsFragment as the sole config. settingsFragment = args[0] || settingsFragment; break; case 2: default: if (typeof args[0] === 'string') { // Don't default to process.cwd(); the intent is to explicitly // set configLocator. If null, then settingsFragment shall be // the sole config. configLocator = args[0] || configLocator; settingsFragment = args[1] || settingsFragment; } else // second arg is invalid, warn user throw new RangeError('Second argument (`settingsFragment`) is invalid if the first argument is also an object.'); break; } // Get configs to process. Returns immediately if configLocator === null let masterConfig = await _getMasterConfig(configLocator); let workingSettings = {}; // If EMSDK variables are top-level, make note of those then remove let emsdkPath = null; let emsdkVersion = null; let configPath = process.cwd(); if ('emsdk' in masterConfig) { emsdkPath = masterConfig.emsdk; delete masterConfig.emsdk; } if ('emsdkVersion' in masterConfig) { emsdkVersion = masterConfig.emsdkVersion; delete masterConfig.emsdkVersion; } if ('_configPath' in masterConfig) { configPath = masterConfig._configPath; delete masterConfig._configPath; } // At this point, we expect masterConfig.default to be populated because: // * either the author set it, or // * we set it ourselves when constructing a config from a build file, or // * we set it ourselves when we found the configLocator as a named key in the masterConfig, or // * we set it ourselves when there's only one config inside the masterConfig if ('default' in masterConfig) { if (typeof masterConfig.default === 'string') { if (masterConfig.default in masterConfig) workingSettings = masterConfig[masterConfig.default]; else throw new RangeError(`Requested base config "${masterConfig.default}" was not found in master config.`); } delete masterConfig.default; } // Else, determine finalConfig from keys let keys = Object.keys(masterConfig); if (!keys.length && (typeof settingsFragment === 'object')) { // If the master config is empty, but the user specified a config fragment, // then the fragment becomes our working config. workingSettings = settingsFragment; } if (!workingSettings || !Object.keys(workingSettings).length) throw new RangeError('Cannot determine base config to use. Specify "configLocator" parameter or set "default" name in the master config.'); // Merge settingsFragment into workingSettings if (!workingSettings === settingsFragment) mergeWith__default['default'](workingSettings, settingsFragment, function(objValue, srcValue) { // Overwrite arrays, don't merge them if (Array.isArray(objValue)) return srcValue; // Else, merge by default behavior (recursive for objects, assignment for other types) }); // A config object must have a build type. // This also catches empty configs. if (!('type' in workingSettings)) throw new RangeError(`Working config does not have a valid build type. Specify "type": <"make"|"autotools"|"cmake"> in the base config.`); // Move EMSDK variables to working config, unless the working config already has them if (emsdkPath && !('emsdk' in workingSettings)) workingSettings.emsdk = emsdkPath; if (emsdkVersion && !('emsdkVersion' in workingSettings)) workingSettings.emsdkVersion = emsdkVersion; if (configPath && !('_configPath' in workingSettings)) workingSettings.configPath = configPath; return workingSettings; } async function _getBootstrap(a, b) { let workingSettings = await GetWorkingConfig(a, b); let bootstrap; switch (workingSettings.type.toLowerCase()) { case 'make': bootstrap = new Make(workingSettings); break; case 'autotools': bootstrap = new Autotools(workingSettings); break; case 'cmake': bootstrap = new CMake(workingSettings); break; } return bootstrap; } async function _callAction(actionName, a, b, ...args) { const bootstrap = await _getBootstrap(a, b); return bootstrap[actionName](...args); } /** * Configure the C/C++ project. Invokes CMake, `./configure`, etc. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function configure(a, b) { return _callAction('configure', a, b); } /** * Build the C/C++ project. Also configure the project if necessary. Invokes Make. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function build(a, b) { return _callAction('build', a, b); } /** * Clean the C/C++ project. Invokes the "clean" target on Make. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function clean(a, b) { return _callAction('clean', a, b); } /** * Install the C/C++ project. Invokes the "install" target on Make. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function install(a, b) { return _callAction('install', a, b); } /** * Clean then configure the C/C++ project. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function reconfigure(a, b) { return _callAction('reconfigure', a, b); } /** * Clean, configure, then build the C/C++ project. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function rebuild(a, b) { return _callAction('rebuild', a, b); } /** * Build the C/C++ project. If the build fails, then clean, configure, and rebuild. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function compile(a, b) { return _callAction('compile', a, b); } /** * Install the given EMSDK version as specified in the config. * @param {string} [configLocator] - Path to a settings or build file, or a folder containing the same, or the name of a settings object in `<cwd>/emscripten.settings.js`, or a settings object. Default: `<cwd>` * @param {object} [settingsFragment] - A settings object to overwrite properties in the active settings object. */ async function installSDK(a, b) { const bootstrap = _getBootstrap(a, b); let version = 'latest'; if (('emsdkVersion' in bootstrap.config) && !!bootstrap.config.emsdkVersion) version = bootstrap.config.emsdkVersion; await InstallEmSDK(version); return bootstrap; } /** * Run an arbitrary command within the EMSDK environment. * @param {string} command - The executable name of the command. * @param {string[]} [args=[]] - Command line arguments * @param {object} [opts={}] - Options to pass to child_process.spawn() */ async function run(command, args = [], opts = {}) { await ActivateEmSDK(); return emsdk__default['default'].run(command, args, opts); } exports.build = build; exports.clean = clean; exports.compile = compile; exports.configure = configure; exports.disableEmSDKUpdates = DisableEmSDKUpdates; exports.forceEmSDKUpdates = ForceEmSDKUpdates; exports.install = install; exports.installSDK = installSDK; exports.rebuild = rebuild; exports.reconfigure = reconfigure; exports.resetEmSDKUpdates = ResetEmSDKUpdates; exports.run = run;