You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					336 lines
				
				14 KiB
			
		
		
			
		
	
	
					336 lines
				
				14 KiB
			| 
											3 years ago
										 | 'use strict'; | ||
|  | 
 | ||
|  | module.exports = exports; | ||
|  | 
 | ||
|  | const path = require('path'); | ||
|  | const semver = require('semver'); | ||
|  | const url = require('url'); | ||
|  | const detect_libc = require('detect-libc'); | ||
|  | const napi = require('./napi.js'); | ||
|  | 
 | ||
|  | let abi_crosswalk; | ||
|  | 
 | ||
|  | // This is used for unit testing to provide a fake
 | ||
|  | // ABI crosswalk that emulates one that is not updated
 | ||
|  | // for the current version
 | ||
|  | if (process.env.NODE_PRE_GYP_ABI_CROSSWALK) { | ||
|  |   abi_crosswalk = require(process.env.NODE_PRE_GYP_ABI_CROSSWALK); | ||
|  | } else { | ||
|  |   abi_crosswalk = require('./abi_crosswalk.json'); | ||
|  | } | ||
|  | 
 | ||
|  | const major_versions = {}; | ||
|  | Object.keys(abi_crosswalk).forEach((v) => { | ||
|  |   const major = v.split('.')[0]; | ||
|  |   if (!major_versions[major]) { | ||
|  |     major_versions[major] = v; | ||
|  |   } | ||
|  | }); | ||
|  | 
 | ||
|  | function get_electron_abi(runtime, target_version) { | ||
|  |   if (!runtime) { | ||
|  |     throw new Error('get_electron_abi requires valid runtime arg'); | ||
|  |   } | ||
|  |   if (typeof target_version === 'undefined') { | ||
|  |     // erroneous CLI call
 | ||
|  |     throw new Error('Empty target version is not supported if electron is the target.'); | ||
|  |   } | ||
|  |   // Electron guarantees that patch version update won't break native modules.
 | ||
|  |   const sem_ver = semver.parse(target_version); | ||
|  |   return runtime + '-v' + sem_ver.major + '.' + sem_ver.minor; | ||
|  | } | ||
|  | module.exports.get_electron_abi = get_electron_abi; | ||
|  | 
 | ||
|  | function get_node_webkit_abi(runtime, target_version) { | ||
|  |   if (!runtime) { | ||
|  |     throw new Error('get_node_webkit_abi requires valid runtime arg'); | ||
|  |   } | ||
|  |   if (typeof target_version === 'undefined') { | ||
|  |     // erroneous CLI call
 | ||
|  |     throw new Error('Empty target version is not supported if node-webkit is the target.'); | ||
|  |   } | ||
|  |   return runtime + '-v' + target_version; | ||
|  | } | ||
|  | module.exports.get_node_webkit_abi = get_node_webkit_abi; | ||
|  | 
 | ||
|  | function get_node_abi(runtime, versions) { | ||
|  |   if (!runtime) { | ||
|  |     throw new Error('get_node_abi requires valid runtime arg'); | ||
|  |   } | ||
|  |   if (!versions) { | ||
|  |     throw new Error('get_node_abi requires valid process.versions object'); | ||
|  |   } | ||
|  |   const sem_ver = semver.parse(versions.node); | ||
|  |   if (sem_ver.major === 0 && sem_ver.minor % 2) { // odd series
 | ||
|  |     // https://github.com/mapbox/node-pre-gyp/issues/124
 | ||
|  |     return runtime + '-v' + versions.node; | ||
|  |   } else { | ||
|  |     // process.versions.modules added in >= v0.10.4 and v0.11.7
 | ||
|  |     // https://github.com/joyent/node/commit/ccabd4a6fa8a6eb79d29bc3bbe9fe2b6531c2d8e
 | ||
|  |     return versions.modules ? runtime + '-v' + (+versions.modules) : | ||
|  |       'v8-' + versions.v8.split('.').slice(0, 2).join('.'); | ||
|  |   } | ||
|  | } | ||
|  | module.exports.get_node_abi = get_node_abi; | ||
|  | 
 | ||
|  | function get_runtime_abi(runtime, target_version) { | ||
|  |   if (!runtime) { | ||
|  |     throw new Error('get_runtime_abi requires valid runtime arg'); | ||
|  |   } | ||
|  |   if (runtime === 'node-webkit') { | ||
|  |     return get_node_webkit_abi(runtime, target_version || process.versions['node-webkit']); | ||
|  |   } else if (runtime === 'electron') { | ||
|  |     return get_electron_abi(runtime, target_version || process.versions.electron); | ||
|  |   } else { | ||
|  |     if (runtime !== 'node') { | ||
|  |       throw new Error("Unknown Runtime: '" + runtime + "'"); | ||
|  |     } | ||
|  |     if (!target_version) { | ||
|  |       return get_node_abi(runtime, process.versions); | ||
|  |     } else { | ||
|  |       let cross_obj; | ||
|  |       // abi_crosswalk generated with ./scripts/abi_crosswalk.js
 | ||
|  |       if (abi_crosswalk[target_version]) { | ||
|  |         cross_obj = abi_crosswalk[target_version]; | ||
|  |       } else { | ||
|  |         const target_parts = target_version.split('.').map((i) => { return +i; }); | ||
|  |         if (target_parts.length !== 3) { // parse failed
 | ||
|  |           throw new Error('Unknown target version: ' + target_version); | ||
|  |         } | ||
|  |         /* | ||
|  |                     The below code tries to infer the last known ABI compatible version | ||
|  |                     that we have recorded in the abi_crosswalk.json when an exact match | ||
|  |                     is not possible. The reasons for this to exist are complicated: | ||
|  | 
 | ||
|  |                        - We support passing --target to be able to allow developers to package binaries for versions of node | ||
|  |                          that are not the same one as they are running. This might also be used in combination with the | ||
|  |                          --target_arch or --target_platform flags to also package binaries for alternative platforms | ||
|  |                        - When --target is passed we can't therefore determine the ABI (process.versions.modules) from the node | ||
|  |                          version that is running in memory | ||
|  |                        - So, therefore node-pre-gyp keeps an "ABI crosswalk" (lib/util/abi_crosswalk.json) to be able to look | ||
|  |                          this info up for all versions | ||
|  |                        - But we cannot easily predict what the future ABI will be for released versions | ||
|  |                        - And node-pre-gyp needs to be a `bundledDependency` in apps that depend on it in order to work correctly | ||
|  |                          by being fully available at install time. | ||
|  |                        - So, the speed of node releases and the bundled nature of node-pre-gyp mean that a new node-pre-gyp release | ||
|  |                          need to happen for every node.js/io.js/node-webkit/nw.js/atom-shell/etc release that might come online if | ||
|  |                          you want the `--target` flag to keep working for the latest version | ||
|  |                        - Which is impractical ^^ | ||
|  |                        - Hence the below code guesses about future ABI to make the need to update node-pre-gyp less demanding. | ||
|  | 
 | ||
|  |                     In practice then you can have a dependency of your app like `node-sqlite3` that bundles a `node-pre-gyp` that | ||
|  |                     only knows about node v0.10.33 in the `abi_crosswalk.json` but target node v0.10.34 (which is assumed to be | ||
|  |                     ABI compatible with v0.10.33). | ||
|  | 
 | ||
|  |                     TODO: use semver module instead of custom version parsing | ||
|  |                 */ | ||
|  |         const major = target_parts[0]; | ||
|  |         let minor = target_parts[1]; | ||
|  |         let patch = target_parts[2]; | ||
|  |         // io.js: yeah if node.js ever releases 1.x this will break
 | ||
|  |         // but that is unlikely to happen: https://github.com/iojs/io.js/pull/253#issuecomment-69432616
 | ||
|  |         if (major === 1) { | ||
|  |           // look for last release that is the same major version
 | ||
|  |           // e.g. we assume io.js 1.x is ABI compatible with >= 1.0.0
 | ||
|  |           while (true) { | ||
|  |             if (minor > 0) --minor; | ||
|  |             if (patch > 0) --patch; | ||
|  |             const new_iojs_target = '' + major + '.' + minor + '.' + patch; | ||
|  |             if (abi_crosswalk[new_iojs_target]) { | ||
|  |               cross_obj = abi_crosswalk[new_iojs_target]; | ||
|  |               console.log('Warning: node-pre-gyp could not find exact match for ' + target_version); | ||
|  |               console.log('Warning: but node-pre-gyp successfully choose ' + new_iojs_target + ' as ABI compatible target'); | ||
|  |               break; | ||
|  |             } | ||
|  |             if (minor === 0 && patch === 0) { | ||
|  |               break; | ||
|  |             } | ||
|  |           } | ||
|  |         } else if (major >= 2) { | ||
|  |           // look for last release that is the same major version
 | ||
|  |           if (major_versions[major]) { | ||
|  |             cross_obj = abi_crosswalk[major_versions[major]]; | ||
|  |             console.log('Warning: node-pre-gyp could not find exact match for ' + target_version); | ||
|  |             console.log('Warning: but node-pre-gyp successfully choose ' + major_versions[major] + ' as ABI compatible target'); | ||
|  |           } | ||
|  |         } else if (major === 0) { // node.js
 | ||
|  |           if (target_parts[1] % 2 === 0) { // for stable/even node.js series
 | ||
|  |             // look for the last release that is the same minor release
 | ||
|  |             // e.g. we assume node 0.10.x is ABI compatible with >= 0.10.0
 | ||
|  |             while (--patch > 0) { | ||
|  |               const new_node_target = '' + major + '.' + minor + '.' + patch; | ||
|  |               if (abi_crosswalk[new_node_target]) { | ||
|  |                 cross_obj = abi_crosswalk[new_node_target]; | ||
|  |                 console.log('Warning: node-pre-gyp could not find exact match for ' + target_version); | ||
|  |                 console.log('Warning: but node-pre-gyp successfully choose ' + new_node_target + ' as ABI compatible target'); | ||
|  |                 break; | ||
|  |               } | ||
|  |             } | ||
|  |           } | ||
|  |         } | ||
|  |       } | ||
|  |       if (!cross_obj) { | ||
|  |         throw new Error('Unsupported target version: ' + target_version); | ||
|  |       } | ||
|  |       // emulate process.versions
 | ||
|  |       const versions_obj = { | ||
|  |         node: target_version, | ||
|  |         v8: cross_obj.v8 + '.0', | ||
|  |         // abi_crosswalk uses 1 for node versions lacking process.versions.modules
 | ||
|  |         // process.versions.modules added in >= v0.10.4 and v0.11.7
 | ||
|  |         modules: cross_obj.node_abi > 1 ? cross_obj.node_abi : undefined | ||
|  |       }; | ||
|  |       return get_node_abi(runtime, versions_obj); | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | module.exports.get_runtime_abi = get_runtime_abi; | ||
|  | 
 | ||
|  | const required_parameters = [ | ||
|  |   'module_name', | ||
|  |   'module_path', | ||
|  |   'host' | ||
|  | ]; | ||
|  | 
 | ||
|  | function validate_config(package_json, opts) { | ||
|  |   const msg = package_json.name + ' package.json is not node-pre-gyp ready:\n'; | ||
|  |   const missing = []; | ||
|  |   if (!package_json.main) { | ||
|  |     missing.push('main'); | ||
|  |   } | ||
|  |   if (!package_json.version) { | ||
|  |     missing.push('version'); | ||
|  |   } | ||
|  |   if (!package_json.name) { | ||
|  |     missing.push('name'); | ||
|  |   } | ||
|  |   if (!package_json.binary) { | ||
|  |     missing.push('binary'); | ||
|  |   } | ||
|  |   const o = package_json.binary; | ||
|  |   if (o) { | ||
|  |     required_parameters.forEach((p) => { | ||
|  |       if (!o[p] || typeof o[p] !== 'string') { | ||
|  |         missing.push('binary.' + p); | ||
|  |       } | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (missing.length >= 1) { | ||
|  |     throw new Error(msg + 'package.json must declare these properties: \n' + missing.join('\n')); | ||
|  |   } | ||
|  |   if (o) { | ||
|  |     // enforce https over http
 | ||
|  |     const protocol = url.parse(o.host).protocol; | ||
|  |     if (protocol === 'http:') { | ||
|  |       throw new Error("'host' protocol (" + protocol + ") is invalid - only 'https:' is accepted"); | ||
|  |     } | ||
|  |   } | ||
|  |   napi.validate_package_json(package_json, opts); | ||
|  | } | ||
|  | 
 | ||
|  | module.exports.validate_config = validate_config; | ||
|  | 
 | ||
|  | function eval_template(template, opts) { | ||
|  |   Object.keys(opts).forEach((key) => { | ||
|  |     const pattern = '{' + key + '}'; | ||
|  |     while (template.indexOf(pattern) > -1) { | ||
|  |       template = template.replace(pattern, opts[key]); | ||
|  |     } | ||
|  |   }); | ||
|  |   return template; | ||
|  | } | ||
|  | 
 | ||
|  | // url.resolve needs single trailing slash
 | ||
|  | // to behave correctly, otherwise a double slash
 | ||
|  | // may end up in the url which breaks requests
 | ||
|  | // and a lacking slash may not lead to proper joining
 | ||
|  | function fix_slashes(pathname) { | ||
|  |   if (pathname.slice(-1) !== '/') { | ||
|  |     return pathname + '/'; | ||
|  |   } | ||
|  |   return pathname; | ||
|  | } | ||
|  | 
 | ||
|  | // remove double slashes
 | ||
|  | // note: path.normalize will not work because
 | ||
|  | // it will convert forward to back slashes
 | ||
|  | function drop_double_slashes(pathname) { | ||
|  |   return pathname.replace(/\/\//g, '/'); | ||
|  | } | ||
|  | 
 | ||
|  | function get_process_runtime(versions) { | ||
|  |   let runtime = 'node'; | ||
|  |   if (versions['node-webkit']) { | ||
|  |     runtime = 'node-webkit'; | ||
|  |   } else if (versions.electron) { | ||
|  |     runtime = 'electron'; | ||
|  |   } | ||
|  |   return runtime; | ||
|  | } | ||
|  | 
 | ||
|  | module.exports.get_process_runtime = get_process_runtime; | ||
|  | 
 | ||
|  | const default_package_name = '{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz'; | ||
|  | const default_remote_path = ''; | ||
|  | 
 | ||
|  | module.exports.evaluate = function(package_json, options, napi_build_version) { | ||
|  |   options = options || {}; | ||
|  |   validate_config(package_json, options); // options is a suitable substitute for opts in this case
 | ||
|  |   const v = package_json.version; | ||
|  |   const module_version = semver.parse(v); | ||
|  |   const runtime = options.runtime || get_process_runtime(process.versions); | ||
|  |   const opts = { | ||
|  |     name: package_json.name, | ||
|  |     configuration: options.debug ? 'Debug' : 'Release', | ||
|  |     debug: options.debug, | ||
|  |     module_name: package_json.binary.module_name, | ||
|  |     version: module_version.version, | ||
|  |     prerelease: module_version.prerelease.length ? module_version.prerelease.join('.') : '', | ||
|  |     build: module_version.build.length ? module_version.build.join('.') : '', | ||
|  |     major: module_version.major, | ||
|  |     minor: module_version.minor, | ||
|  |     patch: module_version.patch, | ||
|  |     runtime: runtime, | ||
|  |     node_abi: get_runtime_abi(runtime, options.target), | ||
|  |     node_abi_napi: napi.get_napi_version(options.target) ? 'napi' : get_runtime_abi(runtime, options.target), | ||
|  |     napi_version: napi.get_napi_version(options.target), // non-zero numeric, undefined if unsupported
 | ||
|  |     napi_build_version: napi_build_version || '', | ||
|  |     node_napi_label: napi_build_version ? 'napi-v' + napi_build_version : get_runtime_abi(runtime, options.target), | ||
|  |     target: options.target || '', | ||
|  |     platform: options.target_platform || process.platform, | ||
|  |     target_platform: options.target_platform || process.platform, | ||
|  |     arch: options.target_arch || process.arch, | ||
|  |     target_arch: options.target_arch || process.arch, | ||
|  |     libc: options.target_libc || detect_libc.familySync() || 'unknown', | ||
|  |     module_main: package_json.main, | ||
|  |     toolset: options.toolset || '', // address https://github.com/mapbox/node-pre-gyp/issues/119
 | ||
|  |     bucket: package_json.binary.bucket, | ||
|  |     region: package_json.binary.region, | ||
|  |     s3ForcePathStyle: package_json.binary.s3ForcePathStyle || false | ||
|  |   }; | ||
|  |     // support host mirror with npm config `--{module_name}_binary_host_mirror`
 | ||
|  |     // e.g.: https://github.com/node-inspector/v8-profiler/blob/master/package.json#L25
 | ||
|  |     // > npm install v8-profiler --profiler_binary_host_mirror=https://npm.taobao.org/mirrors/node-inspector/
 | ||
|  |   const validModuleName = opts.module_name.replace('-', '_'); | ||
|  |   const host = process.env['npm_config_' + validModuleName + '_binary_host_mirror'] || package_json.binary.host; | ||
|  |   opts.host = fix_slashes(eval_template(host, opts)); | ||
|  |   opts.module_path = eval_template(package_json.binary.module_path, opts); | ||
|  |   // now we resolve the module_path to ensure it is absolute so that binding.gyp variables work predictably
 | ||
|  |   if (options.module_root) { | ||
|  |     // resolve relative to known module root: works for pre-binding require
 | ||
|  |     opts.module_path = path.join(options.module_root, opts.module_path); | ||
|  |   } else { | ||
|  |     // resolve relative to current working directory: works for node-pre-gyp commands
 | ||
|  |     opts.module_path = path.resolve(opts.module_path); | ||
|  |   } | ||
|  |   opts.module = path.join(opts.module_path, opts.module_name + '.node'); | ||
|  |   opts.remote_path = package_json.binary.remote_path ? drop_double_slashes(fix_slashes(eval_template(package_json.binary.remote_path, opts))) : default_remote_path; | ||
|  |   const package_name = package_json.binary.package_name ? package_json.binary.package_name : default_package_name; | ||
|  |   opts.package_name = eval_template(package_name, opts); | ||
|  |   opts.staged_tarball = path.join('build/stage', opts.remote_path, opts.package_name); | ||
|  |   opts.hosted_path = url.resolve(opts.host, opts.remote_path); | ||
|  |   opts.hosted_tarball = url.resolve(opts.hosted_path, opts.package_name); | ||
|  |   return opts; | ||
|  | }; |