From 416cac9940760e4bae76fd02d2e6a792d58f0ac6 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Tue, 13 Mar 2018 08:13:31 +0800 Subject: [PATCH] build: refactor os-specific scripts into different files This patch refactors all the OS-specific configuration scripts into separate files: - `scripts/linux.js` for Linux - `scripts/darwin.js` for macOS - `scripts/freebsd.js` for FreeBSD And put lldb-specific code into `scripts/lldb.js`. The `scripts/configure.js` now runs a `main()` function that calls other functions so the configuration code is now more readable. Also adds comments in the configuration scripts and more console outputs during the installation to help users find issues about their installation. Tested with the following combinations (with `npm install`): - macOS 10.12 + Xcode 9.2 - macOS 10.12 + lldb 5.0 (installed with `brew install --with-lldb --with-toolchain llvm`) - Ubuntu 16.04 + lldb 4.0 (`apt-get install lldb-4.0 liblldb-4.0-dev`) - FreeBSD 11 + lldb40 (comes with lldb40, need to `pkg install llvm40` to install the headers) - FreeBSD 11 + lldb50 (`pkg install llvm50` and relink `/usr/bin/lldb` to `/usr/local/bin/lldb50`) - CentOS 7 + lldb 4.0 from llvm-toolset-7 in the SCL - Fedora 27 + lldb 5.0 has been tested. --- scripts/configure.js | 428 ++++++++++++++----------------------------- scripts/darwin.js | 127 +++++++++++++ scripts/freebsd.js | 89 +++++++++ scripts/linux.js | 136 ++++++++++++++ scripts/lldb.js | 55 ++++++ 5 files changed, 545 insertions(+), 290 deletions(-) create mode 100644 scripts/darwin.js create mode 100644 scripts/freebsd.js create mode 100644 scripts/linux.js create mode 100644 scripts/lldb.js diff --git a/scripts/configure.js b/scripts/configure.js index c742c680..c9c5b582 100644 --- a/scripts/configure.js +++ b/scripts/configure.js @@ -5,318 +5,163 @@ const fs = require('fs'); const path = require('path'); const child_process = require('child_process'); -const buildDir = process.cwd(); - -console.log('Build dir is: ' + buildDir); - -const osName = os.type(); - -var lldbVersion; // Similar to what `llvm-config --version` returns -var lldbInstallDir; // Where the lldb installation is, `llvm-config --prefix` - -// Need to determine: -// - What level of lldb we are running. -// - If we need the headers. (Linux may have them installed) -var lldbExe = 'lldb'; - -if (osName === 'Darwin') { - lldbVersion = getDarwinRelease(); - - if (lldbVersion === undefined) { - console.log('Unable to locate lldb binary. llnode installation failed.'); - process.exit(1); - } - - console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - const installedDir = getDarwinInstallDir(); - if (installedDir === undefined) { - const lldbHeadersBranch = lldbVersionToBranch(lldbVersion); - lldbInstallDir = 'lldb-' + lldbVersion; - cloneHeaders(lldbHeadersBranch, lldbInstallDir, buildDir); - fs.writeFileSync('options.gypi', '{}', 'utf-8'); - } else { - lldbInstallDir = installedDir; - setDarwinBuildDir(); - } -} else if (osName === 'Linux') { - lldbExe = getLldbExecutable(); - lldbVersion = getLinuxVersion(lldbExe); - - if (lldbVersion === undefined) { - console.log('Unable to locate lldb binary. llnode installation failed.'); - process.exit(1); - } - - console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - const installedDir = getLinuxInstallDir(lldbVersion); - if (installedDir === undefined) { - const lldbHeadersBranch = lldbVersionToBranch(lldbVersion); - lldbInstallDir = 'lldb-' + lldbVersion; - cloneHeaders(lldbHeadersBranch, lldbInstallDir, buildDir); - fs.writeFileSync('options.gypi', '{}', 'utf-8'); - } else { - lldbInstallDir = installedDir; - } -} else if (osName === 'FreeBSD') { - lldbExe = getLldbExecutable(); - lldbVersion = getFreeBSDVersion(lldbExe); - - if (lldbVersion === undefined) { - console.log('Unable to locate lldb binary. llnode installation failed.'); - process.exit(1); - } - - console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - const installedDir = getFreeBSDInstallDir(lldbVersion); - if (installedDir === undefined) { - // As this is a BSD we know this system is in an improper state - // So we can exit with an error - console.log('The system isn\'t set up correcly.'); - console.log('Try `pkg install llvm39'); - console.log('And `ln -s /usr/local/bin/lldb39 /usr/bin/lldb`'); - process.exit(1); - } else { - lldbInstallDir = installedDir; - } +const lldb = require('./lldb'); + +function main() { + const buildDir = process.cwd(); + console.log('Build dir is: ' + buildDir); + const osName = os.type(); + const installation = configureInstallation(osName, buildDir); + linkHeadersDir(installation.prefix); + const gypDir = getGypDir(buildDir); + linkGyp(gypDir); + writeLlnodeScript(buildDir, installation.executable, osName); + // Exit with success. + process.exit(0); } -// This should be equivalent to `llvm-config --includedir`/lldb -function getLldbHeadersPath(lldbInstallDir) { - return path.join(lldbInstallDir, 'include', 'lldb'); -} - -// Check out source code of the LLDB for headers -// TODO: The llvm project is probably moving to github soon at that point we -// should stop using the mirror. -function cloneHeaders(lldbHeadersBranch, lldbInstallDir, buildDir) { - const lldbHeaders = getLldbHeadersPath(lldbInstallDir); - if (!fs.existsSync(lldbInstallDir)) { - console.log(`Cloning lldb from ${lldbHeadersBranch} to ${lldbInstallDir}`); - child_process.execFileSync('git', - ['clone', '--depth=1', '-b', lldbHeadersBranch, - 'https://github.com/llvm-mirror/lldb.git', lldbInstallDir], - { - cwd: buildDir, - stdio: 'inherit' // show progress - }); - } else { - console.log(`Skip cloning lldb headers because ${lldbHeaders} exists`); - } -} +main(); + +/** + * Get and configure the lldb installation. The returned prefix + * should be linked to ./lldb + * @param {string} osName Name of the OS + * @param {string} buildDir Path of the project directory + * @returns {{executable: string, version: string, prefix: string}} + */ +function configureInstallation(osName, buildDir) { + // Need to determine: + // - What level of lldb we are running. + // - If we need to download the headers. (Linux may have them installed) + let installation; + let prefix; // Similar to what `llvm-config --prefix` returns -// Link to the headers file so we can run gyp_llnode directly and don't need to -// setup parameters to pass it. -console.log(`Linking lldb to installation directory ${lldbInstallDir}`); -try { - fs.unlinkSync('lldb'); -} catch (error) { - // File does not exist, no need to handle. -} -fs.symlinkSync(lldbInstallDir, 'lldb'); - -// npm explore has a different root folder when using -g -// So we are tacking on the extra the additional subfolders -var gypSubDir = 'node-gyp'; -if (process.env.npm_config_global) { - gypSubDir = 'npm/node_modules/node-gyp'; -} - -// npm can be in a different location than the current -// location for global installs so we need find out where the npm is -var npmLocation = child_process.execFileSync('which', ['npm']); -var npmModules = path.join(npmLocation.toString(), '../../lib/node_modules/npm'); - -// Initialize GYP -// We can use the node-gyp that comes with npm. -// We can locate it with npm -g explore npm npm explore node-gyp pwd -// It might have been neater to make node-gyp one of our dependencies -// *but* they don't get installed until after the install step has run. -var gypDir = child_process.execFileSync('npm', - ['-g', 'explore', npmModules, 'npm', 'explore', gypSubDir, 'pwd'], - {cwd: buildDir}).toString().trim(); -child_process.execSync('rm -rf tools'); -fs.mkdirSync('tools'); -console.log(`Linking tools/gyp to ${gypDir}/gyp`); -fs.symlinkSync(`${gypDir}/gyp`, 'tools/gyp'); - -fs.writeFileSync(`${buildDir}/llnode.sh`, scriptText(lldbExe)); - -// Exit with success. -process.exit(0); - -function lldbVersionToBranch(version) { - return 'release_' + version.replace('.', ''); -} - -// On Mac the lldb version string doesn't match the original lldb versions. -function getDarwinRelease() { - var versionFromConfig; - try { - versionFromConfig = child_process.execFileSync('llvm-config', [ - '--version' - ]).toString().trim(); - } catch (err) { - // No llvm-config, try to get the version from xcodebuild - } - if (versionFromConfig !== undefined) { - return versionFromConfig.split('.').slice(0, 2).join('.'); - } - - var xcodeStr; - try { - xcodeStr = child_process.execFileSync('xcodebuild', [ - '-version' - ]).toString(); - } catch (err) { - return undefined; - } - var versionStr = ''; - var splitStr = xcodeStr.split(os.EOL); - for (var str of splitStr) { - if (str.includes('Xcode')) { - versionStr = str.split(' ')[1]; - break; + if (osName === 'Darwin') { + const darwin = require('./darwin'); + installation = darwin.getLldbInstallation(); + prefix = installation.prefix; + if (prefix === undefined) { // Using Xcode installation + prefix = lldb.cloneHeaders(installation.version, buildDir); + } else { // Using custom installation + // Need to configure with the custom prefix + const config = { + variables: { 'lldb_build_dir%': prefix } + }; + writeConfig(config); } - } - // console.log('Xcode version is ' + version_str) - - var version = parseFloat(versionStr); - if (version >= 8.3) { - return '3.9'; - } else if (version > 8.0) { - return '3.8'; + } else if (osName === 'Linux') { + const linux = require('./linux'); + installation = linux.getLldbInstallation(); + if (installation.prefix === undefined) + // Could not find the headers, need to download them + prefix = lldb.cloneHeaders(installation.version, buildDir); + else + prefix = installation.prefix; + + // ./lldb will always be linked to the prefix on Linux + writeConfig({}); + } else if (osName === 'FreeBSD') { + const freebsd = require('./freebsd'); + installation = freebsd.getLldbInstallation(); + prefix = installation.prefix; + // ./lldb will always be linked to the prefix + writeConfig({}); } else { - return '3.4'; + console.log(`Unsupported OS: ${osName}`); + process.exit(1); } -} -function setDarwinBuildDir() { - const prefix = child_process.execFileSync('llvm-config', [ - '--prefix' - ]).toString().trim(); - const options = JSON.stringify({ - variables: { 'lldb_build_dir%': prefix } - }, null, 2); - fs.writeFileSync('options.gypi', options, 'utf-8'); - console.log('Overwriting options.gypi with output from llvm-config:'); - console.log(options); + return { + executable: installation.executable, + version: installation.version, + prefix + }; } -function getDarwinInstallDir() { - var installedDir; +/** + * Link to the headers file so we can run gyp_llnode directly and don't need to + * setup parameters to pass. + * @param {string} lldbInstallDir The destination of the symlink + */ +function linkHeadersDir(lldbInstallDir) { + console.log(`Linking lldb to installation directory ${lldbInstallDir}`); try { - installedDir = child_process.execFileSync('llvm-config', [ - '--prefix' - ]).toString().trim(); - } catch (err) { - // Return undefined, we will download the headers. - } - if (installedDir !== undefined && - fs.existsSync(getLldbHeadersPath(installedDir))) { - return installedDir; + fs.unlinkSync('lldb'); + } catch (error) { + // File does not exist, no need to handle. } - return undefined; + fs.symlinkSync(lldbInstallDir, 'lldb'); } -// Find the 'best' lldb to use. Either: -// - the one specified by the user using npm --lldb_exe=... install llnode -// - the default lldb executable -// - the higest known lldb version -// - the names of future releases are predictable for linux -function getLldbExecutable() { - var lldbExe = process.env.npm_config_lldb_exe; - if (lldbExe === undefined) { - var lldbExeNames = [ - 'lldb', 'lldb-5.0', 'lldb-4.0', - 'lldb-3.9', 'lldb-3.8', 'lldb-3.7', 'lldb-3.6' - ]; - for (var lldbExeVersion of lldbExeNames) { - try { - lldbExe = child_process.execSync('which ' + - lldbExeVersion).toString().trim(); - // If the result starts with '/' `which` found a path. - if (lldbExe.startsWith('/')) { - break; - } - } catch (err) { - // Do nothing - we expect not to find some of these. - } - } - } - return lldbExe; +/** + * Get the path to the GYP installation + * @param {string} buildDir Path of the project directory + */ +function getGypDir(buildDir) { + // npm explore has a different root folder when using -g + // So we are tacking on the extra the additional subfolders + let gypSubDir = 'node-gyp'; + if (process.env.npm_config_global) + gypSubDir = 'npm/node_modules/node-gyp'; + + // npm can be in a different location than the current + // location for global installs so we need find out where the npm is + let npmLocation = child_process.execFileSync('which', ['npm']); + let npmModules = path.join( + npmLocation.toString(), '../../lib/node_modules/npm'); + + // Initialize GYP + // We can use the node-gyp that comes with npm. + // We can locate it with npm -g explore npm npm explore node-gyp pwd + // It might have been neater to make node-gyp one of our dependencies + // *but* they don't get installed until after the install step has run. + let gypDir = child_process.execFileSync( + 'npm', + ['-g', 'explore', npmModules, 'npm', 'explore', gypSubDir, 'pwd'], + { cwd: buildDir } + ).toString().trim(); + return gypDir; } -// There are multiple versions of lldb available for the various linux distos. -// Use the default unless --llnode_exe= has been set on the command line. -function getLinuxVersion(lldbExe) { - var lldbStr; - try { - lldbStr = child_process.execFileSync(lldbExe, ['-v']).toString(); - } catch (err) { - return undefined; - } - // Ignore minor revisions like 3.8.1 - let versionMatch = lldbStr.match(/version (\d.\d)/); - if (versionMatch) { - return versionMatch[1]; - } - return undefined; +/** + * Link tools/gyp to the GYP installation + * @param {string} gypDir path to the GYP installation + */ +function linkGyp(gypDir) { + child_process.execSync('rm -rf tools'); + fs.mkdirSync('tools'); + console.log(`Linking tools/gyp to ${gypDir}/gyp`); + fs.symlinkSync(`${gypDir}/gyp`, 'tools/gyp'); } -// Shim this for consistancy in OS naming -function getFreeBSDVersion(lldbExe) { - // Strip the dots for BSD - return getLinuxVersion(lldbExe).replace('.', ''); +/** + * Generate the llnode shortcut script + * @param {string} buildDir Path of the project directory + * @param {string} lldbExe Name of the lldb executable + * @param {string} osName Name of the OS + */ +function writeLlnodeScript(buildDir, lldbExe, osName) { + const text = scriptText(osName, lldbExe); + const scriptPath = path.join(buildDir, 'llnode.sh'); + console.log(`Writing llnode.sh shortcut to ${scriptPath}`); + fs.writeFileSync(scriptPath, text); } -function getFreeBSDInstallDir(version) { - console.log('Checking for headers, version is ' + version); - - try { - var installDir = child_process.execFileSync('llvm-config' + version, - ['--prefix']).toString().trim(); - if (fs.existsSync(installDir + '/include/lldb')) { - return installDir; - } - } catch (err) { - console.log(installDir + '/include/lldb doesn\'nt exist'); - console.log('Please see README.md'); - console.log(err); - process.exit(1); - } - return undefined; -} - -function getLinuxInstallDir(version) { - // Get the directory which should contain the headers and - // check if they are present. - // (Using the installed headers will ensure we have the correct ones.) - console.log('Checking for headers, version is ' + version); - try { - var installDir = child_process.execFileSync('llvm-config-' + version, - ['--prefix']).toString().trim(); - // console.log('Checking for directory ' + include_dir); - // Include directory doesn't need include/lldb on the end but the llvm - // headers can be installed without the lldb headers so check for them. - if (fs.existsSync(installDir + '/include/lldb')) { - // console.log('Found ' + include_dir); - return installDir; - } - } catch (err) { - // Return undefined, we will download the headers. - } - // On Redhat the headers are just installed in /usr/include - if (fs.existsSync('/usr/include/lldb')) { - return '/usr'; - } - return undefined; +/** + * Write configuration to options.gypi + * @param {string} config + */ +function writeConfig(config) { + const options = JSON.stringify(config, null, 2); + fs.writeFileSync('options.gypi', options, 'utf-8'); + console.log('Writing options.gypi:'); + console.log(options); } -function scriptText(lldbExe) { +function scriptText(osName, lldbExe) { let lib = 'llnode.so'; - if (osName === 'Darwin') { + if (osName === 'Darwin') lib = 'llnode.dylib'; - } return `#!/bin/sh @@ -326,9 +171,12 @@ SCRIPT_PATH=\`dirname $LLNODE_SCRIPT\` if [ \`basename $SCRIPT_PATH\` = ".bin" ]; then # llnode installed locally in node_modules/.bin LLNODE_PLUGIN="$SCRIPT_PATH/../llnode/${lib}" -else +elif [ -e $SCRIPT_PATH/../lib/node_modules/llnode/${lib} ]; then # llnode installed globally in lib/node_modules LLNODE_PLUGIN="$SCRIPT_PATH/../lib/node_modules/llnode/${lib}" +else + # The scrips is invoked directly + LLNODE_PLUGIN="$SCRIPT_PATH/${lib}" fi ${lldbExe} --one-line "plugin load $LLNODE_PLUGIN" --one-line "settings set prompt '(llnode) '" $@ diff --git a/scripts/darwin.js b/scripts/darwin.js new file mode 100644 index 00000000..ae37e578 --- /dev/null +++ b/scripts/darwin.js @@ -0,0 +1,127 @@ + +'use strict'; + +const child_process = require('child_process'); +const os = require('os'); +const fs = require('fs'); +const lldb = require('./lldb'); + +/** + * On Mac the lldb version string doesn't match the original lldb versions, + * we need to get it either from `llvm-config --version` (custom installation) + * or `xcodebuild -version` (Xcode installation). + * + * @returns {string|undefined} Deduced version of lldb, undefined if failed + */ +function getLldbVersion() { + let versionFromConfig; + try { + versionFromConfig = child_process.execFileSync('llvm-config', [ + '--version' + ]).toString().trim(); + } catch (err) { + // No llvm-config, try to get the version from xcodebuild + } + if (versionFromConfig !== undefined) { + const result = versionFromConfig.split('.').slice(0, 2).join('.'); + console.log(`Retrieved lldb version ${result} ` + + 'from `llvm-config --version`'); + return result; + } + + let xcodeStr; + try { + xcodeStr = child_process.execFileSync( + 'xcodebuild', ['-version'] + ).toString().trim(); + } catch (err) { + console.log(err); + console.log('Could not retrieve Xcode version from `xcodebuild -version`'); + return undefined; + } + + let xcodeVersion; + let splitStr = xcodeStr.split(os.EOL); + for (let str of splitStr) + if (str.includes('Xcode')) { + xcodeVersion = str.split(' ')[1]; + break; + } + + if (xcodeVersion === undefined) { + console.log(`Could not get Xcode version from:\n${xcodeStr}`); + return undefined; + } + + let result; + let version = parseFloat(xcodeVersion); + if (version >= 8.3) + result = '3.9'; + else if (version > 8.0) + result = '3.8'; + else + result = '3.4'; + + if (result !== undefined) + console.log('Deduced lldb version from Xcode version: ' + + `Xcode ${xcodeVersion} -> lldb ${result}`); + else + console.log('Could not deduce lldb version from Xcode version' + + xcodeVersion); + + return result; +} + +/** + * Get the directory to the lldb installation, if it returns undefined, + * we need to download the headers to ./lldb/include/lldb + * @returns {string|undefined} lldb installation prefix, undefined if failed + */ +function getInstallDir() { + var installedDir; + try { + installedDir = child_process.execFileSync('llvm-config', [ + '--prefix' + ]).toString().trim(); + } catch (err) { + // Return undefined, we will download the headers. + } + if (installedDir !== undefined && + fs.existsSync(lldb.getHeadersPath(installedDir))) + return installedDir; + + return undefined; +} + +/** + * Get the lldb installation. If prefix is undefined, the headers need to + * be downloaded. + * The version string will be in the form like '3.9' + * @returns {{executable: string, version: string, ?prefix: string}} + */ +function getLldbInstallation() { + const lldbExe = process.env.npm_config_lldb_exe || 'lldb'; + // We cannot just use the executable specified with + // process.env.npm_config_lldb_exe to determine the version of lldb + // because we do not know how. We can only use llvm-config or xcodebuild + // to retrieve the version. + const lldbVersion = getLldbVersion(); + + if (lldbVersion === undefined) { + console.log('Unable to deduce the version of lldb, ' + + 'llnode installation failed.'); + process.exit(1); + } + + console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); + const installedDir = getInstallDir(); + return { + executable: lldbExe, + version: lldbVersion, + prefix: installedDir + }; +} + +module.exports = { + getLldbInstallation +}; diff --git a/scripts/freebsd.js b/scripts/freebsd.js new file mode 100644 index 00000000..a05d14ab --- /dev/null +++ b/scripts/freebsd.js @@ -0,0 +1,89 @@ +'use strict'; + +const child_process = require('child_process'); +const fs = require('fs'); + +const linux = require('./linux'); +const lldb = require('./lldb'); + +/** + * Get the version of the lldb executable, + * shim this for consistancy in OS naming + * @param {string} lldbExe + * @returns {string} lldb version in the form like '39' + */ +function getLldbVersion(lldbExe) { + // Strip the dots for BSD + return linux.getLldbVersion(lldbExe).replace('.', ''); +} + +function printAdvice(version) { + console.log('The system isn\'t set up correcly.'); + console.log(`Try \`pkg install llvm${version}\``); + console.log(`And \`ln -s /usr/local/bin/lldb${version} /usr/bin/lldb\``); +} + +/** + * Get the installation directory (prefix) of lldb + * @param {string} version lldb version for FreeBSD, e.g. '39' for 'lldb-3.9' + * @returns {string} Directory of the lldb installation + */ +function getInstallDir(version) { + // Get the directory which should contain the headers and + // check if they are present. + // (Using the installed headers will ensure we have the correct ones.) + console.log('Checking for headers, version is ' + version); + let installDir; + try { + // Notice the executable is named differently from Linux + installDir = child_process.execFileSync( + `llvm-config${version}`, + ['--prefix'] + ).toString().trim(); + } catch (err) { + // As this is a BSD we know this system is in an improper state + // So we can exit with an error + console.log(`Could not execute llvm-config${version}`); + printAdvice(version); + console.log(err); + process.exit(1); + } + + const headers = lldb.getHeadersPath(installDir); + if (!fs.existsSync(headers)) { + // As this is a BSD we know this system is in an improper state + // So we can exit with an error + console.log(`Could not find ${headers}`); + printAdvice(version); + process.exit(1); + } + + return installDir; +} + +/** + * Get the lldb installation + * @returns {{executable: string, version: string, prefix: string}} + */ +function getLldbInstallation() { + const lldbExe = linux.getLldbExecutable(); + const lldbVersion = getLldbVersion(lldbExe); + + if (lldbVersion === undefined) { + console.log('Unable to get lldb binary or its version. ' + + 'llnode installation failed.'); + process.exit(1); + } + + console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); + const installedDir = getInstallDir(lldbVersion); + return { + executable: lldbExe, + version: lldbVersion, + prefix: installedDir + }; +} + +module.exports = { + getLldbInstallation +}; diff --git a/scripts/linux.js b/scripts/linux.js new file mode 100644 index 00000000..8d07f629 --- /dev/null +++ b/scripts/linux.js @@ -0,0 +1,136 @@ + +'use strict'; + +const child_process = require('child_process'); +const fs = require('fs'); +const lldb = require('./lldb'); + +/** + * Find the 'best' lldb to use, exit the process with 1 if failed. + * The search happens in the following order: + * - the one specified by the user using npm --lldb_exe=... install llnode + * - the default lldb executable (`lldb`) + * - the higest known lldb version (`lldb-x.y`) + * - the names of future releases are predictable for linux + * + * @returns {string} Name of the lldb executable + */ +function getLldbExecutable() { + var lldbExe = process.env.npm_config_lldb_exe; + if (lldbExe !== undefined) + return lldbExe; + + var lldbExeNames = [ + 'lldb', 'lldb-5.0', 'lldb-4.0', + 'lldb-3.9', 'lldb-3.8', 'lldb-3.7', 'lldb-3.6' + ]; + + for (var lldbExeVersion of lldbExeNames) + try { + lldbExe = child_process.execSync('which ' + + lldbExeVersion).toString().trim(); + // If the result starts with '/' `which` found a path. + if (lldbExe.startsWith('/')) + break; + } catch (err) { + // Do nothing - we expect not to find some of these. + } + + if (!lldbExe) { + console.log('Could not find any usable lldb binary'); + console.log('Please see the README.md on how to install lldb'); + process.exit(1); + } + return lldbExe; +} + +/** + * Get the lldb version from the lldb executable, exit the process with 1 + * if failed. + * @param {string} lldbExe + * @returns {string} Version of the executable in the form like '3.9' + */ +function getLldbVersion(lldbExe) { + var lldbStr; + try { + lldbStr = child_process.execFileSync(lldbExe, ['-v']).toString(); + } catch (err) { + console.log(err); + console.log(`Unable to get the version from the lldb binary ${lldbExe}`); + process.exit(1); + } + // Ignore minor revisions like 3.8.1 + const versionMatch = lldbStr.match(/version (\d.\d)/); + if (versionMatch) + return versionMatch[1]; + + console.log(`Unable to get the version from the lldb binary ${lldbExe}`); + console.log(`Output from \`${lldbExe} -v\` was ${lldbStr}`); + process.exit(1); +} + +/** + * Get the directory to the lldb installation, if it returns undefined, + * we need to download the headers to ./lldb/include/lldb + * @param {string} version Version of the lldb executable + * @returns {string|undefined} lldb installation prefix, undefined if failed + */ +function getInstallDir(version) { + // Get the directory which should contain the headers and + // check if they are present. + // (Using the installed headers will ensure we have the correct ones.) + console.log('Checking for headers, version is ' + version); + + let installDir; + try { + installDir = child_process.execFileSync( + `llvm-config-${version}`, + ['--prefix'] + ).toString().trim(); + } catch (err) { + // Could not get the headers through llvm-config, try another way + } + + if (!installDir) + // On Redhat the headers are just installed in /usr/include + if (fs.existsSync('/usr/include/lldb')) { + return '/usr'; + } else { + // We will download the headers + return undefined; + } + + // Include directory doesn't need include/lldb on the end but the llvm + // headers can be installed without the lldb headers so check for them. + const headers = lldb.getHeadersPath(installDir); + if (fs.existsSync(headers)) + return installDir; + + // We will download the headers + return undefined; +} + +/** + * Get the lldb installation. If prefix is undefined, the headers need to + * be downloaded. + * The version string will be in the form like '3.9' + * @returns {{executable: string, version: string, ?prefix: string}} + */ +function getLldbInstallation() { + const lldbExe = getLldbExecutable(); + const lldbVersion = getLldbVersion(lldbExe); + + console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); + const installedDir = getInstallDir(lldbVersion); + return { + executable: lldbExe, + version: lldbVersion, + prefix: installedDir + }; +} + +module.exports = { + getLldbExecutable, + getLldbVersion, + getLldbInstallation +}; diff --git a/scripts/lldb.js b/scripts/lldb.js new file mode 100644 index 00000000..32d9eb3c --- /dev/null +++ b/scripts/lldb.js @@ -0,0 +1,55 @@ +'use strcit'; + +const child_process = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +/** + * @param {string} version lldb version, either in the form '3.9' or '39' + * @returns {string} Branch of the corresponding lldb release + */ +function versionToBranch(version) { + return 'release_' + version.replace('.', ''); +} + +/** + * Equivalent to `llvm-config --includedir`/lldb + * @param {string} lldbInstallDir Path to the lldb installation + * @returns {string} Path to the lldb headers + */ +function getHeadersPath(lldbInstallDir) { + return path.join(lldbInstallDir, 'include', 'lldb'); +} + +/** + * Check out source code of the lldb for headers + * TODO: The llvm project is probably moving to github soon at that point we + * should stop using the mirror. + * @param {string} lldbVersion Version of lldb, either like 3.9 or 39 + * @param {string} buildDir + * @returns {string} The directory where the source code is checked out + */ +function cloneHeaders(lldbVersion, buildDir) { + const lldbHeadersBranch = versionToBranch(lldbVersion); + const lldbInstallDir = `lldb-${lldbVersion}`; + + if (!fs.existsSync(path.join(buildDir, lldbInstallDir))) { + console.log(`Cloning lldb ${lldbHeadersBranch} into ${lldbInstallDir}`); + child_process.execFileSync('git', + ['clone', '--depth=1', '-b', lldbHeadersBranch, + 'https://github.com/llvm-mirror/lldb.git', lldbInstallDir], + { + cwd: buildDir, + stdio: 'inherit' // show progress + }); + } else { + console.log(`Skip cloning lldb headers because ${lldbInstallDir} exists`); + } + return lldbInstallDir; +} + +module.exports = { + versionToBranch, + getHeadersPath, + cloneHeaders +};