author Nick Fitzgerald <>
Thu, 29 Oct 2015 17:28:32 -0700
changeset 304421 b7e22cd2ec02c00065bbd5c2f35e923f7abc3a22
parent 304369 02b4f070f3f2a4c9f009fb1f3a30dc46588425d6
child 308620 85bf0c3e44fd07810cfcc948cd55ef0b300ea5f8
permissions -rw-r--r--
Bug 1219820 - Do not try and select the hidden root when navigating the heap view tree with keyboard shortcuts; r=jsantell

# 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

from __future__ import absolute_import, print_function, unicode_literals

import argparse
import logging
import mozpack.path as mozpath
import os
import platform
import subprocess
import sys
import which

from mozbuild.base import (

from mach.decorators import (

Could not find eslint!  We looked at the --binary option, at the ESLINT
environment variable, and then at your path.  Install eslint and needed plugins

mach eslint --setup

and try again.

nodejs is either not installed or is installed to a non-standard path.
Please install nodejs from and try again.

Valid installation paths:

Node Package Manager (npm) is either not installed or installed to a
non-standard path. Please install npm from (it comes as an
option in the node installation) and try again.

Valid installation paths:

Would you like to use eslint

eslint-plugin-mozilla is an eslint plugin containing rules that help enforce
JavaScript coding standards in the Mozilla project. Would you like to use this

eslint-plugin-react is an eslint plugin containing rules that help React
developers follow strict guidelines. Would you like to install it

class MachCommands(MachCommandBase):
    @Command('python', category='devenv',
        description='Run Python.')
    @CommandArgument('args', nargs=argparse.REMAINDER)
    def python(self, args):
        # Avoid logging the command


        return self.run_process([self.virtualenv_manager.python_path] + args,
            pass_thru=True,  # Allow user to run Python interactively.
            ensure_exit_code=False,  # Don't throw on non-zero exit code.
            # Note: subprocess requires native strings in os.environ on Windows
            append_env={b'PYTHONDONTWRITEBYTECODE': str('1')})

    @Command('python-test', category='testing',
        description='Run Python unit tests.')
        help='Verbose output.')
        help='Stop running tests after the first error or failure.')
    @CommandArgument('tests', nargs='+',
        help='Tests to run. Each test can be a single file or a directory.')
    def python_test(self, tests, verbose=False, stop=False):
        import glob

        # Python's unittest, and in particular discover, has problems with
        # clashing namespaces when importing multiple test modules. What follows
        # is a simple way to keep environments separate, at the price of
        # launching Python multiple times. This also runs tests via mozunit,
        # which produces output in the format Mozilla infrastructure expects.
        return_code = 0
        files = []
        # We search for files in both the current directory (for people running
        # from topsrcdir or cd'd into their test directory) and topsrcdir (to
        # support people running mach from the objdir).  The |break|s in the
        # loop below ensure that we don't run tests twice if we're running mach
        # from topsrcdir
        search_dirs = ['.', self.topsrcdir]
        last_search_dir = search_dirs[-1]
        for t in tests:
            for d in search_dirs:
                test = mozpath.join(d, t)
                if test.endswith('.py') and os.path.isfile(test):
                elif os.path.isfile(test + '.py'):
                    files.append(test + '.py')
                elif os.path.isdir(test):
                    files += glob.glob(mozpath.join(test, 'test*.py'))
                    files += glob.glob(mozpath.join(test, 'unit*.py'))
                elif d == last_search_dir:
                    self.log(logging.WARN, 'python-test',
                             {'test': t},
                             'TEST-UNEXPECTED-FAIL | Invalid test: {test}')
                    if stop:
                        return 1

        for f in files:
            file_displayed_test = []  # Used as a boolean.

            def _line_handler(line):
                if not file_displayed_test and line.startswith('TEST-'):

            inner_return_code = self.run_process(
                [self.virtualenv_manager.python_path, f],
                ensure_exit_code=False,  # Don't throw on non-zero exit code.
                # subprocess requires native strings in os.environ on Windows
                append_env={b'PYTHONDONTWRITEBYTECODE': str('1')},
            return_code += inner_return_code

            if not file_displayed_test:
                self.log(logging.WARN, 'python-test', {'file': f},
                         'TEST-UNEXPECTED-FAIL | No test output (missing mozunit.main() call?): {file}')

            if verbose:
                if inner_return_code != 0:
                    self.log(logging.INFO, 'python-test', {'file': f},
                             'Test failed: {file}')
                    self.log(logging.INFO, 'python-test', {'file': f},
                             'Test passed: {file}')
            if stop and return_code > 0:
                return 1

        return 0 if return_code == 0 else 1

    @Command('eslint', category='devenv',
        description='Run eslint or help configure eslint for optimal development.')
    @CommandArgument('-s', '--setup', default=False, action='store_true',
        help='configure eslint for optimal development.')
    @CommandArgument('path', nargs='?', default='.',
        help='Path to files to lint, like "browser/components/loop" '
            'or "mobile/android". '
            'Defaults to the current directory if not given.')
    @CommandArgument('-e', '--ext', default='[.js,.jsm,.jsx]',
        help='Filename extensions to lint, default: "[.js,.jsm,.jsx]".')
    @CommandArgument('-b', '--binary', default=None,
        help='Path to eslint binary.')
    @CommandArgument('args', nargs=argparse.REMAINDER)  # Passed through to eslint.
    def eslint(self, setup, path, ext=None, binary=None, args=[]):
        '''Run eslint.'''

        if setup:
            return self.eslint_setup()

        if not binary:
            binary = os.environ.get('ESLINT', None)
            if not binary:
                    binary = which.which('eslint')
                except which.WhichError:

        if not binary:
            return 1

        # The cwd below is unfortunate.  eslint --config=PATH/TO/.eslintrc works,
        # but --ignore-path=PATH/TO/.eslintignore treats paths as relative to
        # the current directory, rather than as relative to the location of
        # .eslintignore (see
        # mach commands always execute in the topsrcdir, so we could make all
        # paths in .eslint relative to the topsrcdir, but it's not clear if
        # that's a good choice for future eslint and IDE integrations.
        # Unfortunately, running after chdir does not print the full path to
        # files (convenient for opening with copy-and-paste).  In the meantime,
        # we just print the active path.

        self.log(logging.INFO, 'eslint', {'binary': binary, 'path': path},
            'Running {binary} in {path}')

        cmd_args = [binary,
            '--ext', ext,  # This keeps ext as a single argument.
        ] + args
        # Path must come after arguments.  Path is '.' due to cwd below.
        cmd_args += ['.']

        return self.run_process(cmd_args,
            pass_thru=True,  # Allow user to run eslint interactively.
            ensure_exit_code=False,  # Don't throw on non-zero exit code.
    def eslint_setup(self, update_only=False):
        """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.

        # 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:
            return 1

        # Install eslint-plugin-mozilla.
        success = self.callProcess("eslint-plugin-mozilla",
                                   [npmPath, "link"],
        if not success:
            return 1

        # Install eslint-plugin-react.
        success = self.callProcess("eslint-plugin-react",
                                   [npmPath, "install", "eslint-plugin-react", "-g"])
        if not success:
            return 1

        print("\nESLint and approved plugins installed successfully!")

    def callProcess(self, name, cmd, cwd=None):
        print("\nInstalling %s using \"%s\"..." % (name, " ".join(cmd)))

            with open(os.devnull, "w") as fnull:
                subprocess.check_call(cmd, cwd=cwd, stdout=fnull)
        except subprocess.CalledProcessError:
            if cwd:
                print("\nError installing %s in the %s folder, aborting." % (name, cwd))
                print("\nError installing %s, aborting." % name)

            return False

        return True

    def getPossibleNodePathsWin(self):
        Return possible nodejs paths on Windows.
        if platform.system() != "Windows":
            return []

        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):
        Return the nodejs or npm path.
        if platform.system() == "Windows":
            for ext in [".cmd", ".exe", ""]:
                    nodeOrNpmPath = which.which(filename + ext,
                    if self.is_valid(nodeOrNpmPath):
                        return nodeOrNpmPath
                except which.WhichError:
                return which.which(filename)
            except which.WhichError:

        if filename == "node":
        elif filename == "npm":

        if platform.system() == "Windows":
            appPaths = self.getPossibleNodePathsWin()

            for p in appPaths:
                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):
            with open(os.devnull, "w") as fnull:
                subprocess.check_call([path, "--version"], stdout=fnull)
                return True
        except (subprocess.CalledProcessError, WindowsError):
            return False