diff --git a/configure b/configure index d622a6f0625a18..5695c6b45a9a7b 100755 --- a/configure +++ b/configure @@ -409,6 +409,11 @@ parser.add_option('--enable-static', dest='enable_static', help='build as static library') +parser.add_option('--no-target-type', + action='store_true', + dest='no_target_type', + help='do not compile and link node core') + parser.add_option('--no-browser-globals', action='store_true', dest='no_browser_globals', @@ -741,6 +746,9 @@ def configure_node(o): o['variables']['node_core_target_name'] = 'node_base' o['variables']['node_target_type'] = 'static_library' + if options.no_target_type: + o['variables']['node_target_type'] = 'none' + if target_arch in ('x86', 'x64', 'ia32', 'x32'): o['variables']['node_enable_v8_vtunejit'] = b(options.enable_vtune_profiling) elif options.enable_vtune_profiling: diff --git a/deps/platform_tools/README.md b/deps/platform_tools/README.md new file mode 100644 index 00000000000000..b9bfd4ac89fe9e --- /dev/null +++ b/deps/platform_tools/README.md @@ -0,0 +1,184 @@ +# platform-tools + +A toolchain to build and compile native dependencies with and for Node. + +[![Build Status](https://travis-ci.org/eljefedelrodeodeljefe/platform-tools.svg?branch=master)](https://travis-ci.org/eljefedelrodeodeljefe/platform-tools) [![Build status](https://ci.appveyor.com/api/projects/status/59q34ua3i457k27x?svg=true)](https://ci.appveyor.com/project/eljefederodeodeljefe/platform-tools) [![Join the chat at https://gitter.im/eljefedelrodeodeljefe/platform-tools](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/eljefedelrodeodeljefe/platform-tools?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +[![NPM](https://nodei.co/npm-dl/platform-tools.png?months=6&height=2)](https://nodei.co/npm/platform-tools/) + +## TL;DR + +> Compile C/C++ and native node addons with Node.js. Under the hood this is shelling +out to `gcc`, `clang` and `cl.exe` in a similar way `make` does. To mitigate `gyp` and +`autotools` dependencies node users (eventually) could use this. + +Assume a file `exit_with_1.c` + +```c +int main(int argc, char const* argv[]) { + return 1; +} +``` + +The below would be an example of emulating with Node.js + +```console +gcc -c exit_with_1 +gcc -o exit_with_1.o +./exit_with_1 +``` + +```js +const platform_tools = require('platform-tools') +const spawn = require('child_process').spawn + +let out = 'exit_with_1' +// first compile without linking +platform_tools.compile('exit_with_1.c', {output: `${out}.o`}, () => { + // then link the object file (here an easy case) + platform_tools.link(`${out}.o`, {output: out}, () => { + // now execute the compiled binary and expect the C-program to end + // with code 1 + const cp = spawn(out, [], {shell: true}); + cp.on('close', (code) => { + assert(code === 1), 'Compiled binary exit_with_1 must exit with code 1') + }) + }) +}) +``` + +## Implementation Status +| Method | implemented | +| --- | --- | +| .compile(source [,options, cb]) | **yes** | +| .compileAddon(source [,options, cb]) | **yes** | +| .link(object [,options, cb]) | **yes** | +| .config(library [,options, cb]) | **yes** | + + + +### Overview + +(TBD) + +Also this makes it easier for libarary authors and users, since compilation +output will either be stored into a user specified location or by default into +the current working directories `build/` directory (precisely +``` `${process.cwd()}/build` ```) + +### Technical Overview + +**Rquirements:** +* Node 4.5.0+ +* the default compiler for your system + +## Windows Users + +> Mote: since this repo wants to purposely increase Windows native addon usabilty +please share if you have a hard time. However ue to the Microsoft inherent SDK +and compiler strategy we need to assume prerequisites of you. + +* Windows SDK 10 standalone should be installed and in your %ProgramFiles(x86)% +* Visual Studio 2015 should be installed and in your %ProgramFiles(x86)% + +**For background:** To accomplish unix-like command-line behavior, e.g. +`gcc source_file.c -o source.exe && ./source.exe` we need to assume the location +of the most basic C/C++ headers in various locations on your Windows installation. +The `cl.exe` binary does not assume any search paths on it's own, if it is not +run through Visual Studio. Although that being quite a quirk for embedders and +library authors, Windows compiler support is as good as Unix'. + +## Platform + +This module is currently tested on: + +| Platform | 0.10 | 0.12 | 4.0 | 5.0 | 6.0 | +| --- | --- | --- | --- | ---| ---|---| +| Mac OS X | - | - | **yes** | **yes**| **yes** | +| BSDs| - | - | **yes** | **yes**| **yes** | +| Linux | - | - | **yes** | **yes** | **yes** | +| Windows | - | - | **yes** | **yes** | **yes** | + +## Roadmap + +* have more complex C/C++ files compile and link fully +* ~~make native addons build~~ +* make node build +* make v8 build +* override values that the lib takes as assumption +* gyp-file integration (chop-off comments and trailing commas -> then done?) +* more sophisticated Windows search path fallbacks for not optimal installatons + + +## API + + +## PlatformTools +**Kind**: global class + +* [PlatformTools](#PlatformTools) + * [.compile(source, cb)](#PlatformTools+compile) ⇒ Callback + * [.link(object, options, cb)](#PlatformTools+link) ⇒ Callback + * [.config(lib, cb)](#PlatformTools+config) ⇒ Callback + * [.compileAddon(addonSrcFile, options, cb)](#PlatformTools+compileAddon) ⇒ Callback + + + +### platformTools.compile(source, cb) ⇒ Callback +Compiles a given source code file or array of files to the platforms object +code. + +**Kind**: instance method of [PlatformTools](#PlatformTools) + +| Param | Type | Description | +| --- | --- | --- | +| source | String | Array.<String> | Path to source | +| cb | function | Optional callback for completion | + + + +### platformTools.link(object, options, cb) ⇒ Callback +Links mutiple objects and libraries to a binary. + +**Kind**: instance method of [PlatformTools](#PlatformTools) + +| Param | Type | Description | +| --- | --- | --- | +| object | String | Array.<String> | Path for name of object code file | +| options | Object | Options object | +| cb | function | Optional callback | + + + +### platformTools.config(lib, cb) ⇒ Callback +Returns the necessary libraries to link against, similarly to pkg-config(1). + +**Kind**: instance method of [PlatformTools](#PlatformTools) + +| Param | Type | Description | +| --- | --- | --- | +| lib | String | Library to search dependencies against | +| cb | function | Optional Callback upon completion | + + + +### platformTools.compileAddon(addonSrcFile, options, cb) ⇒ Callback +This method compiles node native addons end-to-end. Motivation behind this +high level approach is past struggles with this technique, and especially +different behaviors across platforms. Eventually this method should take +care of all of the above. If the user has special cases, it is still +possible to pass instructions via the options object and (item for roadmap) +override certain common variables forcefully. + +**Kind**: instance method of [PlatformTools](#PlatformTools) +**Returns**: Callback - returns optional callback + +| Param | Type | Description | +| --- | --- | --- | +| addonSrcFile | String | Path to source file | +| options | Object | Options object | +| cb | function | | + +## License + +MIT diff --git a/deps/platform_tools/index.js b/deps/platform_tools/index.js new file mode 100644 index 00000000000000..f7d4d30d95b28f --- /dev/null +++ b/deps/platform_tools/index.js @@ -0,0 +1,2 @@ +'use strict' +module.exports = require('./lib/platform_tools') diff --git a/deps/platform_tools/lib/compilers.js b/deps/platform_tools/lib/compilers.js new file mode 100644 index 00000000000000..d482a894e76888 --- /dev/null +++ b/deps/platform_tools/lib/compilers.js @@ -0,0 +1,77 @@ +'use strict' +/* + this allows platform dependent mapping of flags that relies on the result + of process.platform and process.arch + + logic is: + platform > compiler | linker | others > key of content + */ +module.exports = { + darwin: { + compiler: { + arch: (platform) => { + return { + arm: ['-arch', 'arm'], //TODO: this is likely not valid; doc is missing on AAPL gcc + ia32: ['-arch', 'i386'], + x64: ['-arch', 'x86_64'] + }[platform] + } + }, + linker: { + arch: (platform) => { + return { + arm: ['-arch', 'arm'], //TODO: this is likely not valid; doc is missing on AAPL gcc + ia32: ['-arch', 'i386'], + x64: ['-arch', 'x86_64'] + }[platform] + } + }, + osx_min_version: (version) => { + return [`-mmacosx-version-min=${version}`] + } + }, + freebsd: { + compiler: { + + }, + linker: { + + } + }, + linux: { + compiler: { + arch: (platform) => { + return { + arm: ['-marm'], // REVIEW + ia32: ['-m32'], + x64: ['-m64'] + }[platform] + } + }, + linker: { + arch: (platform) => { + return { + arm: ['-marm'], // REVIEW + ia32: ['-m32'], + x64: ['-m64'] + }[platform] + } + } + }, + sunos: { + compiler: { + + }, + linker: { + + } + }, + win32: { + compiler: { + + }, + linker: { + + } + } +} diff --git a/deps/platform_tools/lib/flags.js b/deps/platform_tools/lib/flags.js new file mode 100644 index 00000000000000..5dc62b1af483f7 --- /dev/null +++ b/deps/platform_tools/lib/flags.js @@ -0,0 +1,173 @@ +'use strict'; +let linkerArch = '' +switch (process.arch) { + case 'x64': linkerArch = 'X64'; break; + case 'ia32': linkerArch = 'X84'; break; + case 'arm': linkerArch = 'ARM'; break; +} + +exports.linux = { + +} + +exports.darwin = { + +} + +exports.windows = { + vsStdIncludePaths: [ + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\10\\Include\\10.0.10240.0\\ucrt`, //REVIEW + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0V140`, + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\bin\\x86`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\bin\\x64`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\bin\\arm`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\lib\\arm`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\lib\\winv6.3\\um\\x86`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\lib\\winv6.3\\um\\x64`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\lib\\winv6.3\\um\\arm`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\um`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\shared`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\winrt`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\atlmfc\\include`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\include`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\bin`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\lib`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\lib\\amd64`, + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0\V140`, + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\um`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\shared`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\winrt` + ], + vsStdLibPaths: [ + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\bin\\x86`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 11.0\\VC\\bin`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\10\\Lib\\10.0.10240.0\\ucrt\\x64`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\lib\\amd64`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\x64`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\10\\Include\\10.0.10240.0\\ucr`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\atlmfc\\include`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\include`, + `${process.env['ProgramFiles(x86)']}\\Microsoft Visual Studio 14.0\\VC\\include`, + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0V140`, + `${process.env['ProgramFiles(x86)']}\\MSBuild\\Microsoft.Cpp\\v4.0`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\um`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\shared`, + `${process.env['ProgramFiles(x86)']}\\Windows Kits\\8.1\\Include\\winrt`, + ] +} + +exports.addon = { + darwin: { + compiler_flags: [ + '-Os', + /*'-gdwarf-2' */, + '-mmacosx-version-min=10.7', + '-Wall', + '-Wall', + '-Wendif-labels', + '-W', + '-Wno-unused-parameter', + '-std=gnu++0x', + '-fno-rtti', + '-fno-exceptions', + '-fno-threadsafe-statics', + '-fno-strict-aliasing' + ], + linker_flags: [ + '-bundle', + '-undefined', + 'dynamic_lookup', + '-Wl,-no_pie', + '-Wl,-search_paths_first', + '-mmacosx-version-min=10.7' + ] + }, + linux: { + compiler_flags: [ + '-fPIC', + '-pthread', + '-Wall', + '-Wextra', + '-Wno-unused-parameter', + '-m64', + '-O3','-ffunction-sections', + '-fdata-sections', + '-fno-omit-frame-pointer', + '-fno-rtti', + '-fno-exceptions', + // NOTE: -std=c++0x is for gcc earlier than 4.7. In the future this might + // need to be checked, since 4.7 and 4.8 are more recent ones and may + // be used more widely + '-std=c++11' + ], + linker_flags: [ + '-shared', + '-pthread', + '-rdynamic', + '-m64', + '-Wl,--start-group', + /*${process.cwd()}/build/${options.output}.o`,*/ + '-Wl,--end-group' + ] + }, + windows: { + compiler_flags: [ + '/W3', + '/WX-', + '/Ox', + '/Ob2', + '/Oi', + '/Ot', + '/Oy', + '/GL', + '/D', 'WIN32', + '/D', '_CRT_SECURE_NO_DEPRECATE', + '/D', '_CRT_NONSTDC_NO_DEPRECATE', + '/D', '_HAS_EXCEPTIONS=0', + '/D', 'BUILDING_V8_SHARED=1', + '/D', 'BUILDING_UV_SHARED=1', + '/D', 'BUILDING_NODE_EXTENSION', + '/D', '_WINDLL', + '/GF', + '/Gm-', + '/MT', + '/GS', + '/Gy', + '/fp:precise', + '/Zc:wchar_t', + '/Zc:forScope', + '/Zc:inline', + '/GR-', + '/Gd', + '/TP', + '/wd4351', + '/wd4355', + '/wd4800', + '/wd4251', + '/errorReport:queue', + '/MP' + ], + linker_flags: [ + '/ERRORREPORT:QUEUE', + '/INCREMENTAL:NO', + 'kernel32.lib', 'user32.lib', 'gdi32.lib', 'winspool.lib ', 'comdlg32.lib', + 'advapi32.lib', 'shell32.lib', 'ole32.lib', 'oleaut32.lib', 'uuid.lib', 'odbc32.lib', + `${process.cwd()}\\build\\deps\\${process.versions.node}\\node.lib`, + // '/MANIFEST', + // `/MANIFESTUAC:"level='asInvoker' uiAccess='false'"`, + // '/manifest:embed', + '/MAP', '/MAPINFO:EXPORTS', + '/OPT:REF', + '/OPT:ICF', + '/LTCG', + '/TLBID:1', + '/DYNAMICBASE', + '/NXCOMPAT', + `/MACHINE:${linkerArch}`, + '/ignore:4199', + '/DLL' + ] + } +} diff --git a/deps/platform_tools/lib/node_platform.js b/deps/platform_tools/lib/node_platform.js new file mode 100644 index 00000000000000..9dab6a9321e407 --- /dev/null +++ b/deps/platform_tools/lib/node_platform.js @@ -0,0 +1,109 @@ +'use strict'; +const http = require('https'); +const fs = require('fs'); +const path = require('path'); +const zlib = require('zlib'); +// TODO: get rid of dependency +const tar = require('./tar'); + +const buildDirs = [ + `${process.cwd()}/build`, + `${process.cwd()}/build/deps`, + `${process.cwd()}/build/deps/${process.versions.node}`, + `${process.cwd()}/build/Release`, +] + +const r = process.release +const urls = [ {name: 'headers', url: r.headersUrl} ] +// only present on win32 +if (process.platform === 'win32') urls.push( {name: 'node.lib', url: r.libUrl} ) + + +class Addon { + constructor() { + this.buildDirs = buildDirs + this.urls = urls + this.downloadCount = this.urls.length + } + + _setupBuildDir(cb) { + this.buildDirs.forEach((dir) => { + try { + fs.mkdirSync(dir) + } catch (e) { + // ignore all errors + } + }) + process.nextTick(() => { return cb(null) }) + } + + _download(url, output, cb) { + let self = this + + const options = { + 'method': 'GET', + 'hostname': 'nodejs.org', + 'path': url, + 'headers': {} + }; + const req = http.request(options, function (res) { + if (path.parse(url).ext === '.gz') { + res + .pipe(zlib.createUnzip()) + .pipe(tar.extract(output, {strip: 1})) + .on('finish', () => { + // check output + fs.readdir(output, (err, files) => { + if (err || files.length <= 0) { + process.nextTick(() => { + return cb(err ? err : new Error('no files')) }) + } + self.downloadCount-- + + if (self.downloadCount <= 0) + process.nextTick(() => { return cb(null) }) + }); + }) + + res.on('error', (err) => { return process.nextTick(() => { cb(err) }) }); + } else { + res.pipe(fs.createWriteStream(output)).on('finish', () =>{ + self.downloadCount-- + if (self.downloadCount <= 0) { + process.nextTick(() => { return cb(null) }) + } + }) + } + + }) + req.end() + } + + + getDeps(cb) { + let count = this.urls.length + + this._setupBuildDir((err) => { + if (err) return cb(err) + // first check whether download is necessary + fs.readdir(`${process.cwd()}/build/deps/${process.versions.node}/headers/include/node`, (err, files) => { + // Ignore the error case here, since we'll then override. + // If the header dir has at least one file we assume it has them all + if (files && files.length > 1) + return process.nextTick(() => { cb(null) }) + + this.urls.forEach((el) => { + --count + this._download(el.url, `${process.cwd()}/build/deps/${process.versions.node}/${el.name}`, (err) => { + if (err) return cb(err) + // invoke callback only when all deps have been downloaded or err'd + if (count <= 0) + return process.nextTick(() => { return cb(null) }) + }) + }) + }) + }) + } +} + +module.exports = new Addon() diff --git a/deps/platform_tools/lib/pkg_config.js b/deps/platform_tools/lib/pkg_config.js new file mode 100644 index 00000000000000..1dc8ee30aede3e --- /dev/null +++ b/deps/platform_tools/lib/pkg_config.js @@ -0,0 +1,177 @@ +'use strict'; +const path = require('path') +const fs = require('fs') +const readline = require('readline') +const debuglog = require('util').debuglog('platform_tools') + +const pkgConfigSearchPaths = [ + '/usr/lib/pkgconfig', + '/usr/share/pkgconfig', + '/usr/local/lib/pkgconfig', + '/usr/local/share/pkgconfig' +] + +exports.pkgConfigSearchPaths = pkgConfigSearchPaths + +function parsePcLine(line) { + // brute force REVIEW + // Rationale: + // * match line if it has ': ' or '=' delimiter via split + // * both cases get first result as key for the result + // * Libs, Cflags, Requires (all colon-case) are special cases, since they + // will have multiple values that are space delimited + // * those will be split up into an array and checked against, if the array + // has empty elements and hence those exluded if so + // + const hasColon = line.split(': ') + const hasEqual= line.split('=') + + let key + let values + if (hasColon.length > 1) { + key = hasColon[0] + values = hasColon[1] + } else if (hasEqual.length > 1) { + key = hasEqual[0] + values = hasEqual[1] + } + + if (key === 'Libs' || key === 'Cflags' || key === 'Requires') { + values = values.split(' ') + values = values.filter((val) => { + if (!(val === '')) return val + }) + } + + return { + key: key, + values: values + } +} + +function resolveVariables(result) { + // brute force REVIEW + // Rationale: + // * check if current result is in array or string format + // * for both take the (element-level) string and check if it has + // a variable include (checks only for one REVIEW) + // * string replace include with the information as key of the result object, + // which assumes that the object has a declartion of that variable already + // * not recursive + // + const re = /(\$\{([^)]+)\})/ // matches ${} brackets and its inside + for (var key in result) { + if (typeof result[key] === 'string') { + let m + if ((m = re.exec(result[key])) !== null) { + if (m.index === re.lastIndex) + re.lastIndex++ + if (m[0]) + result[key] = result[key].replace(m[0], result[m[2]]) + } + } else if (result[key] && result[key].constructor === Array) { + let res = [] + result[key].forEach((el) => { + let m + // just regular regex stuff here, REVIEW: verbose + if ((m = re.exec(el)) !== null) { + if (m.index === re.lastIndex) + re.lastIndex++ + if (m[0]) + res.push(el.replace(m[0], result[m[2]])) // insert value with key + else + res.push(el) // fall through if regex actually hasn't matched a string + } else { + res.push(el) // fall through if regex does not variable include + } + }) + result[key] = res + } + } + return result +} + +// internal doc taken from man plg-config: +// +// The pkg-config program is used to retrieve information about installed +// libraries in the system. It is typically used to compile and link against +// one or more libraries. Here is a typical usage scenario in a Makefile: +// +// program: program.c +// cc program.c $(pkg-config --cflags --libs gnomeui) +// +// pkg-config retrieves information about packages from special metadata files. +// These files are named after the package, and has a .pc extension. On most +// systems, pkg-config looks in /usr/lib/pkgconfig, /usr/share/pkgconfig, +// /usr/local/lib/pkgconfig and /usr/local/share/pkgconfig for these files. It +// will additionally look in the colon-separated (on Windows, semicolon-sepa- +// rated) list of directories specified by the PKG_CONFIG_PATH environment +// variable. +// +// The package name specified on the pkg-config command line is defined to be +// the name of the metadata file, minus the .pc extension. If a library can +// install multiple versions simultaneously, it must give each version its own +// name (for example, GTK 1.2 might have the package name "gtk+" while GTK 2.0 +// has "gtk+-2.0"). +// +// In addition to specifying a package name on the command line, the full path +// to a given .pc file may be given instead. This allows a user to directly +// query a particular .pc file. +// +exports.config = (lib, cb) => { + const parseTargets = [] + // template for pkg-config-like output + let result = { + prefix: null, + exec_prefix: null, + libdir: null, + includedir: null, + name: null, + description: null, + version: null, + requires: null, // array + libs: null, // array + cflags: null // array + } + + let searchPaths = pkgConfigSearchPaths + // if user specifies a path, include into the search paths + // will be git first in case it is a file. If not, falls back to defaults + if (lib.indexOf(path.sep) >= 0) { + const file = path.parse(lib) + if (file.ext !== '.pc') + return process.nextTick(() => { cb(new InputError('File must have .pc extension.'))}) + searchPaths.unshift(file.dir) + lib = file.name + } + + searchPaths.forEach((pathToLib) => { + let target = `${pathToLib}${path.sep}${lib}.pc` + let res + try { + res = fs.statSync(target) + } catch(e) { + // ignore ENOENTs; check will be done below + } + if (res && res.isFile()) parseTargets.push(target) + }) + // bail when no .pc file was found REVIEW + if (parseTargets.length === 0) + return process.nextTick(() => { cb(null, null) }) + + if (parseTargets.length > 1) + debuglog('Found more than one .pc file. Only using first one found') + + const runner = readline.createInterface({ input: fs.createReadStream(parseTargets[0]) }) + runner.on('line', (line) => { + const res = parsePcLine(line) + if (res && res.key) { + result[res.key.toLowerCase()] = res.values + } + }) + runner.on('error', (err) => { return process.nextTick(() => { cb(err) }) }) + runner.on('close', () => { + result = resolveVariables(result) + return process.nextTick(() => { cb(null, result) }) + }) +} diff --git a/deps/platform_tools/lib/platform_tools.js b/deps/platform_tools/lib/platform_tools.js new file mode 100644 index 00000000000000..1bbc631e8413f1 --- /dev/null +++ b/deps/platform_tools/lib/platform_tools.js @@ -0,0 +1,425 @@ +'use strict' +const spawn = require('child_process').spawn +const EventEmitter = require('events') +const path = require('path') +const fs = require('fs') +const debuglog = require('util').debuglog('platform_tools') + +const addon = require('./node_platform') +const compilerUtil = require('./compilers') +const pkgConfig = require('./pkg_config') +const pkgConfigSearchPaths = pkgConfig.pkgConfigSearchPaths +const flags = require('./flags') + +const DEBUG = process.env.NODE_DEBUG === 'platform_tools' ? true : false + +// default, can be extended only in the class +const compilers = { + win32: '"%programfiles(x86)%\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\cl.exe"', + freeBSD: 'clang', + darwin: 'clang', + linux: 'gcc', + sunos: 'gcc' // REVIEW +} + +function shouldBeSyncAPI(a, b) { + if (a && b) return b + if (!a && !b) return true + if (!b && (a !== null && typeof a === 'object')) return true + if (!b && typeof a === 'function') return a +} + +/** + * @class + */ +class PlatformTools extends EventEmitter { + constructor(options) { + super() + this.options = Object.assign({}, options) + + this.pkgConfigSearchPaths = pkgConfigSearchPaths + this.cc = compilers[process.platform] + // branch for g++ on linux + if (process.platform === 'linux') this.cxx = 'g++' + else if (process.platform === 'darwin' || process.platform === 'freeBSD') + this.cxx = 'clang++' + else this.cxx = compilers[process.platform] + // defaul to cc but keep reference here, for passing state, say, to .link + this.compiler = this.cc + + this.options.silent = this.options.silent || false + // utilities for .compileAdddon() + this.downloading = false // lock for compileAddon + this.deferedAddonCompilationCBs = [] + } + + /** + * Compiles a given source code file or array of files to the platforms object + * code. + * @param {String|String[]} source Path to source + * @param {Function} cb Optional callback for completion + * @return {Callback} + */ + compile(sources, opts, cb) { + const options = Object.assign({}, opts) + const sync = shouldBeSyncAPI(options, cb) + + if (typeof sources === 'string' || sources instanceof String) { + return this._compile(sources, opts, cb) + + } else if (Array.isArray(sources) && sources.length > 0) { + let opts = options + let count = sources.length + const files = [] + opts.output = undefined + + sources.forEach((source) => { + this._compile(source, opts, (err, file) => { + --count + if (err) return process.nextTick(() => { cb(err) }) + files.push(file) + + if (count === 0) + process.nextTick(() => { return cb(null, files) }) + }) + }) + + } else { + const err = new Error('First argument must be a string or array of strings\ + \r to source files.') + if (sync) throw err + else return cb(err) + } + } + + _compile(source, opts, cb) { + // forward declarations and input handling + const silent = this.options.silent + const options = Object.assign({}, opts) + + const sync = shouldBeSyncAPI(options, cb) + if (sync !== true) cb = sync + + if (!source) + throw new Error('Function expects a source file as first argument') + + // parsing inputs + const ext = path.extname(source) + if (!(ext === '.c' || ext === '.cc' || ext === '.cpp')) + throw new Error('Source file must have .c, .cc or .cpp extension') + + // sets compiler to cxx for subsequent calls to .link and others + if (ext === '.cc' || ext === '.cpp') this.compiler = this.cxx + + const args = [] + if (process.platform === 'win32') args.push('/nologo') + // -D / /D flags + if (opts.defines) { + const isNode4 = process.versions.node[0] <= 4 // v4 quirk: doesn't properply escape ''-DFLAG_IS_SET'' + options.defines.forEach((define) => { + if (process.platform === 'win32') + args.push(`${isNode4 ? '' : '\''}/D${define}${isNode4 ? '' : '\''}`) + else + args.push(`${isNode4 ? '' : '\''}-D${define}${isNode4 ? '' : '\''}`) + }) + } + // add seach paths for the compiler + if (opts.include_headers) { + options.include_headers.forEach((el) => { + if (process.platform === 'win32') + args.push(`/I"${el}"`) + else + args.push(`-I${el}`) + }) + } + // windows quirk: VS hides away standard include paths. For that reason we + // need to push those manually + if (process.platform === 'win32') + flags.windows.vsStdIncludePaths.forEach((el) => { args.push(`/I"${el}"`) }) + + // adds unrestricted compiler flags, such as optimizations + if (opts.compiler_flags) { + options.compiler_flags.forEach((el) => { + if (process.platform === 'win32') + args.push(el) // not implemented + else + args.push(`${el}`) + }) + } + + args.push(process.platform === 'win32' ? '/c' : '-c') // compile, only + // specify minimal output, REVIEW: refactor verbosity + let filename = '' + if (options.output) + filename = options.output + else + filename = `${process.cwd()}/build/${path.parse(source).name}` + + if (opts.output) { + if (process.platform === 'win32') { + args.push(`/Fo${filename}`) + } else { + args.push('-o') + args.push(`${filename}`) + } + } else { + if (process.platform === 'win32') { + args.push(`/Fo${filename}.o`) + } else { + args.push('-o') + args.push(`${filename}.o`) + } + } + // necessary input file + args.push(source) + + debuglog(this.compiler, args) + // actual entry point + const runner = spawn(this.compiler, args, {shell: true}); + runner.stderr.on('data', (data) => { + process.stdout.write(data) + }); + runner.on('error', (err) => { return cb(err) }) + runner.on('exit', (code) => { + if (code != 0) { + return cb(new Error('compiler suffered errors')) + } + // REVIEW: normalizing extension seems to be hack from APi inconsistency + // between .compileAddon's and .compile's options + const ext = path.parse(filename).ext === '.o' ? '' : '.o' + return process.nextTick(() => { cb(null, `${filename}${ext}`) }) + }); + } + + /** + * Links mutiple objects and libraries to a binary. + * @param {String|String[]} object Path for name of object code file + * @param {Object} options Options object + * @param {Function} cb Optional callback + * @return {Callback} + */ + link(objectFiles, opts, cb) { + // unlike _compile there will be no API handling here for now. + // The array as input case is handled in _link itself due to how the + // compilers take inputs + this._link(objectFiles, opts, cb) + } + + _link(objectFiles, opts, cb) { + const silent = this.options.silent + const options = Object.assign({}, opts) + + const sync = shouldBeSyncAPI(options, cb) + if (sync !== true) cb = sync + + const args = [] + + let filename = '' + // necessary object file(s) + // NOTE: this branches for the array or single string case for the linker. + // The former will emulate `gcc file_1.o file_2.o -o somenewname` + if (typeof objectFiles === 'string' || objectFiles instanceof String) { + args.push(objectFiles) + // specify minimal output + filename = `${process.cwd()}/build/${path.parse(objectFiles).name}` + } else if (Array.isArray(objectFiles) && objectFiles.length > 0) { + objectFiles.forEach((objectFile) => { args.push(objectFile) }) + // NOTE: there is no fallback for when options.output is not defined. + // This would need to be handled here + filename = `${options.output}` + } + // on win32 everything herafter is a linker flag + if (process.platform === 'win32') + args.push('/link') + // library lookup paths from options object + if (options && options.include_libraries) { + options.include_libraries.forEach((el) => { + if (process.platform === 'win32') + args.push("/LIBPATH:" + '\"' + el + '\"') + else + args.push(`-L${el}`) + }) + } + // additional linker flags from options object + if (options && options.linker_flags) { + options.linker_flags.forEach((el) => { + if (process.platform === 'win32') + args.push(el) + else + args.push(`${el}`) + }) + } + + if (process.platform === 'win32') + flags.windows.vsStdLibPaths.forEach((el) => { args.push("/LIBPATH:" + '\"' + el + '\"') }) + // output name. NOTE: on windows the output is a linker flag and needs to + // succeed it. The other platforms do not have a dedicated linker flag, + // though in their cases one could use the `ld` binary. Here we are just + // aware of the fact and let the compiler binary figure that out + if (options && options.output) { + if (process.platform === 'win32') { + args.push(`/OUT:${filename}${opts.isWinAddon ? '.node' : '.exe'}`) + } else { + args.push('-o') + args.push(`${options.output}`) + } + } else { + if (process.platform === 'win32') { + args.push(`/OUT:${filename}${opts.isWinAddon ? '.node' : '.exe'}`) + } else { + args.push('-o') + args.push(`${filename}`) + } + } + // some situations demand flags at the end position, i.e. gcc for .a libraries + if (options && options.tail_flags) { + options.tail_flags.forEach((el) => { + if (process.platform === 'win32') + args.push(el) + else + args.push(`${el}`) + }) + } + // + let cwd = undefined + if (opts.isWinAddon) cwd = `${process.cwd()}/build/` + + debuglog(this.compiler, args) + // actual entry point: shelling out to the compiler + const runner = spawn(this.compiler, args, {shell: true, cwd: cwd}); + runner.stdout.on('data', (data) => { + if (!silent) + process.stdout.write(data) + }); + runner.on('error', (err) => { return cb(err) }) + runner.on('exit', (code) => { + if (code != 0) { + return process.nextTick(() => { cb(new Error('linker suffered errors')) }) + } + return process.nextTick(() => { cb(null, filename) }) + }); + } + + /** + * Returns the necessary libraries to link against, similarly to pkg-config(1). + * @param {String} lib Library to search dependencies against + * @param {Function} cb Optional Callback upon completion + * @return {Callback} + */ + config(lib, cb) { + return pkgConfig.config(lib, cb) + } + /** + * This method compiles node native addons end-to-end. Motivation behind this + * high level approach is past struggles with this technique, and especially + * different behaviors across platforms. Eventually this method should take + * care of all of the above. If the user has special cases, it is still + * possible to pass instructions via the options object and (item for roadmap) + * override certain common variables forcefully. + * + * @param {String} addonSrcFile Path to source file + * @param {Object} options Options object + * @param {Function} cb + * @return {Callback} returns optional callback + */ + compileAddon(addonSrcFile, options, cb) { + // checks for existing deps. + fs.readdir(`${process.cwd()}/build/deps/${process.versions.node}/headers/include/node`, (err, files) => { + if (!this.downloading && (err || files.length <= 0)) { + this.downloading = true // lock here, for the multi addon per project case + + return addon.getDeps((err) => { + this.downloading = false + if (err) return cb(err) + // execute all deferred functions + if (this.deferedAddonCompilationCBs.length > 0) + this.deferedAddonCompilationCBs.forEach((exe) => { exe() }) + // proceeed normally + this._compileAddon(addonSrcFile, options, (err) => { + process.nextTick(() => { + if (err) + return cb(err) + return cb(null) + }) + }) + }) + } + // compile when header files are already present + // NOTE: this function should only be called when headers are present. If + // we arrive here we are pushing it to a class variable and let .getDeps() + // callback execute those functions. + // The conequence of not doing this are real flaky race conditions. + const exe = () => { + this._compileAddon(addonSrcFile, options, (err) => { + process.nextTick(() => { + if (err) + return cb(err) + return cb(null) + }) + }) + } + // determine state of the module and decide to defer execution + if (this.downloading) + this.deferedAddonCompilationCBs.push(exe) + else + exe() + }); + } + + _compileAddon(addonSrcFile, options, cb) { + let opt = { + output: `${process.cwd()}/build/${options.output}.o`, + include_headers: [ + `${process.cwd()}/build/deps/${process.versions.node}/headers/include/node`, + `${process.cwd()}/node_modules/nan` + ], + compiler_flags: [] + } + // the following bumps flags and deps on a per platform basis. No magic. + if (process.platform === 'darwin') { + opt.compiler_flags = flags.addon.darwin.compiler_flags + } else if (process.platform === 'linux') { + opt.compiler_flags = flags.addon.linux.compiler_flags + opt.compiler_flags.push(`-Wl,-soname=${options.output}.node`) + } else if (process.platform === 'win32') { + opt.compiler_flags = flags.addon.windows.compiler_flags + } + + this.compile(addonSrcFile, opt, (err, files) => { + if (err) return cb(err.toString()) + // then link the object file (here an easy case) + let opt = { + output: `${process.cwd()}/build/${options.output}.node`, + include_libraries: [ + `${process.cwd()}/build/Release` + ], + linker_flags: [] + } + + if (process.platform === 'darwin') { + opt.linker_flags = flags.addon.darwin.linker_flags + } else if (process.platform === 'linux') { + opt.linker_flags = flags.addon.linux.linker_flags + // REVIEW: the above won't compile. Have considered trailing + // flag mechanism, but works without. Review this asap + } else if (process.platform === 'win32') { + opt.linker_flags = flags.addon.windows.linker_flags + } + + // NOTE: quirk b/c unix executables don't have ending, win have, exlude + // it manually when building addons + if (process.platform === 'win32') opt.isWinAddon = true + + this.link(files, opt, (err) => { + if (err) return cb(err) + + process.nextTick(() => { return cb(null) }) + }) + }) + } +} + + +module.exports = new PlatformTools() +module.exports.PlatformTools = PlatformTools +module.exports.compilerUtil = compilerUtil diff --git a/deps/platform_tools/lib/tar/index.js b/deps/platform_tools/lib/tar/index.js new file mode 100644 index 00000000000000..016372d994edea --- /dev/null +++ b/deps/platform_tools/lib/tar/index.js @@ -0,0 +1,205 @@ +/* + widely based on and copied from: + * https://github.com/mafintosh/tar-stream + * https://github.com/mafintosh/tar-fs + * https://github.com/rvagg/bl + * https://github.com/mafintosh/end-of-stream + * https://github.com/mafintosh/pump + * https://github.com/substack/node-mkdirp + */ +var tar = require('./tar-stream') +var pump = require('./pump') +var fs = require('fs') +var path = require('path') +var os = require('os') + +var win32 = os.platform() === 'win32' + +var noop = function () {} + +var echo = function (name) { + return name +} + +var normalize = !win32 ? echo : function (name) { + return name.replace(/\\/g, '/').replace(/:/g, '_') +} + + +var strip = function (map, level) { + return function (header) { + header.name = header.name.split('/').slice(level).join('/') + if (header.linkname) header.linkname = header.linkname.split('/').slice(level).join('/') + return map(header) + } +} + +var head = function (list) { + return list.length ? list[list.length - 1] : null +} + +var processGetuid = function () { + return process.getuid ? process.getuid() : -1 +} + +var processUmask = function () { + return process.umask ? process.umask() : 0 +} + +exports.extract = function (cwd, opts) { + if (!cwd) cwd = '.' + if (!opts) opts = {} + + var xfs = opts.fs || fs + var ignore = opts.ignore || opts.filter || noop + var map = opts.map || noop + var mapStream = opts.mapStream || echo + var own = opts.chown !== false && !win32 && processGetuid() === 0 + var extract = opts.extract || tar.extract() + var stack = [] + var now = new Date() + var umask = typeof opts.umask === 'number' ? ~opts.umask : ~processUmask() + var dmode = typeof opts.dmode === 'number' ? opts.dmode : 0 + var fmode = typeof opts.fmode === 'number' ? opts.fmode : 0 + var strict = opts.strict !== false + + if (opts.strip) map = strip(map, opts.strip) + + if (opts.readable) { + dmode |= parseInt(555, 8) + fmode |= parseInt(444, 8) + } + if (opts.writable) { + dmode |= parseInt(333, 8) + fmode |= parseInt(222, 8) + } + + var utimesParent = function (name, cb) { // we just set the mtime on the parent dir again everytime we write an entry + var top + while ((top = head(stack)) && name.slice(0, top[0].length) !== top[0]) stack.pop() + if (!top) return cb() + xfs.utimes(top[0], now, top[1], cb) + } + + var utimes = function (name, header, cb) { + if (opts.utimes === false) return cb() + + if (header.type === 'directory') return xfs.utimes(name, now, header.mtime, cb) + if (header.type === 'symlink') return utimesParent(name, cb) // TODO: how to set mtime on link? + + xfs.utimes(name, now, header.mtime, function (err) { + if (err) return cb(err) + utimesParent(name, cb) + }) + } + + var chperm = function (name, header, cb) { + var link = header.type === 'symlink' + var chmod = link ? xfs.lchmod : xfs.chmod + var chown = link ? xfs.lchown : xfs.chown + + if (!chmod) return cb() + chmod(name, (header.mode | (header.type === 'directory' ? dmode : fmode)) & umask, function (err) { + if (err) return cb(err) + if (!own) return cb() + if (!chown) return cb() + chown(name, header.uid, header.gid, cb) + }) + } + + extract.on('entry', function (header, stream, next) { + header = map(header) || header + header.name = normalize(header.name) + var name = path.join(cwd, path.join('/', header.name)) + + if (ignore(name, header)) { + stream.resume() + return next() + } + + var stat = function (err) { + if (err) return next(err) + utimes(name, header, function (err) { + if (err) return next(err) + if (win32) return next() + chperm(name, header, next) + }) + } + + var onfile = function () { + var ws = xfs.createWriteStream(name) + var rs = mapStream(stream, header) + + ws.on('error', function (err) { // always forward errors on destroy + rs.destroy(err) + }) + + pump(rs, ws, function (err) { + if (err) return next(err) + ws.on('close', stat) + }) + } + + if (header.type === 'directory') { + stack.push([name, header.mtime]) + return mkdirp(name, {fs: xfs}, stat) + } + + xfs.mkdir(path.dirname(name), function (err) { + if (err && err.code != 'EEXIST') return next(err) + + switch (header.type) { + case 'file': return onfile() + } + + if (strict) return next(new Error('unsupported type for ' + name + ' (' + header.type + ')')) + + stream.resume() + next() + }) + }) + + return extract +} + +var _0777 = parseInt('0777', 8); + +function mkdirp (p, opts, f, made) { + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirp(path.dirname(p), opts, function (er, made) { + if (er) cb(er, made); + else mkdirp(p, opts, cb, made); + }); + break; + + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} diff --git a/deps/platform_tools/lib/tar/pump.js b/deps/platform_tools/lib/tar/pump.js new file mode 100644 index 00000000000000..9009b2fcecb5e6 --- /dev/null +++ b/deps/platform_tools/lib/tar/pump.js @@ -0,0 +1,146 @@ +var fs = require('fs') // we only need fs to get the ReadStream and WriteStream prototypes + +var noop = function () {} + +var isFn = function (fn) { + return typeof fn === 'function' +} + +var isFS = function (stream) { + return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close) +} + +var isRequest = function (stream) { + return stream.setHeader && isFn(stream.abort) +} + +var destroyer = function (stream, reading, writing, callback) { + callback = callback + + var closed = false + stream.on('close', function () { + closed = true + }) + + eos(stream, {readable: reading, writable: writing}, function (err) { + if (err) return callback(err) + closed = true + callback() + }) + + var destroyed = false + return function (err) { + if (closed) return + if (destroyed) return + destroyed = true + + if (isFS(stream)) return stream.close() // use close for fs streams to avoid fd leaks + if (isRequest(stream)) return stream.abort() // request.destroy just do .end - .abort is what we want + + if (isFn(stream.destroy)) return stream.destroy() + + callback(err || new Error('stream was destroyed')) + } +} + +var call = function (fn) { + fn() +} + +var pipe = function (from, to) { + return from.pipe(to) +} + +var pump = function () { + var streams = Array.prototype.slice.call(arguments) + var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop + + if (Array.isArray(streams[0])) streams = streams[0] + if (streams.length < 2) throw new Error('pump requires two streams per minimum') + + var error + var destroys = streams.map(function (stream, i) { + var reading = i < streams.length - 1 + var writing = i > 0 + return destroyer(stream, reading, writing, function (err) { + if (!error) error = err + if (err) destroys.forEach(call) + if (reading) return + destroys.forEach(call) + callback(error) + }) + }) + + return streams.reduce(pipe) +} + +var noop = function() {}; + +var isRequest = function(stream) { + return stream.setHeader && typeof stream.abort === 'function'; +}; + +var isChildProcess = function(stream) { + return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3 +}; + +var eos = function(stream, opts, callback) { + if (typeof opts === 'function') return eos(stream, null, opts); + if (!opts) opts = {}; + + callback = callback || noop + + var ws = stream._writableState; + var rs = stream._readableState; + var readable = opts.readable || (opts.readable !== false && stream.readable); + var writable = opts.writable || (opts.writable !== false && stream.writable); + + var onlegacyfinish = function() { + if (!stream.writable) onfinish(); + }; + + var onfinish = function() { + writable = false; + if (!readable) callback(); + }; + + var onend = function() { + readable = false; + if (!writable) callback(); + }; + + var onexit = function(exitCode) { + callback(exitCode ? new Error('exited with error code: ' + exitCode) : null); + }; + + var onclose = function() { + if (readable && !(rs && rs.ended)) return callback(new Error('premature close')); + if (writable && !(ws && ws.ended)) return callback(new Error('premature close')); + }; + + var onrequest = function() { + stream.req.on('finish', onfinish); + }; + + if (isRequest(stream)) { + stream.on('complete', onfinish); + stream.on('abort', onclose); + if (stream.req) onrequest(); + else stream.on('request', onrequest); + } else if (writable && !ws) { // legacy streams + stream.on('end', onlegacyfinish); + stream.on('close', onlegacyfinish); + } + + if (isChildProcess(stream)) stream.on('exit', onexit); + + stream.on('end', onend); + stream.on('finish', onfinish); + if (opts.error !== false) stream.on('error', callback); + stream.on('close', onclose); + return function() { + + }; +}; + +module.exports = pump diff --git a/deps/platform_tools/lib/tar/tar-stream.js b/deps/platform_tools/lib/tar/tar-stream.js new file mode 100644 index 00000000000000..63b7e837fb8a8d --- /dev/null +++ b/deps/platform_tools/lib/tar/tar-stream.js @@ -0,0 +1,504 @@ +var util = require('util') +var bl = BufferList + +var Writable = require('stream').Writable +var PassThrough = require('stream').PassThrough + +var noop = function () {} + +var overflow = function (size) { + size &= 511 + return size && 512 - size +} + +var emptyStream = function (self, offset) { + var s = new Source(self, offset) + s.end() + return s +} + +var mixinPax = function (header, pax) { + if (pax.path) header.name = pax.path + if (pax.linkpath) header.linkname = pax.linkpath + header.pax = pax + return header +} + +var Source = function (self, offset) { + this._parent = self + this.offset = offset + PassThrough.call(this) +} + +util.inherits(Source, PassThrough) + +Source.prototype.destroy = function (err) { + this._parent.destroy(err) +} + +var Extract = function (opts) { + if (!(this instanceof Extract)) return new Extract(opts) + Writable.call(this, opts) + + this._offset = 0 + this._buffer = bl() + this._missing = 0 + this._onparse = noop + this._header = null + this._stream = null + this._overflow = null + this._cb = null + this._locked = false + this._destroyed = false + this._pax = null + this._paxGlobal = null + this._gnuLongPath = null + this._gnuLongLinkPath = null + + var self = this + var b = self._buffer + + var oncontinue = function () { + self._continue() + } + + var onunlock = function (err) { + self._locked = false + if (err) return self.destroy(err) + if (!self._stream) oncontinue() + } + + var onstreamend = function () { + self._stream = null + var drain = overflow(self._header.size) + if (drain) self._parse(drain, ondrain) + else self._parse(512, onheader) + if (!self._locked) oncontinue() + } + + var ondrain = function () { + self._buffer.consume(overflow(self._header.size)) + self._parse(512, onheader) + oncontinue() + } + + var onpaxglobalheader = function () { + var size = self._header.size + self._paxGlobal = headers.decodePax(b.slice(0, size)) + b.consume(size) + onstreamend() + } + + var onpaxheader = function () { + var size = self._header.size + self._pax = headers.decodePax(b.slice(0, size)) + if (self._paxGlobal) self._pax = Object.assign(self._paxGlobal, self._pax) + b.consume(size) + onstreamend() + } + + var ongnulongpath = function () { + var size = self._header.size + this._gnuLongPath = headers.decodeLongPath(b.slice(0, size)) + b.consume(size) + onstreamend() + } + + var ongnulonglinkpath = function () { + var size = self._header.size + this._gnuLongLinkPath = headers.decodeLongPath(b.slice(0, size)) + b.consume(size) + onstreamend() + } + + var onheader = function () { + var offset = self._offset + var header + try { + header = self._header = headers.decode(b.slice(0, 512)) + } catch (err) { + self.emit('error', err) + } + b.consume(512) + + if (!header) { + self._parse(512, onheader) + oncontinue() + return + } + if (header.type === 'gnu-long-path') { + self._parse(header.size, ongnulongpath) + oncontinue() + return + } + if (header.type === 'gnu-long-link-path') { + self._parse(header.size, ongnulonglinkpath) + oncontinue() + return + } + if (header.type === 'pax-global-header') { + self._parse(header.size, onpaxglobalheader) + oncontinue() + return + } + if (header.type === 'pax-header') { + self._parse(header.size, onpaxheader) + oncontinue() + return + } + + if (self._gnuLongPath) { + header.name = self._gnuLongPath + self._gnuLongPath = null + } + + if (self._gnuLongLinkPath) { + header.linkname = self._gnuLongLinkPath + self._gnuLongLinkPath = null + } + + if (self._pax) { + self._header = header = mixinPax(header, self._pax) + self._pax = null + } + + self._locked = true + + if (!header.size) { + self._parse(512, onheader) + self.emit('entry', header, emptyStream(self, offset), onunlock) + return + } + + self._stream = new Source(self, offset) + + self.emit('entry', header, self._stream, onunlock) + self._parse(header.size, onstreamend) + oncontinue() + } + + this._parse(512, onheader) +} + +util.inherits(Extract, Writable) + +Extract.prototype.destroy = function (err) { + if (this._destroyed) return + this._destroyed = true + + if (err) this.emit('error', err) + this.emit('close') + if (this._stream) this._stream.emit('close') +} + +Extract.prototype._parse = function (size, onparse) { + if (this._destroyed) return + this._offset += size + this._missing = size + this._onparse = onparse +} + +Extract.prototype._continue = function () { + if (this._destroyed) return + var cb = this._cb + this._cb = noop + if (this._overflow) this._write(this._overflow, undefined, cb) + else cb() +} + +Extract.prototype._write = function (data, enc, cb) { + if (this._destroyed) return + + var s = this._stream + var b = this._buffer + var missing = this._missing + + // we do not reach end-of-chunk now. just forward it + + if (data.length < missing) { + this._missing -= data.length + this._overflow = null + if (s) return s.write(data, cb) + b.append(data) + return cb() + } + + // end-of-chunk. the parser should call cb. + + this._cb = cb + this._missing = 0 + + var overflow = null + if (data.length > missing) { + overflow = data.slice(missing) + data = data.slice(0, missing) + } + + if (s) s.end(data) + else b.append(data) + + this._overflow = overflow + this._onparse() +} + +var DuplexStream = require('stream').Duplex + , util = require('util') + + +function BufferList (callback) { + if (!(this instanceof BufferList)) + return new BufferList(callback) + + this._bufs = [] + this.length = 0 + + DuplexStream.call(this) +} +util.inherits(BufferList, DuplexStream) + + +BufferList.prototype._offset = function _offset (offset) { + var tot = 0, i = 0, _t + for (; i < this._bufs.length; i++) { + _t = tot + this._bufs[i].length + if (offset < _t) + return [ i, offset - tot ] + tot = _t + } +} + + +BufferList.prototype.append = function append (buf) { + var i = 0 + , newBuf + + if (buf != null) { + // coerce number arguments to strings, since Buffer(number) does + // uninitialized memory allocation + if (typeof buf == 'number') + buf = buf.toString() + + newBuf = Buffer.isBuffer(buf) ? buf : new Buffer(buf) + this._bufs.push(newBuf) + this.length += newBuf.length + } + + return this +} + + +BufferList.prototype.slice = function slice (start, end) { + return this.copy(null, 0, start, end) +} + + +BufferList.prototype.copy = function copy (dst, dstStart, srcStart, srcEnd) { + if (typeof srcStart != 'number' || srcStart < 0) + srcStart = 0 + if (typeof srcEnd != 'number' || srcEnd > this.length) + srcEnd = this.length + if (srcStart >= this.length) + return dst || new Buffer(0) + if (srcEnd <= 0) + return dst || new Buffer(0) + + var copy = !!dst + , off = this._offset(srcStart) + , len = srcEnd - srcStart + , bytes = len + , bufoff = (copy && dstStart) || 0 + , start = off[1] + , l + , i + + // copy/slice everything + if (srcStart === 0 && srcEnd == this.length) { + if (!copy) // slice, just return a full concat + return Buffer.concat(this._bufs) + + // copy, need to copy individual buffers + for (i = 0; i < this._bufs.length; i++) { + this._bufs[i].copy(dst, bufoff) + bufoff += this._bufs[i].length + } + + return dst + } + + // easy, cheap case where it's a subset of one of the buffers + if (bytes <= this._bufs[off[0]].length - start) { + return copy + ? this._bufs[off[0]].copy(dst, dstStart, start, start + bytes) + : this._bufs[off[0]].slice(start, start + bytes) + } + + if (!copy) // a slice, we need something to copy in to + dst = new Buffer(len) + + for (i = off[0]; i < this._bufs.length; i++) { + l = this._bufs[i].length - start + + if (bytes > l) { + this._bufs[i].copy(dst, bufoff, start) + } else { + this._bufs[i].copy(dst, bufoff, start, start + bytes) + break + } + + bufoff += l + bytes -= l + + if (start) + start = 0 + } + + return dst +} + +BufferList.prototype.consume = function consume (bytes) { + while (this._bufs.length) { + if (bytes >= this._bufs[0].length) { + bytes -= this._bufs[0].length + this.length -= this._bufs[0].length + this._bufs.shift() + } else { + this._bufs[0] = this._bufs[0].slice(bytes) + this.length -= bytes + break + } + } + return this +} + + +var ZEROS = '0000000000000000000' +var SEVENS = '7777777777777777777' +var ZERO_OFFSET = '0'.charCodeAt(0) +var USTAR = 'ustar\x0000' +var MASK = parseInt('7777', 8) + +var clamp = function (index, len, defaultValue) { + if (typeof index !== 'number') return defaultValue + index = ~~index // Coerce to integer. + if (index >= len) return len + if (index >= 0) return index + index += len + if (index >= 0) return index + return 0 +} + +var toType = function (flag) { + switch (flag) { + case 0: + return 'file' + case 1: + return 'link' + case 2: + return 'symlink' + case 3: + return 'character-device' + case 4: + return 'block-device' + case 5: + return 'directory' + case 6: + return 'fifo' + case 7: + return 'contiguous-file' + case 72: + return 'pax-header' + case 55: + return 'pax-global-header' + case 27: + return 'gnu-long-link-path' + case 28: + case 30: + return 'gnu-long-path' + } + + return null +} + + +var indexOf = function (block, num, offset, end) { + for (; offset < end; offset++) { + if (block[offset] === num) return offset + } + return end +} + +var cksum = function (block) { + var sum = 8 * 32 + for (var i = 0; i < 148; i++) sum += block[i] + for (var j = 156; j < 512; j++) sum += block[j] + return sum +} + + +var decodeOct = function (val, offset) { + // If prefixed with 0x80 then parse as a base-256 integer + if (val[offset] & 0x80) { + return parse256(val.slice(offset, offset + 8)) + } else { + // Older versions of tar can prefix with spaces + while (offset < val.length && val[offset] === 32) offset++ + var end = clamp(indexOf(val, 32, offset, val.length), val.length, val.length) + while (offset < end && val[offset] === 0) offset++ + if (end === offset) return 0 + return parseInt(val.slice(offset, end).toString(), 8) + } +} + +var decodeStr = function (val, offset, length) { + return val.slice(offset, indexOf(val, 0, offset, offset + length)).toString() +} + +const headers = {} + +headers.decode = function (buf) { + var typeflag = buf[156] === 0 ? 0 : buf[156] - ZERO_OFFSET + + var name = decodeStr(buf, 0, 100) + var mode = decodeOct(buf, 100) + var uid = decodeOct(buf, 108) + var gid = decodeOct(buf, 116) + var size = decodeOct(buf, 124) + var mtime = decodeOct(buf, 136) + var type = toType(typeflag) + var linkname = buf[157] === 0 ? null : decodeStr(buf, 157, 100) + var uname = decodeStr(buf, 265, 32) + var gname = decodeStr(buf, 297, 32) + var devmajor = decodeOct(buf, 329) + var devminor = decodeOct(buf, 337) + + if (buf[345]) name = decodeStr(buf, 345, 155) + '/' + name + + // to support old tar versions that use trailing / to indicate dirs + if (typeflag === 0 && name && name[name.length - 1] === '/') typeflag = 5 + + var c = cksum(buf) + + // checksum is still initial value if header was null. + if (c === 8 * 32) return null + + // valid checksum + if (c !== decodeOct(buf, 148)) throw new Error('Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?') + + return { + name: name, + mode: mode, + uid: uid, + gid: gid, + size: size, + mtime: new Date(1000 * mtime), + type: type, + linkname: linkname, + uname: uname, + gname: gname, + devmajor: devmajor, + devminor: devminor + } +} + + +module.exports.extract = Extract diff --git a/deps/platform_tools/package.json b/deps/platform_tools/package.json new file mode 100644 index 00000000000000..08aebe03ac4175 --- /dev/null +++ b/deps/platform_tools/package.json @@ -0,0 +1,39 @@ +{ + "name": "platform-tools", + "version": "0.3.3", + "description": "A toolchain to build and compile native dependencies with and for Node.", + "main": "index.js", + "scripts": { + "test": "node test/test-runner.js", + "doc": "jsdoc2md lib/platform_tools.js > doc/API.md && node -e \"const fs = require('fs');fs.writeFile('README.md', fs.readFileSync('doc/README.md'));['doc/README.md', 'doc/API.md', 'doc/LICENSE.md'].forEach(function(el){fs.appendFileSync('README.md',fs.readFileSync(el))})\"", + "smoke-deps": "git clone -b feature/platform-tools --depth=1 https://github.com/eljefedelrodeodeljefe/node test/fixtures/sources/smoke/node", + "smoke-node": "cd test/fixtures/sources/smoke/node && ./configure --no-target-type && make -j8 > pt_debug_log.txt && cd ../../../../../ && node test/smoke/node.js", + "smoke": "npm run smoke-deps && npm run smoke-node" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/eljefedelrodeodeljefe/platform-tools.git" + }, + "keywords": [ + "compiler", + "toolchain", + "linker", + "c", + "c++", + "cpp", + "native", + "addons" + ], + "author": "Robert Jefe Lindstaedt ", + "license": "MIT", + "bugs": { + "url": "https://github.com/eljefedelrodeodeljefe/platform-tools/issues" + }, + "homepage": "https://github.com/eljefedelrodeodeljefe/platform-tools#readme", + "devDependencies": { + "jsdoc-to-markdown": "^1.3.6", + "nan": "^2.3.5", + "tape": "^4.6.0" + }, + "dependencies": {} +} diff --git a/lib/internal/module.js b/lib/internal/module.js index a12af12f3e3d7e..3934fb0570c35d 100644 --- a/lib/internal/module.js +++ b/lib/internal/module.js @@ -54,7 +54,7 @@ function stripBOM(content) { exports.builtinLibs = ['assert', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream', - 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib']; + 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib', 'platform_tools']; function addBuiltinLibsToObject(object) { // Make built-in modules available directly (loaded lazily). diff --git a/lib/platform_tools.js b/lib/platform_tools.js new file mode 100644 index 00000000000000..7197f532e056db --- /dev/null +++ b/lib/platform_tools.js @@ -0,0 +1,2 @@ +'use strict'; +module.exports = require('../deps/platform_tools'); diff --git a/node.gyp b/node.gyp index c3f591351d52ce..90a00f8d952e58 100644 --- a/node.gyp +++ b/node.gyp @@ -47,6 +47,7 @@ 'lib/path.js', 'lib/process.js', 'lib/punycode.js', + 'lib/platform_tools.js', 'lib/querystring.js', 'lib/readline.js', 'lib/repl.js', diff --git a/vcbuild.bat b/vcbuild.bat index ee5b4c8a17df08..5b98181633f610 100644 --- a/vcbuild.bat +++ b/vcbuild.bat @@ -20,6 +20,7 @@ set noprojgen= set nobuild= set nosign= set nosnapshot= +set notargettype= set test_args= set package= set msi= @@ -55,6 +56,7 @@ if /i "%1"=="nosign" set nosign=1&goto arg-ok if /i "%1"=="nosnapshot" set nosnapshot=1&goto arg-ok if /i "%1"=="noetw" set noetw=1&goto arg-ok if /i "%1"=="noperfctr" set noperfctr=1&goto arg-ok +if /i "%1"=="notargettype" set notargettype=1&goto arg-ok if /i "%1"=="licensertf" set licensertf=1&goto arg-ok if /i "%1"=="test" set test_args=%test_args% addons doctool known_issues message parallel sequential -J&set jslint=1&set build_addons=1&goto arg-ok if /i "%1"=="test-ci" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap addons doctool known_issues message sequential parallel&set build_addons=1&goto arg-ok