diff --git a/.gitignore b/.gitignore index 600f1239..e9d7f718 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,15 @@ *.dylib +*.so *.dSYM tools/ out/ lldb/ +lldb-*/ build/ out/ npm-debug.log node_modules/ -options.gypi + +# Generated by scripts/configure.js +config.gypi llnode.sh diff --git a/README.md b/README.md index d26a4b30..78d0ea18 100644 --- a/README.md +++ b/README.md @@ -251,12 +251,12 @@ node scripts/configure.js # To configure with the detected lldb installation node-gyp configure -# To configure with a specified path to headers, where `$lldb_header_dir/include` +# To configure with a specified path to headers, where `$lldb_include_dir` # contains the headers -node-gyp configure -- -Dlldb_header_dir=/usr/local/Cellar/llvm/5.0.0 -# To configure with a specified path to the libraries, where `$lldb_lib_dir/lib` +node-gyp configure -- -Dlldb_include_dir=/usr/local/Cellar/llvm/5.0.0/include +# To configure with a specified path to the libraries, where `$lldb_lib_dir` # contains `liblldb.so` or `liblldb.dylib` -node-gyp configure -- -Dlldb_lib_dir=/usr/lib/llvm-3.9 +node-gyp configure -- -Dlldb_lib_dir=/usr/lib/llvm-3.9/lib # Build the plugin node-gyp build diff --git a/binding.gyp b/binding.gyp index aa1a2a5a..78864d48 100644 --- a/binding.gyp +++ b/binding.gyp @@ -10,21 +10,10 @@ "lldb_lib_dir%": "" }, - "targets": [{ - "target_name": "plugin", - "type": "shared_library", - "product_prefix": "", - + "target_defaults": { "include_dirs": [ - ".", - "<(lldb_header_dir)/include", - ], - - "sources": [ - "src/llnode.cc", - "src/llv8.cc", - "src/llv8-constants.cc", - "src/llscan.cc", + "<(module_root_dir)", + "<(lldb_include_dir)", ], "cflags" : [ "-std=c++11" ], @@ -54,19 +43,25 @@ { "xcode_settings": { "OTHER_LDFLAGS": [ - "-Wl,-rpath,<(lldb_lib_dir)/lib", - "-L<(lldb_lib_dir)/lib", + "-Wl,-rpath,<(lldb_lib_dir)", + "-L<(lldb_lib_dir)", "-l<(lldb_lib)", ], }, }], ], - }], + }] ] }, - { - "target_name": "install", - "type":"none", - "dependencies" : [ "plugin" ] + + "targets": [{ + "target_name": "plugin", + "type": "shared_library", + "sources": [ + "src/llnode.cc", + "src/llv8.cc", + "src/llv8-constants.cc", + "src/llscan.cc", + ] }], } diff --git a/llnode.gypi b/llnode.gypi index 6e8c8ada..c1cf05f5 100644 --- a/llnode.gypi +++ b/llnode.gypi @@ -9,8 +9,8 @@ "msvs_multi_core_compile": "0", # we do enable multicore compiles, but not using the V8 way "gcc_version%": "unknown", "clang%": 1, - "lldb_header_dir%": "lldb", - "lldb_lib%": "lldb", + "lldb_include_dir%": "lldb/include", # location of the lldb headers + "lldb_lib%": "lldb", # name of the -l library to link "conditions": [ ["GENERATOR == 'ninja'", { "OBJ_DIR": "<(PRODUCT_DIR)/obj", diff --git a/scripts/cleanup.js b/scripts/cleanup.js index 3739c742..4db6b906 100644 --- a/scripts/cleanup.js +++ b/scripts/cleanup.js @@ -1,31 +1,23 @@ 'use strict'; -const os = require('os'); -const child_process = require('child_process'); const fs = require('fs'); - const cwd = process.cwd(); -const osName = os.type(); -var libExt = 'so'; - -if (osName === 'Darwin') { - libExt = 'dylib'; -} +const libExt = require('os').type() === 'Darwin' ? 'dylib' : 'so'; const llnodeLib = `plugin.${libExt}`; const destLib = `llnode.${libExt}`; let buildPath = `${cwd}/build/Release/${llnodeLib}`; -if (!fs.existsSync(buildPath)) { +if (!fs.existsSync(buildPath)) buildPath = `${cwd}/build/Debug/${llnodeLib}`; -} const destPath = `${cwd}/${destLib}`; console.log(`Moving ${buildPath} to ${destPath}`); fs.renameSync(buildPath, destPath); -console.log(`${os.EOL}llnode plugin installed, load in lldb with:`); +console.log(`\nllnode plugin installed, load in lldb with:\n`); console.log(`(lldb) plugin load ${destPath}`); -console.log(`or copy plugin to lldb system plugin directory, see www.npmjs.org/llnode${os.EOL}`); +console.log('\nor copy plugin to lldb system plugin directory'); +console.log('see https://github.com/nodejs/llnode\n'); diff --git a/scripts/configure.js b/scripts/configure.js index 5941a456..a01e071c 100644 --- a/scripts/configure.js +++ b/scripts/configure.js @@ -3,17 +3,15 @@ const os = require('os'); const fs = require('fs'); const path = require('path'); -const child_process = require('child_process'); 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); - writeConfig(installation.config); - linkHeadersDir(installation.prefix); - writeLlnodeScript(buildDir, installation.executable, osName); + const result = configureInstallation(osName, buildDir); + writeConfig(result.config); + writeLlnodeScript(buildDir, result.executable, osName); // Exit with success. process.exit(0); } @@ -32,61 +30,43 @@ function configureInstallation(osName, buildDir) { // - 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 - // On Linux and BSD the config is always empty. - const config = {}; + let includeDir; + const config = { + variables: {} + }; 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 - config.variables = { 'lldb_lib_dir%': prefix }; + installation = require('./darwin').getLldbInstallation(); + if (installation.libDir) { + config.variables['lldb_lib_dir%'] = installation.libDir; } } 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; + installation = require('./linux').getLldbInstallation(); + if (installation.libDir) { + config.variables['lldb_lib_dir%'] = installation.libDir; } + config.variables['lldb_lib%'] = installation.libName; } else if (osName === 'FreeBSD') { - const freebsd = require('./freebsd'); - installation = freebsd.getLldbInstallation(); - prefix = installation.prefix; + installation = require('./freebsd').getLldbInstallation(); + config.variables['lldb_lib_dir%'] = installation.libDir; } else { console.log(`Unsupported OS: ${osName}`); process.exit(1); } + if (installation.includeDir === undefined) { + // Could not find the headers, need to download them + includeDir = lldb.cloneHeaders(installation.version, buildDir); + } else { + includeDir = installation.includeDir; + } + config.variables['lldb_include_dir%'] = includeDir; return { executable: installation.executable, - version: installation.version, - prefix, config }; } -/** - * Link to the headers file so we can run `node-gyp configure` 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 { - fs.unlinkSync('lldb'); - } catch (error) { - // File does not exist, no need to handle. - } - fs.symlinkSync(lldbInstallDir, 'lldb'); -} - /** * Generate the llnode shortcut script * @param {string} buildDir Path of the project directory @@ -101,7 +81,7 @@ function writeLlnodeScript(buildDir, lldbExe, osName) { } /** - * Write configuration to options.gypi + * Write configuration to config.gypi * @param {string} config */ function writeConfig(config) { @@ -113,8 +93,7 @@ function writeConfig(config) { function scriptText(osName, lldbExe) { let lib = 'llnode.so'; - if (osName === 'Darwin') - lib = 'llnode.dylib'; + if (osName === 'Darwin') { lib = 'llnode.dylib'; } return `#!/bin/sh diff --git a/scripts/darwin.js b/scripts/darwin.js index ae37e578..b7f9a1a2 100644 --- a/scripts/darwin.js +++ b/scripts/darwin.js @@ -2,27 +2,40 @@ 'use strict'; const child_process = require('child_process'); -const os = require('os'); const fs = require('fs'); +const path = require('path'); const lldb = require('./lldb'); +/** + * Try to find an executable llvm-config in the system. Returns undefined + * it it could not be found. + * @returns {string|undefined} + */ +function getLlvmConfig() { + const llvmConfig = lldb.tryExecutables(['llvm-config']); + if (llvmConfig) { + try { + child_process.execFileSync(llvmConfig, ['--version']); + } catch (err) { + return undefined; // Not really executable + } + } + return llvmConfig; +} + /** * 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). * + * @param {string|undefined} llvmConfig Path to llvm-config, if it's installed * @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) { +function getLldbVersion(llvmConfig) { + if (llvmConfig) { + const versionFromConfig = child_process.execFileSync( + llvmConfig, ['--version'] + ).toString().trim(); const result = versionFromConfig.split('.').slice(0, 2).join('.'); console.log(`Retrieved lldb version ${result} ` + 'from `llvm-config --version`'); @@ -41,12 +54,13 @@ function getLldbVersion() { } let xcodeVersion; - let splitStr = xcodeStr.split(os.EOL); - for (let str of splitStr) + let splitStr = xcodeStr.split('\n'); + 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}`); @@ -55,49 +69,65 @@ function getLldbVersion() { let result; let version = parseFloat(xcodeVersion); - if (version >= 8.3) + if (version >= 8.3) { result = '3.9'; - else if (version > 8.0) + } else if (version > 8.0) { result = '3.8'; - else + } else { result = '3.4'; + } - if (result !== undefined) + if (result !== undefined) { console.log('Deduced lldb version from Xcode version: ' + `Xcode ${xcodeVersion} -> lldb ${result}`); - else + } 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, + * Get the directory containing the lldb headers. If it returns undefined, * we need to download the headers to ./lldb/include/lldb - * @returns {string|undefined} lldb installation prefix, undefined if failed + * Using the installed headers will ensure we have the correct ones. + * @param {string|undefined} llvmConfig Path to llvm-config, if it's installed + * @returns {string|undefined} the lldb include directory, 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. +function getIncludeDir(llvmConfig) { + if (!llvmConfig) { + return undefined; + } + const includeDir = child_process.execFileSync( + llvmConfig, ['--includedir'] + ).toString().trim(); + if (fs.existsSync(lldb.getApiHeadersPath(includeDir))) { + return includeDir; } - if (installedDir !== undefined && - fs.existsSync(lldb.getHeadersPath(installedDir))) - return installedDir; +} - return undefined; +/** + * Get the directory containing the lldb shared libraries. If it returns + * undefined, the shared library will be searched from the global search paths + * @param {string|undefined} llvmConfig Path to llvm-config, if it's installed + * @returns {string|undefined} the lldb library directory, undefined if failed + */ +function getLibDir(llvmConfig) { + if (!llvmConfig) { + return undefined; + } + const libDir = child_process.execFileSync( + llvmConfig, ['--libdir'] + ).toString().trim(); + if (fs.existsSync(path.join(libDir, 'liblldb.dylib'))) { + return libDir; + } } /** - * Get the lldb installation. If prefix is undefined, the headers need to - * be downloaded. + * Get the lldb installation. * 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'; @@ -105,20 +135,48 @@ function getLldbInstallation() { // 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(); + console.log(`Using lldb executable ${lldbExe}`); + + console.log('\nLooking for llvm-config...'); + const llvmConfig = getLlvmConfig(); + if (!llvmConfig) { + console.log('No llvm-config found'); + } else { + console.log(`Using llvm-config in ${llvmConfig}`); + } - if (lldbVersion === undefined) { + console.log('\nReading lldb version...'); + const lldbVersion = getLldbVersion(llvmConfig); + if (!lldbVersion) { 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(); + console.log(`\nLooking for headers for lldb ${lldbVersion}...`); + const includeDir = getIncludeDir(llvmConfig); + if (!includeDir) { + console.log('Could not find the headers, will download them later'); + } else { + console.log(`Found lldb headers in ${includeDir}`); + } + + console.log(`\nLooking for shared libraries for lldb ${lldbVersion}...`); + const libDir = getLibDir(llvmConfig); + if (!libDir) { + console.log('Could not find the shared libraries '); + console.log('llnode will be linked to the LLDB shared framework from ' + + 'the Xcode installation'); + } else { + console.log(`Found lldb liblldb.dylib in ${libDir}`); + } + return { executable: lldbExe, version: lldbVersion, - prefix: installedDir + includeDir: includeDir, + libDir: libDir }; } diff --git a/scripts/freebsd.js b/scripts/freebsd.js index a05d14ab..c2895805 100644 --- a/scripts/freebsd.js +++ b/scripts/freebsd.js @@ -2,85 +2,159 @@ const child_process = require('child_process'); const fs = require('fs'); +const path = require('path'); -const linux = require('./linux'); const lldb = require('./lldb'); +function printAdvice(version) { + console.log('The system is not set up correctly. Please try:\n'); + console.log(`$ pkg install llvm${version}`); + console.log(`$ ln -s /usr/local/bin/lldb${version} /usr/bin/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-xy`) + * - the names of future releases are predictable for FreeBSD + * + * @returns {string} Path to the lldb executable + */ +function getLldbExecutable() { + if (process.env.npm_config_lldb_exe !== undefined) { + return process.env.npm_config_lldb_exe; + } + + const lldbExeNames = [ + 'lldb', 'lldb50', 'lldb40', + 'lldb39', 'lldb38', 'lldb37', 'lldb36' + ]; + + const lldbExe = lldb.tryExecutables(lldbExeNames); + return lldbExe; +} + /** - * Get the version of the lldb executable, - * shim this for consistancy in OS naming - * @param {string} lldbExe + * Get the version of the lldb executable, shim this for consistency + * in OS naming + * @param {string} lldbExe Path to lldb executable * @returns {string} lldb version in the form like '39' */ function getLldbVersion(lldbExe) { // Strip the dots for BSD - return linux.getLldbVersion(lldbExe).replace('.', ''); + return lldb.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\``); +/** + * Try to find an executable llvm-config in the system. Returns undefined + * it it could not be found. + * @param {string} version lldb version for FreeBSD, e.g. '39' for lldb-3.9 + * @returns {string|undefined} + */ +function getLlvmConfig(version) { + let llvmConfig = lldb.tryExecutables([`llvm-config${version}`]); + if (!llvmConfig) { + llvmConfig = lldb.tryExecutables(['llvm-config']); + } + if (llvmConfig) { + try { + child_process.execFileSync(llvmConfig, ['--version']); + } catch (err) { + return undefined; // Not really executable + } + } + + return llvmConfig; } /** - * 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 + * Get the directory containing the lldb headers. + * Using the installed headers will ensure we have the correct ones. + * @param {string} llvmConfig Path to llvm-config + * @returns {string} the lldb include directory */ -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); +function getIncludeDir(llvmConfig) { + const includeDir = child_process.execFileSync( + llvmConfig, ['--includedir'] + ).toString().trim(); + const headers = lldb.getApiHeadersPath(includeDir); + if (fs.existsSync(headers)) { + return includeDir; } + return undefined; +} - 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); +/** + * Get the directory containing the lldb shared libraries. + * @param {string} llvmConfig Path to llvm-config + * @returns {string} the lldb library directory + */ +function getLibDir(llvmConfig) { + const libDir = child_process.execFileSync( + llvmConfig, ['--libdir'] + ).toString().trim(); + if (fs.existsSync(path.join(libDir, 'liblldb.so'))) { + return libDir; } - - return installDir; + return undefined; } /** - * Get the lldb installation - * @returns {{executable: string, version: string, prefix: string}} + * Get the lldb installation. On FreeBSD we expect lldb to be + * installed with the llvm package, which comes with headers, + * shared libraries, and llvm-config. */ function getLldbInstallation() { - const lldbExe = linux.getLldbExecutable(); + console.log('Looking for lldb executable...'); + const lldbExe = getLldbExecutable(); + if (!lldbExe) { + printAdvice('50'); // Recommended version is lldb 5.0 + process.exit(1); + } + console.log(`Found lldb executable ${lldbExe}`); + + console.log('\nReading lldb version...'); const lldbVersion = getLldbVersion(lldbExe); + if (!lldbVersion === undefined) { + console.log(`Unable to get the version from the lldb binary ${lldbExe}`); + process.exit(1); + } + console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - if (lldbVersion === undefined) { - console.log('Unable to get lldb binary or its version. ' + - 'llnode installation failed.'); + console.log(`\nLooking for llvm-config for lldb ${lldbVersion}...`); + const llvmConfig = getLlvmConfig(lldbVersion); + if (!llvmConfig) { + console.log(`Could not find llvm-config in the system`); + printAdvice(lldbVersion); process.exit(1); } + console.log(`Using llvm-config in ${llvmConfig}`); + + console.log(`\nLooking for headers for lldb ${lldbVersion}...`); + const includeDir = getIncludeDir(llvmConfig); + if (!includeDir) { + console.log(`Could not find lldb headers in the system`); + printAdvice(lldbVersion); + process.exit(1); + } + console.log(`Found lldb headers in ${includeDir}`); + + console.log(`\nLooking for shared libraries for lldb ${lldbVersion}...`); + const libDir = getLibDir(llvmConfig); + if (!libDir) { + console.log(`Could not find shared libraries in the system`); + printAdvice(lldbVersion); + process.exit(1); + } + console.log(`Found lldb liblldb.so in ${libDir}`); - console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - const installedDir = getInstallDir(lldbVersion); return { executable: lldbExe, version: lldbVersion, - prefix: installedDir + includeDir: includeDir, + libDir: libDir }; } diff --git a/scripts/linux.js b/scripts/linux.js index 8d07f629..eb7eba42 100644 --- a/scripts/linux.js +++ b/scripts/linux.js @@ -3,6 +3,7 @@ const child_process = require('child_process'); const fs = require('fs'); +const path = require('path'); const lldb = require('./lldb'); /** @@ -13,124 +14,187 @@ const lldb = require('./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 + * @returns {string} Path to the lldb executable */ function getLldbExecutable() { - var lldbExe = process.env.npm_config_lldb_exe; - if (lldbExe !== undefined) - return lldbExe; + if (process.env.npm_config_lldb_exe !== undefined) { + return process.env.npm_config_lldb_exe; + } - var lldbExeNames = [ + const 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) + return lldb.tryExecutables(lldbExeNames); +} + +/** + * Try to find an executable llvm-config in the system. Returns undefined + * it it could not be found. + * @param {string} version lldb version for linux, e.g. '3.9' for lldb-3.9 + * @returns {string|undefined} + */ +function getLlvmConfig(version) { + // On Ubuntu llvm-config is suffixed + let llvmConfig = lldb.tryExecutables([`llvm-config-${version}`]); + if (!llvmConfig) { + llvmConfig = lldb.tryExecutables(['llvm-config']); + } + if (llvmConfig) { try { - lldbExe = child_process.execSync('which ' + - lldbExeVersion).toString().trim(); - // If the result starts with '/' `which` found a path. - if (lldbExe.startsWith('/')) - break; + child_process.execFileSync(llvmConfig, ['--version']); } catch (err) { - // Do nothing - we expect not to find some of these. + return undefined; // Not really executable } - - 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; + + return llvmConfig; } /** - * 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' + * Get the directory containing the lldb headers. If it returns undefined, + * we need to download the headers to ./lldb/include/lldb. + * Using the installed headers will ensure we have the correct ones. + * @param {string|undefined} llvmConfig Path to llvm-config, if it's installed + * @returns {string|undefined} the lldb include directory, undefined if failed */ -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); +function getIncludeDir(llvmConfig) { + if (llvmConfig) { + const includeDir = child_process.execFileSync( + llvmConfig, ['--includedir'] + ).toString().trim(); + // 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 (includeDir && fs.existsSync(lldb.getApiHeadersPath(includeDir))) { + return includeDir; + } } - // 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); + + // On RedHat the headers are just installed in /usr/include + if (fs.existsSync(lldb.getApiHeadersPath('/usr/include'))) { + return '/usr/include'; + } + + // We will download the headers + return undefined; } /** - * 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 + * Get the directory containing the lldb shared libraries. If it returns + * undefined, the shared library will be searched from the global search paths + * @param {string} lldbExe Path to the corresponding lldb executable + * @param {string|undefined} llvmConfig Path to llvm-config, if it's installed + * @returns {{dir:string, name:string}} */ -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); +function getLib(lldbExe, llvmConfig) { + // First look for the libraries in the directory returned by + // llvm-config --libdir + if (llvmConfig) { + const libDir = child_process.execFileSync( + llvmConfig, ['--libdir'] + ).toString().trim(); + if (fs.existsSync(path.join(libDir, 'liblldb.so'))) { + return { dir: libDir, name: 'lldb' }; + } + } - let installDir; + // Look for the libraries loaded by the executable using ldd + let libs; try { - installDir = child_process.execFileSync( - `llvm-config-${version}`, - ['--prefix'] + libs = child_process.execFileSync( + 'ldd', [lldbExe] ).toString().trim(); } catch (err) { - // Could not get the headers through llvm-config, try another way + return { dir: undefined, name: 'lldb' }; } - 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; - } + const lib = libs.split(/\s/).find( + line => line.includes('liblldb') && line.startsWith('/')); + if (!lib) { + return { dir: undefined, name: 'lldb' }; + } - // 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; + console.log(`From ldd: ${lldbExe} loads ${lib}`); + + const libDir = path.dirname(path.resolve(lib)); + // On Ubuntu the libraries are suffixed and installed globally + const libName = path.basename(lib).match(/lib(lldb.*?)\.so/)[1]; + + // TODO(joyeecheung): on RedHat there might not be a non-versioned liblldb.so + // in the system. It's fine in the case of plugins since the lldb executable + // will load the library before loading the plugin, but we will have to link + // to the versioned library file for addons. + if (!fs.existsSync(path.join(libDir, `lib${libName}.so`))) { + return { + dir: undefined, + name: libName + }; + } - // We will download the headers - return undefined; + return { + dir: libDir, + name: libName + }; } /** * 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() { + console.log('Looking for lldb executable...'); const lldbExe = getLldbExecutable(); - const lldbVersion = getLldbVersion(lldbExe); + if (!lldbExe) { + console.log('Could not find any usable lldb executable'); + console.log('Please see the README.md on how to install lldb'); + process.exit(1); + } + console.log(`Found lldb executable ${lldbExe}`); + console.log('\nReading lldb version...'); + const lldbVersion = lldb.getLldbVersion(lldbExe); + if (!lldbVersion) { + console.log(`Unable to get the version from the lldb binary ${lldbExe}`); + process.exit(1); + } console.log(`Installing llnode for ${lldbExe}, lldb version ${lldbVersion}`); - const installedDir = getInstallDir(lldbVersion); + + console.log(`\nLooking for llvm-config for lldb ${lldbVersion}...`); + const llvmConfig = getLlvmConfig(lldbVersion); + if (!llvmConfig) { + console.log('No llvm-config found'); + } else { + console.log(`Using llvm-config in ${llvmConfig}`); + } + + console.log(`\nLooking for headers for lldb ${lldbVersion}...`); + const includeDir = getIncludeDir(llvmConfig); + if (!includeDir) { + console.log('Could not find the headers, will download them later'); + } else { + console.log(`Found lldb headers in ${includeDir}`); + } + + console.log(`\nLooking for shared libraries for lldb ${lldbVersion}...`); + const lib = getLib(lldbExe, llvmConfig); + if (!lib.dir) { + console.log(`Could not find non-versioned lib${lib.name}.so in the system`); + console.log(`Symbols will be resolved by the lldb executable at runtime`); + } else { + console.log(`Found lib${lib.name}.so in ${lib.dir}`); + } + return { executable: lldbExe, version: lldbVersion, - prefix: installedDir + includeDir: includeDir, + libDir: lib.dir, + libName: lib.name }; } module.exports = { - getLldbExecutable, - getLldbVersion, getLldbInstallation }; diff --git a/scripts/lldb.js b/scripts/lldb.js index 32d9eb3c..7506675b 100644 --- a/scripts/lldb.js +++ b/scripts/lldb.js @@ -1,7 +1,8 @@ -'use strcit'; +'use strict'; const child_process = require('child_process'); const path = require('path'); +const os = require('os'); const fs = require('fs'); /** @@ -13,12 +14,20 @@ function versionToBranch(version) { } /** - * Equivalent to `llvm-config --includedir`/lldb - * @param {string} lldbInstallDir Path to the lldb installation - * @returns {string} Path to the lldb headers + * @param {string} includeDir Path to the equivalent of llvm-config --includedir + * @returns {string} Path to the lldb API headers */ -function getHeadersPath(lldbInstallDir) { - return path.join(lldbInstallDir, 'include', 'lldb'); +function getApiHeadersPath(includeDir) { + return path.join(includeDir, 'lldb', 'API'); +} + +/** + * @param {string} includeDir Path to the equivalent of llvm-config --libddir + * @returns {string} Path to the lldb shared library + */ +function getLibPath(libDir) { + const lib = os.type() === 'Darwin' ? 'liblldb.dylib' : 'liblldb.so'; + return path.join(libDir, lib); } /** @@ -26,30 +35,82 @@ function getHeadersPath(lldbInstallDir) { * 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 + * @param {string} buildDir Path to the llnode module directory + * @returns {string} The include directory in the downloaded lldb source code */ 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 - }); + const lldbInstallDir = path.resolve(buildDir, `lldb-${lldbVersion}`); + + if (!fs.existsSync(lldbInstallDir)) { + console.log(`\nCloning lldb ${lldbHeadersBranch} into ${lldbInstallDir}`); + child_process.execFileSync( + 'git', ['clone', + '--depth', '1', + '--branch', lldbHeadersBranch, + 'https://github.com/llvm-mirror/lldb.git', + lldbInstallDir + ], + { stdio: 'inherit' }); // show progress } else { - console.log(`Skip cloning lldb headers because ${lldbInstallDir} exists`); + console.log(`\nSkip cloning lldb headers because ${lldbInstallDir} exists`); } - return lldbInstallDir; + return path.join(lldbInstallDir, 'include'); +} + +/** + * Try to find the first valid executable out of an array executable names. + * Returns undefined if none of the provided executables is valid, otherwise + * returns the path to the first found valid executable. + * @param {string[]} exeNames + * @returns {string|undefined} + */ +function tryExecutables(exeNames) { + for (let name of exeNames) { + let exePath; + try { + exePath = child_process.execFileSync( + 'which', [name], { stdio: 'pipe' } // to suppress stderr + ).toString().trim(); + } catch (err) { + // Do nothing - we expect not to find some of these. + } + // If the result starts with '/' `which` found a path. + if (exePath && exePath.startsWith('/')) { + return exePath; + } + } +} + +/** + * 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) { + let lldbStr; + try { + lldbStr = child_process.execFileSync(lldbExe, ['-v']).toString(); + } catch (err) { + console.log(err); + return undefined; + } + // Ignore minor revisions like 3.8.1 + const versionMatch = lldbStr.match(/version (\d.\d)/); + if (versionMatch) { + return versionMatch[1]; + } + + console.log(`Output from \`${lldbExe} -v\` was ${lldbStr}`); + return undefined; } module.exports = { versionToBranch, - getHeadersPath, - cloneHeaders + getApiHeadersPath, + getLibPath, + cloneHeaders, + tryExecutables, + getLldbVersion };