diff --git a/bin/node-gyp-bin/node-gyp b/bin/node-gyp-bin/node-gyp deleted file mode 100755 index 70efb6f339..0000000000 --- a/bin/node-gyp-bin/node-gyp +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env sh -if [ "x$npm_config_node_gyp" = "x" ]; then - node "`dirname "$0"`/../../node_modules/node-gyp/bin/node-gyp.js" "$@" -else - "$npm_config_node_gyp" "$@" -fi diff --git a/bin/node-gyp-bin/node-gyp.cmd b/bin/node-gyp-bin/node-gyp.cmd deleted file mode 100644 index bb0a7f7106..0000000000 --- a/bin/node-gyp-bin/node-gyp.cmd +++ /dev/null @@ -1,5 +0,0 @@ -if not defined npm_config_node_gyp ( - node "%~dp0\..\..\node_modules\node-gyp\bin\node-gyp.js" %* -) else ( - node %npm_config_node_gyp% %* -) diff --git a/package.json b/package.json index 3b845db034..e190e7f940 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "minimatch": "^3.0.3", "mkdirp": "^0.5.1", "node-emoji": "^1.0.4", - "node-gyp": "^3.2.1", "object-path": "^0.11.2", "proper-lockfile": "^2.0.0", "read": "^1.0.7", diff --git a/scripts/build-dist.sh b/scripts/build-dist.sh index 1c62a12580..11d24ddf46 100755 --- a/scripts/build-dist.sh +++ b/scripts/build-dist.sh @@ -31,7 +31,6 @@ cp LICENSE dist/ cp artifacts/yarn-legacy-*.js dist/lib/yarn-cli.js cp bin/yarn-bundle-entry.js dist/bin/yarn.js cp bin/{yarn,yarnpkg,*.cmd} dist/bin/ -cp -r bin/node-gyp-bin dist/bin/ # We cannot bundle v8-compile-cache as it must be loaded separately to be effective. cp node_modules/v8-compile-cache/v8-compile-cache.js dist/lib/v8-compile-cache.js diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index 78dbcc0931..e1a96e23ed 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -137,6 +137,8 @@ const messages = { possibleCommands: 'Project commands', commandQuestion: 'Which command would you like to run?', commandFailed: 'Command failed with exit code $0.', + packageRequiresNodeGyp: 'This package requires node-gyp, which is not currently installed. Yarn will attempt to automatically install it. If this fails, you can run "yarn global add node-gyp" to manually install it.', + nodeGypAutoInstallFailed: 'Failed to auto-install node-gyp. Please run "yarn global add node-gyp" manually. Error: $0', foundIncompatible: 'Found incompatible module', incompatibleEngine: 'The engine $0 is incompatible with this module. Expected version $1.', diff --git a/src/util/execute-lifecycle-script.js b/src/util/execute-lifecycle-script.js index 7270a27cc9..124e92e4b4 100644 --- a/src/util/execute-lifecycle-script.js +++ b/src/util/execute-lifecycle-script.js @@ -5,8 +5,13 @@ import type Config from '../config.js'; import {MessageError, SpawnError} from '../errors.js'; import * as constants from '../constants.js'; import * as child from './child.js'; +import {exists} from './fs.js'; import {registries} from '../resolvers/index.js'; import {fixCmdWinSlashes} from './fix-cmd-win-slashes.js'; +import { + run as globalRun, + getBinFolder as getGlobalBinFolder, +} from '../cli/commands/global.js'; const path = require('path'); @@ -116,8 +121,22 @@ export async function executeLifecycleScript( // split up the path const pathParts = (env[constants.ENV_PATH_KEY] || '').split(path.delimiter); - // add node-gyp - pathParts.unshift(path.join(__dirname, '..', '..', 'bin', 'node-gyp-bin')); + // Include node-gyp version that was bundled with the current Node.js version, + // if available. + pathParts.unshift( + path.join( + path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'node-gyp-bin', + ), + ); + pathParts.unshift( + path.join( + path.dirname(process.execPath), '..', 'lib', 'node_modules', 'npm', 'bin', 'node-gyp-bin', + ), + ); + + // Add global bin folder, as some packages depend on a globally-installed + // version of node-gyp. + pathParts.unshift(getGlobalBinFolder(config, {})); // add .bin folders to PATH for (const registry of Object.keys(registries)) { @@ -126,6 +145,8 @@ export async function executeLifecycleScript( pathParts.unshift(path.join(cwd, binFolder)); } + await checkForGypIfNeeded(config, cmd, pathParts); + // join path back together env[constants.ENV_PATH_KEY] = pathParts.join(path.delimiter); @@ -168,6 +189,57 @@ export async function executeLifecycleScript( export default executeLifecycleScript; +let checkGypPromise: ?Promise = null; +/** + * Special case: Some packages depend on node-gyp, but don't specify this in + * their package.json dependencies. They assume that node-gyp is available + * globally. We need to detect this case and show an error message. + */ +function checkForGypIfNeeded( + config: Config, + cmd: string, + paths: Array, +): Promise { + if (cmd.substr(0, cmd.indexOf(' ')) !== 'node-gyp') { + return Promise.resolve(); + } + + // Ensure this only runs once, rather than multiple times in parallel. + if (!checkGypPromise) { + checkGypPromise = _checkForGyp(config, paths); + } + return checkGypPromise; +} + +async function _checkForGyp( + config: Config, + paths: Array, +): Promise { + const {reporter} = config; + + // Check every directory in the PATH + const allChecks = await Promise.all( + paths.map((dir) => exists(path.join(dir, 'node-gyp'))), + ); + if (allChecks.some(Boolean)) { + // node-gyp is available somewhere + return; + } + + reporter.info(reporter.lang('packageRequiresNodeGyp')); + + try { + await globalRun( + config, + reporter, + {}, + ['add', 'node-gyp'], + ); + } catch (e) { + throw new MessageError(reporter.lang('nodeGypAutoInstallFailed', e.message)); + } +} + export async function execFromManifest(config: Config, commandName: string, cwd: string): Promise { const pkg = await config.maybeReadManifest(cwd); if (!pkg || !pkg.scripts) { diff --git a/yarn.lock b/yarn.lock index 6728e7b4af..81fa74be14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3295,24 +3295,6 @@ node-emoji@^1.0.4: dependencies: string.prototype.codepointat "^0.2.0" -node-gyp@^3.2.1: - version "3.5.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.5.0.tgz#a8fe5e611d079ec16348a3eb960e78e11c85274a" - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - minimatch "^3.0.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "2" - rimraf "2" - semver "2.x || 3.x || 4 || 5" - tar "^2.0.0" - which "1" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -3368,7 +3350,7 @@ node-pre-gyp@^0.6.29: tar "~2.2.1" tar-pack "~3.3.0" -"nopt@2 || 3", nopt@~3.0.6: +nopt@~3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" dependencies: @@ -3387,7 +3369,7 @@ normalize-path@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1: +npmlog@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" dependencies: @@ -3501,13 +3483,6 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" @@ -3903,7 +3878,7 @@ request-capture-har@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/request-capture-har/-/request-capture-har-1.2.2.tgz#cd692cfb2cc744fd84a3358aac6ee51528cf720d" -request@2, request@^2.79.0, request@^2.81.0: +request@^2.79.0, request@^2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -4054,7 +4029,7 @@ sax@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -4352,7 +4327,7 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: readable-stream "^2.0.0" xtend "^4.0.0" -tar@^2.0.0, tar@~2.2.1: +tar@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" dependencies: @@ -4705,7 +4680,7 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" -which@1, which@^1.1.1, which@^1.2.12: +which@^1.1.1, which@^1.2.12: version "1.2.12" resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" dependencies: