Bug 1470332 - teach 'mach bootstrap' to install Node on Debian/Ubuntu, r?gps
MozReview-Commit-ID: IHdpNe0g8Nd
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -68,16 +68,46 @@ the $PATH environment variable.
We recommend the following tools for installing Python:
pyenv -- https://github.com/yyuu/pyenv)
pythonz -- https://github.com/saghul/pythonz
official installers -- http://www.python.org/
'''
+NODE_UNABLE_UPGRADE = '''
+You are currently running NodeJS %s. Running %s or newer is required.
+
+Unfortunately, this bootstrapper does not currently know how to automatically
+upgrade NodeJS on your machine.
+
+Please search the Internet for how to upgrade your NodeJS and try running this
+bootstrapper again to ensure your machine is up to date.
+'''
+
+NODE_UPGRADE_FAILED = '''
+We attempted to upgrade NodeJS to a modern version (%s or newer).
+However, you appear to still have version %s.
+
+It's possible your package manager doesn't yet expose a modern version of
+NodeJS. It's also possible NodeJS is not being installed in the search path for
+this shell. Try creating a new shell and run this bootstrapper again.
+
+If this continues to fail and you are sure you have a modern NodeJS on your
+system, ensure it is on the $PATH and try again. If that fails, you'll need to
+install NodeJS manually and ensure the path with the node binary is listed in
+the $PATH environment variable.
+
+We recommend installing NodeJS from one of the following sources:
+
+ package manager builds: https://nodejs.org/en/download/package-manager/
+ nvm: https://github.com/creationix/nvm
+ official builds: https://nodejs.org/en/download/
+'''
+
RUST_INSTALL_COMPLETE = '''
Rust installation complete. You should now have rustc and cargo
in %(cargo_bin)s
The installer tries to add these to your default shell PATH, so
restarting your shell and running this script again may work.
If it doesn't, you'll need to add the new command location
manually.
@@ -147,16 +177,18 @@ ac_add_options --enable-artifact-builds
MODERN_MERCURIAL_VERSION = LooseVersion('4.3.3')
# Upgrade Python older than this.
MODERN_PYTHON_VERSION = LooseVersion('2.7.3')
# Upgrade rust older than this.
MODERN_RUST_VERSION = LooseVersion('1.24.0')
+# Upgrade NodeJS older than this.
+MODERN_NODE_VERSION = LooseVersion('8.11.0')
class BaseBootstrapper(object):
"""Base class for system bootstrappers."""
def __init__(self, no_interactive=False):
self.package_manager_updated = False
self.no_interactive = no_interactive
self.state_dir = None
@@ -427,17 +459,17 @@ class BaseBootstrapper(object):
if not name:
name = os.path.basename(path)
if name.endswith('.exe'):
name = name[:-4]
info = self.check_output([path, '--version'],
env=env,
stderr=subprocess.STDOUT)
- match = re.search(name + ' ([a-z0-9\.]+)', info)
+ match = re.search(name + ' ?([a-z0-9\.]+)', info)
if not match:
print('ERROR! Unable to identify %s version.' % name)
return None
return LooseVersion(match.group(1))
def _hg_cleanenv(self, load_hgrc=False):
""" Returns a copy of the current environment updated with the HGPLAIN
@@ -544,16 +576,61 @@ class BaseBootstrapper(object):
def upgrade_python(self, current):
"""Upgrade Python.
Child classes should reimplement this.
"""
print(PYTHON_UNABLE_UPGRADE % (current, MODERN_PYTHON_VERSION))
+ def is_node_modern(self):
+ node = None
+
+ for test in ['nodejs', 'node']:
+ node = self.which(test)
+ if node:
+ break
+
+ assert node
+
+ # "node --version" doesn't print the name of the program at all,
+ # but it does prepend "v" to the version string. Good times!
+ our = self._parse_version(node, name='v')
+ if not our:
+ return False, None
+
+ return our >= MODERN_NODE_VERSION, our
+
+ def ensure_node_modern(self):
+ modern, version = self.is_node_modern()
+
+ if modern:
+ print('Your version of NodeJS (%s) is new enough.' % version)
+ return
+
+ print('Your version of NodeJS (%s) is too old. Will try to upgrade.' %
+ version)
+
+ self._ensure_package_manager_updated()
+ self.upgrade_node(version)
+
+ modern, after = self.is_node_modern()
+
+ if not modern:
+ print(NODE_UPGRADE_FAILED % (MODERN_NODE_VERSION, after))
+ sys.exit(1)
+
+ def upgrade_node(self, current):
+ """Upgrade NodeJS.
+
+ Child classes should reimplement this.
+ """
+ print(NODE_UNABLE_UPGRADE % (current, MODERN_NODE_VERSION))
+ sys.exit(1)
+
def is_rust_modern(self):
rustc = self.which('rustc')
if not rustc:
print('Could not find a Rust compiler.')
return False, None
our = self._parse_version(rustc)
if not our:
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -277,16 +277,17 @@ class Bootstrapper(object):
self.instance.install_system_packages()
# Like 'install_browser_packages' or 'install_mobile_android_packages'.
getattr(self.instance, 'install_%s_packages' % application)()
hg_installed, hg_modern = self.instance.ensure_mercurial_modern()
self.instance.ensure_python_modern()
+ self.instance.ensure_node_modern()
self.instance.ensure_rust_modern()
# The state directory code is largely duplicated from mach_bootstrap.py.
# We can't easily import mach_bootstrap.py because the bootstrapper may
# run in self-contained mode and only the files in this directory will
# be available. We /could/ refactor parts of mach_bootstrap.py to be
# part of this directory to avoid the code duplication.
state_dir, _ = get_state_dir()
--- a/python/mozboot/mozboot/debian.py
+++ b/python/mozboot/mozboot/debian.py
@@ -181,8 +181,45 @@ class DebianBootstrapper(StyloInstall, B
# No Mercurial.
if res == 3:
print('Not installing Mercurial.')
return False
# pip.
assert res == 1
self.run_as_root(['pip', 'install', '--upgrade', 'Mercurial'])
+
+ def upgrade_node(self, current):
+ import os
+ import errno
+ import stat
+ import tempfile
+
+ # setup_8.x is chosen since 8.11.0 is the current minimum.
+ # We _could_ use setup_10.x here, but that seems like a footgun, since
+ # people who aren't conscious of node versioning but want to script
+ # something with node could end up using 10.x features unknowingly,
+ # have them work locally, and then explode on the try servers.
+ node_setup_url = "https://deb.nodesource.com/setup_8.x"
+
+ # hash created by hand against current version of script by sha256sum
+ setup_hash = "84d4d2b394eb3a16dcbcf89c8f7c223eec5eb96bc2f88dee617085566986966d"
+
+ print('Downloading node-setup-... ', end='')
+ fd, node_setup = tempfile.mkstemp(prefix=os.path.basename(node_setup_url))
+ os.close(fd)
+
+ try:
+ self.http_download_and_save(node_setup_url, node_setup, setup_hash)
+ mode = os.stat(node_setup).st_mode
+ os.chmod(node_setup, mode | stat.S_IRWXU)
+ print('Ok')
+ print('Running node-setup...')
+ self.run_as_root([node_setup])
+ self.run_as_root(['apt-get', 'install', '-y', 'nodejs'])
+ finally:
+ try:
+ os.remove(node_setup)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+