Bug 1482435 - Separate out nodejs finding logic from configure and use it for ESLint. r=firefox-build-system-reviewers,gps

This extracts the current logic for finding nodejs into its own module in mozbuild. Configure and ESLint then use it.

For ESLint, this will change the first location it looks for nodejs to be the .mozbuild directory.

Differential Revision: https://phabricator.services.mozilla.com/D6430

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Banner 2018-09-25 18:15:51 +00:00
Родитель 44a6b1b547
Коммит 3043f2a053
5 изменённых файлов: 198 добавлений и 167 удалений

Просмотреть файл

@ -6,74 +6,31 @@
option('--disable-nodejs',
help='Require Node.js to build')
option(env='NODEJS', nargs=1, help='Path to nodejs')
@depends(host)
@imports('os')
@imports(_from='os', _import='environ')
def node_toolchain_search_path(host):
# XXX partly copied from tooltool.py; should be hoisted somewhere central
@depends('--enable-nodejs', 'NODEJS')
@checking('for nodejs',
callback=lambda x: '%s (%s)' % (x.path, x.str_version) if x else 'no')
@imports(_from='mozbuild.nodeutil', _import='find_node_executable')
@imports(_from='mozbuild.nodeutil', _import='NODE_MIN_VERSION')
def nodejs(require, env_node):
node_exe = env_node[0] if env_node else None
# Also add in the location to which `mach bootstrap` or
# `mach artifact toolchain` installs clang.
mozbuild_state_dir = environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(
os.path.join('~', '.mozbuild')))
if host.kernel == "WINNT":
mozbuild_node_path = os.path.join(mozbuild_state_dir, 'node')
else:
mozbuild_node_path = os.path.join(mozbuild_state_dir, 'node', 'bin')
# We still fallback to the PATH, since on OSes that don't have toolchain
# artifacts available to download, Node may be coming from $PATH.
path = [environ.get('PATH')]
updated_path = [mozbuild_node_path] + path
return updated_path
# "nodejs" is first in the tuple on the assumption that it's only likely to
# exist on systems (probably linux distros) where there is a program in the path
# called "node" that does something else.
nodejs = check_prog('NODEJS', ('nodejs', 'node',),
allow_missing=True, paths=node_toolchain_search_path,
paths_have_priority=True)
@depends_if(nodejs)
@checking('for node.js version')
@imports('os')
@imports('re')
@imports('subprocess')
def nodejs_version(node):
env = dict(os.environ)
env[b'NODE_DISABLE_COLORS'] = b'1'
out = check_cmd_output(node, '--version', env=env)
match = re.search(r'v(.+)$', out)
if not match:
return None
return Version(match.group(1))
@depends('--enable-nodejs', nodejs, nodejs_version)
def nodejs_suitability(require, node, version):
MIN_NODE_VERSION = Version('8.11')
nodejs, version = find_node_executable(node_exe)
MAYBE_FILE_A_BUG = '''
If you believe this is a bug, <https://mzl.la/2vLbXAv> is a good way
to file. Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
More details: <https://bit.ly/2BbyD1E>
'''
Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
If you believe this is a bug, <https://mzl.la/2vLbXAv> is a good way
to file. More details: <https://bit.ly/2BbyD1E>
'''
if not node:
msg = ('could not find Node.js executable; ensure `node` or `nodejs` '
'is in PATH or set NODEJS in environment to point to an '
'executable.%s' % (MAYBE_FILE_A_BUG)
if not nodejs:
msg = ('could not find Node.js executable later than %s; ensure '
'`node` or `nodejs` is in PATH or set NODEJS in environment '
'to point to an executable.%s' % (NODE_MIN_VERSION, MAYBE_FILE_A_BUG)
)
if require:
@ -84,19 +41,20 @@ More details: <https://bit.ly/2BbyD1E>
return
if not version:
msg = 'could not resolve Node.js version.%s' % (MAYBE_FILE_A_BUG)
msg = 'NODEJS must point to node %s or newer; found node location: %s. %s' % (
NODE_MIN_VERSION, nodejs, MAYBE_FILE_A_BUG)
if require:
raise FatalCheckError(msg)
else:
log.warning(msg)
log.warning('(This will become an error in the near future.)')
return
if version < MIN_NODE_VERSION:
msg = 'NODEJS must point to node %s or newer; %s found. %s' % (
MIN_NODE_VERSION, version, MAYBE_FILE_A_BUG)
return namespace(
path=nodejs,
version=version,
str_version='.'.join(str(v) for v in version),
)
if require:
raise FatalCheckError(msg)
else:
log.warning(msg)
set_config('NODEJS', depends_if(nodejs)(lambda p: p.path))

Просмотреть файл

@ -0,0 +1,134 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import
import os
import subprocess
import platform
from mozboot.util import get_state_dir
import which
from distutils.version import (
StrictVersion,
)
NODE_MIN_VERSION = StrictVersion("8.11.0")
NPM_MIN_VERSION = StrictVersion("5.6.0")
def find_node_paths():
""" Determines the possible paths for node executables.
Returns a list of paths, which includes the build state directory.
"""
# Also add in the location to which `mach bootstrap` or
# `mach artifact toolchain` installs clang.
mozbuild_state_dir, _ = get_state_dir()
if platform.system() == "Windows":
mozbuild_node_path = os.path.join(mozbuild_state_dir, 'node')
else:
mozbuild_node_path = os.path.join(mozbuild_state_dir, 'node', 'bin')
# We still fallback to the PATH, since on OSes that don't have toolchain
# artifacts available to download, Node may be coming from $PATH.
paths = [mozbuild_node_path] + os.environ.get('PATH').split(os.pathsep)
if platform.system() == "Windows":
paths += [
"%s\\nodejs" % os.environ.get("SystemDrive"),
os.path.join(os.environ.get("ProgramFiles"), "nodejs"),
os.path.join(os.environ.get("PROGRAMW6432"), "nodejs"),
os.path.join(os.environ.get("PROGRAMFILES"), "nodejs")
]
return paths
def check_executable_version(exe):
"""Determine the version of a Node executable by invoking it.
May raise ``subprocess.CalledProcessError`` or ``ValueError`` on failure.
"""
out = subprocess.check_output([exe, "--version"]).lstrip('v').rstrip()
return StrictVersion(out)
def simple_which(filename, path=None):
# Note: On windows, npm uses ".cmd"
exts = [".cmd", ".exe", ""] if platform.system() == "Windows" else [""]
for ext in exts:
try:
return which.which(filename + ext, path)
except which.WhichError:
pass
# If we got this far, we didn't find it with any of the extensions, so
# just return.
return None
def find_node_executable(nodejs_exe=os.environ.get('NODEJS'), min_version=NODE_MIN_VERSION):
"""Find a Node executable from the mozbuild directory.
Returns a tuple containing the the path to an executable binary and a
version tuple. Both tuple entries will be None if a Node executable
could not be resolved.
"""
if nodejs_exe:
try:
version = check_executable_version(nodejs_exe)
except (subprocess.CalledProcessError, ValueError):
return None, None
if version >= min_version:
return nodejs_exe, version.version
return None, None
# "nodejs" is first in the tuple on the assumption that it's only likely to
# exist on systems (probably linux distros) where there is a program in the path
# called "node" that does something else.
return find_executable(['nodejs', 'node'], min_version)
def find_npm_executable(min_version=NPM_MIN_VERSION):
"""Find a Node executable from the mozbuild directory.
Returns a tuple containing the the path to an executable binary and a
version tuple. Both tuple entries will be None if a Node executable
could not be resolved.
"""
return find_executable(["npm"], min_version)
def find_executable(names, min_version):
paths = find_node_paths()
found_exe = None
for name in names:
try:
exe = simple_which(name, paths)
except which.WhichError:
continue
if not exe:
continue
if not found_exe:
found_exe = exe
# We always verify we can invoke the executable and its version is
# sane.
try:
version = check_executable_version(exe)
except (subprocess.CalledProcessError, ValueError):
continue
if version >= min_version:
return exe, version.version
return found_exe, None

Просмотреть файл

@ -4,9 +4,12 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# This script installs Node v8.
# XXX For now, this should match the version installed in
# taskcluster/scripts/misc/repack-node.sh. Later we'll get the ESLint builder
# to use the linux64-node toolchain directly.
wget --progress=dot:mega https://nodejs.org/dist/v8.9.4/node-v8.9.4-linux-x64.tar.gz
echo '21fb4690e349f82d708ae766def01d7fec1b085ce1f5ab30d9bda8ee126ca8fc node-v8.9.4-linux-x64.tar.gz' | sha256sum -c
tar -C /usr/local -xz --strip-components 1 < node-v8.9.4-linux-x64.tar.gz
wget --progress=dot:mega https://nodejs.org/dist/v8.11.3/node-v8.11.3-linux-x64.tar.xz
echo '08e2fcfea66746bd966ea3a89f26851f1238d96f86c33eaf6274f67fce58421a node-v8.11.3-linux-x64.tar.xz' | sha256sum -c
tar -C /usr/local -xJ --strip-components 1 < node-v8.11.3-linux-x64.tar.xz
node -v # verify
npm -v

Просмотреть файл

@ -10,6 +10,7 @@ import signal
import sys
sys.path.append(os.path.join(os.path.dirname(__file__), "eslint"))
import setup_helper
from mozbuild.nodeutil import find_node_executable
from mozprocess import ProcessHandler
@ -52,12 +53,7 @@ def lint(paths, config, binary=None, fix=None, setup=None, **lintargs):
# - Those provided by |mach lint --setup|.
if not binary:
binary = os.environ.get('ESLINT', None)
if not binary:
binary = os.path.join(module_path, "node_modules", ".bin", "eslint")
if not os.path.isfile(binary):
binary = None
binary, _ = find_node_executable()
if not binary:
print(ESLINT_NOT_FOUND_MESSAGE)
@ -65,6 +61,7 @@ def lint(paths, config, binary=None, fix=None, setup=None, **lintargs):
extra_args = lintargs.get('extra_args') or []
cmd_args = [binary,
os.path.join(module_path, "node_modules", "eslint", "bin", "eslint.js"),
# Enable the HTML plugin.
# We can't currently enable this in the global config file
# because it has bad interactions with the SublimeText

Просмотреть файл

@ -14,39 +14,39 @@ import subprocess
import sys
import shutil
import tempfile
from distutils.version import LooseVersion
from distutils.version import LooseVersion, StrictVersion
from mozbuild.nodeutil import (find_node_executable, find_npm_executable,
NPM_MIN_VERSION, NODE_MIN_VERSION)
sys.path.append(os.path.join(
os.path.dirname(__file__), "..", "..", "..", "third_party", "python", "which"))
import which
NODE_MIN_VERSION = "8.9.1"
NPM_MIN_VERSION = "5.5.1"
NODE_MACHING_VERSION_NOT_FOUND_MESSAGE = """
nodejs is out of date. You currently have node v%s but v%s is required.
Please update nodejs from https://nodejs.org and try again.
Could not find Node.js executable later than %s.
Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
""".strip()
NPM_MACHING_VERSION_NOT_FOUND_MESSAGE = """
npm is out of date. You currently have npm v%s but v%s is required.
You can usually update npm with:
Could not find npm executable later than %s.
npm i -g npm
Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
""".strip()
NODE_NOT_FOUND_MESSAGE = """
nodejs is either not installed or is installed to a non-standard path.
Please install nodejs from https://nodejs.org and try again.
Valid installation paths:
Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
""".strip()
NPM_NOT_FOUND_MESSAGE = """
Node Package Manager (npm) is either not installed or installed to a
non-standard path. Please install npm from https://nodejs.org (it comes as an
option in the node installation) and try again.
non-standard path.
Valid installation paths:
Executing `mach bootstrap --no-system-changes` should
install a compatible version in ~/.mozbuild on most platforms.
""".strip()
@ -88,7 +88,7 @@ def eslint_setup(should_clobber=False):
else:
mozfileremove(node_modules_path)
npm_path, version = get_node_or_npm_path("npm")
npm_path, version = find_npm_executable()
if not npm_path:
return 1
@ -99,7 +99,7 @@ def eslint_setup(should_clobber=False):
# If we have an npm version newer than 5.8.0, just use 'ci', as that's much
# simpler and does exactly what we want.
npm_is_older_version = version < LooseVersion("5.8.0")
npm_is_older_version = version < StrictVersion("5.8.0").version
if npm_is_older_version:
cmd = [npm_path, "install"]
@ -272,74 +272,6 @@ def get_possible_node_paths_win():
})
def simple_which(filename, path=None):
exts = [".cmd", ".exe", ""] if platform.system() == "Windows" else [""]
for ext in exts:
try:
return which.which(filename + ext, path)
except which.WhichError:
pass
# If we got this far, we didn't find it with any of the extensions, so
# just return.
return None
def which_path(filename):
"""
Return the nodejs or npm path.
"""
# Look in the system path first.
path = simple_which(filename)
if path is not None:
return path
if platform.system() == "Windows":
# If we didn't find it fallback to the non-system paths.
path = simple_which(filename, get_possible_node_paths_win())
elif filename == "node":
path = simple_which("nodejs")
return path
def get_node_or_npm_path(filename, minversion=None):
node_or_npm_path = which_path(filename)
if not node_or_npm_path:
if filename in ('node', 'nodejs'):
print(NODE_NOT_FOUND_MESSAGE)
elif filename == "npm":
print(NPM_NOT_FOUND_MESSAGE)
if platform.system() == "Windows":
app_paths = get_possible_node_paths_win()
for p in app_paths:
print(" - %s" % p)
elif platform.system() == "Darwin":
print(" - /usr/local/bin/{}".format(filename))
elif platform.system() == "Linux":
print(" - /usr/bin/{}".format(filename))
return None, None
version_str = get_version(node_or_npm_path).lstrip('v')
version = LooseVersion(version_str)
if not minversion or version > minversion:
return node_or_npm_path, version
if filename == "npm":
print(NPM_MACHING_VERSION_NOT_FOUND_MESSAGE % (version_str.strip(), minversion))
else:
print(NODE_MACHING_VERSION_NOT_FOUND_MESSAGE % (version_str.strip(), minversion))
return None, None
def get_version(path):
try:
version_str = subprocess.check_output([path, "--version"],
@ -393,13 +325,20 @@ def get_eslint_module_path():
def check_node_executables_valid():
# eslint requires at least node 6.9.1
node_path = get_node_or_npm_path("node", LooseVersion(NODE_MIN_VERSION))
node_path, version = find_node_executable()
if not node_path:
print(NODE_NOT_FOUND_MESSAGE)
return False
if not version:
print(NODE_MACHING_VERSION_NOT_FOUND_MESSAGE % NODE_MIN_VERSION)
return False
npm_path = get_node_or_npm_path("npm", LooseVersion(NPM_MIN_VERSION))
npm_path, version = find_npm_executable()
if not npm_path:
print(NPM_NOT_FOUND_MESSAGE)
return False
if not version:
print(NPM_MACHING_VERSION_NOT_FOUND_MESSAGE % NPM_MIN_VERSION)
return False
return True