Bug 1239139: Verify that a high enough node version is available before running eslint. r?gps draft
authorDave Townsend <dtownsend@oxymoronical.com>
Tue, 09 Feb 2016 15:34:37 -0800
changeset 330431 906a2ece350e189876711a105431099a4cb92ce2
parent 330430 fe8b8550ff4b0a6aada0a67dd65979f8668c7e0e
child 514165 9b0ad7cd53b32d7c3f79b364d33cf5fe713305f5
push id10748
push userdtownsend@mozilla.com
push dateThu, 11 Feb 2016 17:46:03 +0000
reviewersgps
bugs1239139
milestone47.0a1
Bug 1239139: Verify that a high enough node version is available before running eslint. r?gps The most common issue I'm hearing with eslint is people who have an outdated node installed. This does a quick check to verify the version is high enough before linting. MozReview-Commit-ID: Em0jn18OUYo
python/mach_commands.py
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -7,16 +7,17 @@ from __future__ import absolute_import, 
 import argparse
 import logging
 import mozpack.path as mozpath
 import os
 import platform
 import subprocess
 import sys
 import which
+from distutils.version import LooseVersion
 
 from mozbuild.base import (
     MachCommandBase,
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
@@ -29,17 +30,17 @@ environment variable, and then at your p
 with
 
 mach eslint --setup
 
 and try again.
 '''.strip()
 
 NODE_NOT_FOUND_MESSAGE = '''
-nodejs is either not installed or is installed to a non-standard path.
+nodejs v4.2.3 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:
 '''.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
@@ -156,16 +157,21 @@ class MachCommands(MachCommandBase):
     @CommandArgument('-e', '--ext', default='[.js,.jsm,.jsx,.xml,.html]',
         help='Filename extensions to lint, default: "[.js,.jsm,.jsx,.xml,.html]".')
     @CommandArgument('-b', '--binary', default=None,
         help='Path to eslint binary.')
     @CommandArgument('args', nargs=argparse.REMAINDER)  # Passed through to eslint.
     def eslint(self, setup, ext=None, binary=None, args=None):
         '''Run eslint.'''
 
+        # eslint requires at least node 4.2.3
+        nodePath = self.getNodeOrNpmPath("node", LooseVersion("4.2.3"))
+        if not nodePath:
+            return 1
+
         if setup:
             return self.eslint_setup()
 
         if not binary:
             binary = os.environ.get('ESLINT', None)
             if not binary:
                 try:
                     binary = which.which('eslint')
@@ -204,21 +210,16 @@ class MachCommands(MachCommandBase):
         """Ensure eslint is optimally configured.
 
         This command will inspect your eslint configuration and
         guide you through an interactive wizard helping you configure
         eslint for optimal use on Mozilla projects.
         """
         sys.path.append(os.path.dirname(__file__))
 
-        # At the very least we need node installed.
-        nodePath = self.getNodeOrNpmPath("node")
-        if not nodePath:
-            return 1
-
         npmPath = self.getNodeOrNpmPath("npm")
         if not npmPath:
             return 1
 
         # Install eslint.
         success = self.callProcess("eslint",
                                    [npmPath, "install", "eslint", "-g"])
         if not success:
@@ -270,32 +271,34 @@ class MachCommands(MachCommandBase):
 
         return list({
             "%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")
         })
 
-    def getNodeOrNpmPath(self, filename):
+    def getNodeOrNpmPath(self, filename, minversion=None):
         """
         Return the nodejs or npm path.
         """
         if platform.system() == "Windows":
             for ext in [".cmd", ".exe", ""]:
                 try:
                     nodeOrNpmPath = which.which(filename + ext,
                                                 path=self.getPossibleNodePathsWin())
-                    if self.is_valid(nodeOrNpmPath):
+                    if self.is_valid(nodeOrNpmPath, minversion):
                         return nodeOrNpmPath
                 except which.WhichError:
                     pass
         else:
             try:
-                return which.which(filename)
+                nodeOrNpmPath = which.which(filename)
+                if self.is_valid(nodeOrNpmPath, minversion):
+                    return nodeOrNpmPath
             except which.WhichError:
                 pass
 
         if filename == "node":
             print(NODE_NOT_FOUND_MESSAGE)
         elif filename == "npm":
             print(NPM_NOT_FOUND_MESSAGE)
 
@@ -306,15 +309,19 @@ class MachCommands(MachCommandBase):
                 print("  - %s" % p)
         elif platform.system() == "Darwin":
             print("  - /usr/local/bin/node")
         elif platform.system() == "Linux":
             print("  - /usr/bin/nodejs")
 
         return None
 
-    def is_valid(self, path):
+    def is_valid(self, path, minversion = None):
         try:
-            with open(os.devnull, "w") as fnull:
-                subprocess.check_call([path, "--version"], stdout=fnull)
-                return True
+            version_str = subprocess.check_output([path, "--version"],
+                                                  stderr=subprocess.STDOUT)
+            if minversion:
+                # nodejs prefixes its version strings with "v"
+                version = LooseVersion(version_str.lstrip('v'))
+                return version >= minversion
+            return True
         except (subprocess.CalledProcessError, WindowsError):
             return False