Bug 1580280 - [mozlint] Run |mach lint| with Python 3 and drop support for Python 2 r=mars
☠☠ backed out by 8a3dbb1b5e0e ☠ ☠
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Wed, 25 Sep 2019 20:03:14 +0000
changeset 494974 371641b1010b834f4144fdb8688a511c381a6776
parent 494973 ed5efc0182326c0a131b71cd150da351d5b4d400
child 494975 570be65ab4acd4f40ce48dd48544314cbd55c5c4
push id114131
push userdluca@mozilla.com
push dateThu, 26 Sep 2019 09:47:34 +0000
treeherdermozilla-inbound@1dc1a755079a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmars
bugs1580280
milestone71.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1580280 - [mozlint] Run |mach lint| with Python 3 and drop support for Python 2 r=mars Differential Revision: https://phabricator.services.mozilla.com/D45441
mach
python/mozlint/mozlint/__init__.py
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/editor.py
python/mozlint/mozlint/errors.py
python/mozlint/mozlint/formatters/__init__.py
python/mozlint/mozlint/formatters/compact.py
python/mozlint/mozlint/formatters/stylish.py
python/mozlint/mozlint/formatters/summary.py
python/mozlint/mozlint/formatters/treeherder.py
python/mozlint/mozlint/formatters/unix.py
python/mozlint/mozlint/pathutils.py
python/mozlint/mozlint/result.py
python/mozlint/mozlint/roller.py
python/mozlint/mozlint/types.py
python/mozlint/mozlint/util/pip.py
python/mozlint/mozlint/util/string.py
python/mozlint/setup.py
python/mozlint/test/conftest.py
python/mozlint/test/linters/external.py
python/mozlint/test/linters/global_payload.py
python/mozlint/test/python.ini
python/mozlint/test/runcli.py
python/mozlint/test/test_cli.py
python/mozlint/test/test_editor.py
python/mozlint/test/test_formatters.py
python/mozlint/test/test_parser.py
python/mozlint/test/test_pathutils.py
python/mozlint/test/test_result.py
python/mozlint/test/test_roller.py
python/mozlint/test/test_types.py
taskcluster/ci/source-test/python.yml
taskcluster/docker/lint/system-setup.sh
tools/lint/android/lints.py
tools/lint/cpp/mingw-capitalization.py
tools/lint/docs/conf.py
tools/lint/eslint/setup_helper.py
tools/lint/file-perm/__init__.py
tools/lint/file-whitespace/__init__.py
tools/lint/hooks_clang_format.py
tools/lint/hooks_js_format.py
tools/lint/license/__init__.py
tools/lint/mach_commands.py
tools/lint/py2.yml
tools/lint/python/check_compat.py
tools/lint/python/compat.py
tools/lint/python/flake8.py
tools/lint/python/flake8_requirements.txt
tools/lint/python/l10n_lint.py
tools/lint/rst/__init__.py
tools/lint/rust/__init__.py
tools/lint/shell/__init__.py
tools/lint/spell/__init__.py
tools/lint/test/python.ini
tools/lint/test/test_eslint.py
tools/lint/test/test_flake8.py
tools/lint/wpt/wpt.py
tools/lint/yamllint_/__init__.py
--- a/mach
+++ b/mach
@@ -32,17 +32,16 @@ py2commands="
     cppunittest
     cramtest
     crashtest
     devtools-css-db
     doc
     doctor
     empty-makefiles
     environment
-    eslint
     file-info
     firefox-ui-functional
     fluent-migration-test
     geckodriver
     geckodriver-test
     geckoview-junit
     gradle
     gtest
@@ -50,17 +49,16 @@ py2commands="
     import-pr
     install
     install-android
     install-desktop
     jsapi-tests
     jsshell-bench
     jstestbrowser
     jstests
-    lint
     marionette-test
     mochitest
     mozbuild-reference
     mozharness
     mozregression
     package
     package-multi-locale
     pastebin
--- a/python/mozlint/mozlint/__init__.py
+++ b/python/mozlint/mozlint/__init__.py
@@ -1,9 +1,7 @@
 # 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/.
 # flake8: noqa
 
-from __future__ import absolute_import
-
 from .roller import LintRoller
 from .result import Issue
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -1,14 +1,12 @@
 # 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, print_function, unicode_literals
-
 import os
 import sys
 from argparse import REMAINDER, ArgumentParser
 
 from mozlint.formatters import all_formatters
 
 SEARCH_PATHS = []
 
@@ -208,21 +206,16 @@ def run(paths, linters, formats, outgoin
     if edit and result.issues:
         edit_issues(result)
         result = lint.roll(result.issues.keys())
 
     for formatter_name, path in formats:
         formatter = formatters.get(formatter_name)
 
         out = formatter(result)
-        if sys.version_info[0] == 2:
-            # Encode output with 'replace' to avoid UnicodeEncodeErrors on
-            # environments that aren't using utf-8.
-            out = formatter(result).encode(sys.stdout.encoding or 'ascii', 'replace')
-
         if out:
             output_file = open(path, 'w') if path else sys.stdout
             print(out, file=output_file)
 
     return result.returncode
 
 
 if __name__ == '__main__':
--- a/python/mozlint/mozlint/editor.py
+++ b/python/mozlint/mozlint/editor.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals, print_function
-
 import os
 import subprocess
 import tempfile
 
 from mozlint import formatters
 
 
 def get_editor():
--- a/python/mozlint/mozlint/errors.py
+++ b/python/mozlint/mozlint/errors.py
@@ -1,14 +1,12 @@
 # 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
-
 
 class LintException(Exception):
     pass
 
 
 class LinterNotFound(LintException):
     def __init__(self, path):
         LintException.__init__(self, "Could not find lint file '{}'".format(path))
--- a/python/mozlint/mozlint/formatters/__init__.py
+++ b/python/mozlint/mozlint/formatters/__init__.py
@@ -1,14 +1,12 @@
 # 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 json
 
 from ..result import IssueEncoder
 from .compact import CompactFormatter
 from .stylish import StylishFormatter
 from .summary import SummaryFormatter
 from .treeherder import TreeherderFormatter
 from .unix import UnixFormatter
--- a/python/mozlint/mozlint/formatters/compact.py
+++ b/python/mozlint/mozlint/formatters/compact.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 from ..result import Issue
 
 
 class CompactFormatter(object):
     """Formatter for compact output.
 
     This formatter prints one error per line, mimicking the
     eslint 'compact' formatter.
--- a/python/mozlint/mozlint/formatters/stylish.py
+++ b/python/mozlint/mozlint/formatters/stylish.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 from mozterm import Terminal
 
 from ..result import Issue
 from ..util.string import pluralize
 
 
 class StylishFormatter(object):
     """Formatter based on the eslint default."""
--- a/python/mozlint/mozlint/formatters/summary.py
+++ b/python/mozlint/mozlint/formatters/summary.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 import os
 from collections import defaultdict
 
 import mozpack.path as mozpath
 
 from ..util.string import pluralize
 
 
--- a/python/mozlint/mozlint/formatters/treeherder.py
+++ b/python/mozlint/mozlint/formatters/treeherder.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 from ..result import Issue
 
 
 class TreeherderFormatter(object):
     """Formatter for treeherder friendly output.
 
     This formatter looks ugly, but prints output such that
     treeherder is able to highlight the errors and warnings.
--- a/python/mozlint/mozlint/formatters/unix.py
+++ b/python/mozlint/mozlint/formatters/unix.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 from ..result import Issue
 
 
 class UnixFormatter(object):
     """Formatter that respects Unix output conventions frequently
     employed by preprocessors and compilers.  The format is
     `<FILENAME>:<LINE>[:<COL>]: <RULE> <LEVEL>: <MESSAGE>`.
 
--- a/python/mozlint/mozlint/pathutils.py
+++ b/python/mozlint/mozlint/pathutils.py
@@ -1,14 +1,12 @@
 # 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 unicode_literals, absolute_import
-
 import os
 
 from mozpack import path as mozpath
 from mozpack.files import FileFinder
 
 
 class FilterPath(object):
     """Helper class to make comparing and matching file paths easier."""
--- a/python/mozlint/mozlint/result.py
+++ b/python/mozlint/mozlint/result.py
@@ -1,14 +1,12 @@
 # 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
-
 from collections import defaultdict
 from json import dumps, JSONEncoder
 import os
 import mozpack.path as mozpath
 
 
 class ResultSummary(object):
     """Represents overall result state from an entire lint run."""
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -1,32 +1,28 @@
 # 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, print_function, unicode_literals
 
+import atexit
 import copy
 import logging
 import os
 import signal
 import sys
 import time
 import traceback
 from concurrent.futures import ProcessPoolExecutor
+from concurrent.futures.process import _python_exit as futures_atexit
 from itertools import chain
 from math import ceil
-from multiprocessing import cpu_count
+from multiprocessing import cpu_count, get_context
 from multiprocessing.queues import Queue
 from subprocess import CalledProcessError
 
-try:
-    from multiprocessing import get_context
-except ImportError:
-    get_context = None
-
 import mozpack.path as mozpath
 from mozversioncontrol import get_repository_object, MissingUpstreamRepo, InvalidRepoPath
 
 from .errors import LintersNotConfigured
 from .parser import Parser
 from .pathutils import findobject
 from .result import ResultSummary
 from .types import supported_types
@@ -84,18 +80,17 @@ def _run_worker(config, paths, **lintarg
 class InterruptableQueue(Queue):
     """A multiprocessing.Queue that catches KeyboardInterrupt when a worker is
     blocking on it and returns None.
 
     This is needed to gracefully handle KeyboardInterrupts when a worker is
     blocking on ProcessPoolExecutor's call queue.
     """
     def __init__(self, *args, **kwargs):
-        if get_context:
-            kwargs['ctx'] = get_context()
+        kwargs['ctx'] = get_context()
         super(InterruptableQueue, self).__init__(*args, **kwargs)
 
     def get(self, *args, **kwargs):
         try:
             return Queue.get(self, *args, **kwargs)
         except KeyboardInterrupt:
             return None
 
@@ -106,16 +101,34 @@ def _worker_sigint_handler(signum, frame
     Tells workers not to process the extra jobs on the call queue that couldn't
     be canceled by the parent process.
     """
     global SHUTDOWN
     SHUTDOWN = True
     orig_sigint(signum, frame)
 
 
+def wrap_futures_atexit():
+    """Sometimes futures' atexit handler can spew tracebacks. This wrapper
+    suppresses them."""
+    try:
+        futures_atexit()
+    except Exception:
+        # Generally `atexit` handlers aren't supposed to raise exceptions, but the
+        # futures' handler can sometimes raise when the user presses `CTRL-C`. We
+        # suppress all possible exceptions here so users have a nice experience
+        # when canceling their lint run. Any exceptions raised by this function
+        # won't be useful anyway.
+        pass
+
+
+atexit.unregister(futures_atexit)
+atexit.register(wrap_futures_atexit)
+
+
 class LintRoller(object):
     """Registers and runs linters.
 
     :param root: Path to which relative paths will be joined. If
                  unspecified, root will either be determined from
                  version control or cwd.
     :param lintargs: Arguments to pass to the underlying linter(s).
     """
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -1,23 +1,20 @@
 # 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, unicode_literals
-
 import os
 import re
 import sys
 from abc import ABCMeta, abstractmethod
 
 from mozlog import get_default_logger, commandline, structuredlog
 from mozlog.reader import LogHandler
 from mozpack.files import FileFinder
-from six import PY2
 
 from . import result
 from .pathutils import expand_exclusions, filterpaths, findobject
 
 
 class BaseType(object):
     """Abstract base class for all types of linters."""
     __metaclass__ = ABCMeta
@@ -99,18 +96,17 @@ class LineType(BaseType):
                 errors.extend(self._lint(os.path.join(path, p), config, **lintargs))
         return errors
 
     def _lint(self, path, config, **lintargs):
         if os.path.isdir(path):
             return self._lint_dir(path, config, **lintargs)
 
         payload = config['payload']
-        kwargs = {} if PY2 else {'errors': 'replace'}
-        with open(path, 'r', **kwargs) as fh:
+        with open(path, 'r', errors='replace') as fh:
             lines = fh.readlines()
 
         errors = []
         for i, line in enumerate(lines):
             if self.condition(payload, line):
                 errors.append(result.from_config(config, path=path, lineno=i+1))
 
         return errors
--- a/python/mozlint/mozlint/util/pip.py
+++ b/python/mozlint/mozlint/util/pip.py
@@ -1,15 +1,12 @@
 # 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 print_function
-from __future__ import absolute_import
-
 import subprocess
 
 
 def _run_pip(*args):
     """
     Helper function that runs pip with subprocess
     """
     try:
--- a/python/mozlint/mozlint/util/string.py
+++ b/python/mozlint/mozlint/util/string.py
@@ -1,11 +1,9 @@
 # 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, unicode_literals
-
 
 def pluralize(s, num):
     if num != 1:
         s += 's'
     return str(num) + ' ' + s
--- a/python/mozlint/setup.py
+++ b/python/mozlint/setup.py
@@ -1,14 +1,12 @@
 # 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
-
 from setuptools import setup
 
 VERSION = 0.1
 DEPS = ["mozlog>=3.4"]
 
 setup(
     name='mozlint',
     description='Framework for registering and running micro lints',
--- a/python/mozlint/test/conftest.py
+++ b/python/mozlint/test/conftest.py
@@ -1,14 +1,12 @@
 # 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 sys
 from argparse import Namespace
 
 import pytest
 
 from mozlint import LintRoller
 
--- a/python/mozlint/test/linters/external.py
+++ b/python/mozlint/test/linters/external.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 import os
 import time
 
 from mozlint import result
 from mozlint.errors import LintException
 
 
 def badreturncode(files, config, **lintargs):
--- a/python/mozlint/test/linters/global_payload.py
+++ b/python/mozlint/test/linters/global_payload.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 from mozlint import result
 from mozpack.files import FileFinder
 import mozpack.path as mozpath
 
 from external import external
 
 
 def global_payload(config, **lintargs):
--- a/python/mozlint/test/python.ini
+++ b/python/mozlint/test/python.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 subsuite = mozlint
+skip-if = python == 2
 
 [test_cli.py]
 [test_editor.py]
 [test_formatters.py]
 [test_parser.py]
 [test_pathutils.py]
 [test_result.py]
 [test_roller.py]
--- a/python/mozlint/test/runcli.py
+++ b/python/mozlint/test/runcli.py
@@ -1,20 +1,17 @@
 # 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 sys
 
+from mozlint import cli
+
 here = os.path.abspath(os.path.dirname(__file__))
-sys.path.insert(0, os.path.join(os.path.dirname(here), 'mozlint'))
-
-from mozlint import cli
 cli.SEARCH_PATHS.append(os.path.join(here, 'linters'))
 
 if __name__ == '__main__':
     parser = cli.MozlintParser()
     args = vars(parser.parse_args(sys.argv[1:]))
     args['root'] = here
     sys.exit(cli.run(**args))
--- a/python/mozlint/test/test_cli.py
+++ b/python/mozlint/test/test_cli.py
@@ -1,14 +1,12 @@
 # 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
 from distutils.spawn import find_executable
 
 import mozunit
 import pytest
 
 from mozlint import cli
 
--- a/python/mozlint/test/test_editor.py
+++ b/python/mozlint/test/test_editor.py
@@ -1,14 +1,12 @@
 # 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 mozunit
 import pytest
 
 from mozlint import editor
 from mozlint.result import ResultSummary, Issue
--- a/python/mozlint/test/test_formatters.py
+++ b/python/mozlint/test/test_formatters.py
@@ -1,14 +1,12 @@
 # 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, unicode_literals
-
 import json
 
 import mozunit
 import mozpack.path as mozpath
 import pytest
 
 from mozlint.result import Issue, ResultSummary
 from mozlint import formatters
--- a/python/mozlint/test/test_parser.py
+++ b/python/mozlint/test/test_parser.py
@@ -1,14 +1,12 @@
 # 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 mozunit
 import pytest
 
 from mozlint.parser import Parser
 from mozlint.errors import (
     LinterNotFound,
--- a/python/mozlint/test/test_pathutils.py
+++ b/python/mozlint/test/test_pathutils.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 import os
 from fnmatch import fnmatch
 
 import mozunit
 import pytest
 
 from mozlint import pathutils
 
--- a/python/mozlint/test/test_result.py
+++ b/python/mozlint/test/test_result.py
@@ -1,14 +1,12 @@
 # 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 mozunit
 
 from mozlint.result import Issue
 from mozlint.result import ResultSummary
 
 
 def test_issue_defaults():
     ResultSummary.root = '/fake/root'
--- a/python/mozlint/test/test_roller.py
+++ b/python/mozlint/test/test_roller.py
@@ -1,14 +1,12 @@
 # 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 platform
 import signal
 import subprocess
 import sys
 import time
 
 import mozunit
@@ -221,22 +219,25 @@ def test_max_paths_per_job_global(monkey
 @pytest.mark.skipif(platform.system() == 'Windows',
                     reason="signal.CTRL_C_EVENT isn't causing a KeyboardInterrupt on Windows")
 def test_keyboard_interrupt():
     # We use two linters so we'll have two jobs. One (string.yml) will complete
     # quickly. The other (slow.yml) will run slowly.  This way the first worker
     # will be be stuck blocking on the ProcessPoolExecutor._call_queue when the
     # signal arrives and the other still be doing work.
     cmd = [sys.executable, 'runcli.py', '-l=string', '-l=slow']
+    env = os.environ.copy()
+    env['PYTHONPATH'] = os.pathsep.join(sys.path)
     proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
-                            cwd=here, universal_newlines=True)
+                            cwd=here, env=env, universal_newlines=True)
     time.sleep(1)
     proc.send_signal(signal.SIGINT)
 
     out = proc.communicate()[0]
+    print(out)
     assert 'warning: not all files were linted' in out
     assert '2 problems' in out
     assert 'Traceback' not in out
 
 
 def test_support_files(lint, linters, filedir, monkeypatch):
     jobs = []
 
--- a/python/mozlint/test/test_types.py
+++ b/python/mozlint/test/test_types.py
@@ -1,14 +1,12 @@
 # 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 mozunit
 import pytest
 import mozpack.path as mozpath
 
 from mozlint.result import Issue, ResultSummary
 
--- a/taskcluster/ci/source-test/python.yml
+++ b/taskcluster/ci/source-test/python.yml
@@ -145,17 +145,17 @@ mozharness:
             - 'testing/mozharness/**'
 
 mozlint:
     description: python/mozlint unit tests
     platform:
         - linux64/opt
         - macosx1014-64/opt
         - windows10-64/opt
-    python-version: [2]
+    python-version: [3]
     treeherder:
         symbol: mozlint
     run:
         using: python-test
         subsuite: mozlint
     when:
         files-changed:
             - 'python/mozlint/**'
--- a/taskcluster/docker/lint/system-setup.sh
+++ b/taskcluster/docker/lint/system-setup.sh
@@ -77,33 +77,33 @@ EOF
 mv fzf /usr/local/bin
 
 ###
 # Flake8 Setup
 ###
 
 cd /setup
 
-pip install --require-hashes -r /tmp/flake8_requirements.txt
+pip3 install --require-hashes -r /tmp/flake8_requirements.txt
 
 ###
 # codespell Setup
 ###
 
 cd /setup
 
-pip install --require-hashes -r /tmp/codespell_requirements.txt
+pip3 install --require-hashes -r /tmp/codespell_requirements.txt
 
 ###
 # tox Setup
 ###
 
 cd /setup
 
-pip install --require-hashes -r /tmp/tox_requirements.txt
+pip3 install --require-hashes -r /tmp/tox_requirements.txt
 
 ###
 # rustfmt
 ###
 
 cd /setup
 export RUSTUP_HOME=/build/rust
 export CARGO_HOME="$RUSTUP_HOME"
--- a/tools/lint/android/lints.py
+++ b/tools/lint/android/lints.py
@@ -1,16 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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, print_function, unicode_literals
-
 import itertools
 import json
 import os
 import re
 import six
 import subprocess
 import sys
 
--- a/tools/lint/cpp/mingw-capitalization.py
+++ b/tools/lint/cpp/mingw-capitalization.py
@@ -1,14 +1,12 @@
 # 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 re
 
 from mozlint.types import LineType
 
 here = os.path.abspath(os.path.dirname(__file__))
 HEADERS_FILE = os.path.join(here, 'mingw-headers.txt')
 # generated by cd mingw-w64/mingw-w64-headers &&
--- a/tools/lint/docs/conf.py
+++ b/tools/lint/docs/conf.py
@@ -5,18 +5,16 @@
 # -*- coding: utf-8 -*-
 #
 # mozlint documentation build configuration file, created by
 # sphinx-quickstart on Fri Nov 27 17:38:49 2015.
 #
 # This file is execfile()d with the current directory set to its
 # containing dir.
 
-from __future__ import absolute_import
-
 import os
 
 # -- General configuration ------------------------------------------------
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
--- a/tools/lint/eslint/setup_helper.py
+++ b/tools/lint/eslint/setup_helper.py
@@ -1,16 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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, print_function
-
 import json
 import os
 import platform
 import re
 import shutil
 import subprocess
 import sys
 import tempfile
--- a/tools/lint/file-perm/__init__.py
+++ b/tools/lint/file-perm/__init__.py
@@ -1,14 +1,12 @@
 # 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 platform
 
 from mozlint import result
 from mozlint.pathutils import expand_exclusions
 
 results = []
 
--- a/tools/lint/file-whitespace/__init__.py
+++ b/tools/lint/file-whitespace/__init__.py
@@ -1,14 +1,12 @@
 # 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
-
 from mozlint import result
 from mozlint.pathutils import expand_exclusions
 
 results = []
 
 
 def lint(paths, config, fix=None, **lintargs):
     files = list(expand_exclusions(paths, config, lintargs['root']))
--- a/tools/lint/hooks_clang_format.py
+++ b/tools/lint/hooks_clang_format.py
@@ -1,15 +1,13 @@
 #!/usr/bin/env python
 # 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, print_function
-
 import os
 import subprocess
 from subprocess import check_output, CalledProcessError
 import sys
 
 here = os.path.dirname(os.path.realpath(__file__))
 topsrcdir = os.path.join(here, os.pardir, os.pardir)
 
--- a/tools/lint/hooks_js_format.py
+++ b/tools/lint/hooks_js_format.py
@@ -1,15 +1,13 @@
 #!/usr/bin/env python
 # 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, print_function
-
 import os
 import subprocess
 from subprocess import check_output, CalledProcessError
 import sys
 
 here = os.path.dirname(os.path.realpath(__file__))
 topsrcdir = os.path.join(here, os.pardir, os.pardir)
 
--- a/tools/lint/license/__init__.py
+++ b/tools/lint/license/__init__.py
@@ -1,19 +1,16 @@
 # 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, print_function
-
 import os
 
 from mozlint import result
 from mozlint.pathutils import expand_exclusions
-from six import PY2
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 results = []
 
 # Official source: https://www.mozilla.org/en-US/MPL/headers/
 TEMPLATES = {
     "mpl2_license":
@@ -41,18 +38,17 @@ def load_valid_license():
         return list(filter(bool, [x.replace('\n', '') for x in l]))
 
 
 def is_valid_license(licenses, filename):
     """
     From a given file, check if we can find the license patterns
     in the X first lines of the file
     """
-    kwargs = {} if PY2 else {'errors': 'replace'}
-    with open(filename, 'r', **kwargs) as myfile:
+    with open(filename, 'r', errors='replace') as myfile:
         contents = myfile.read()
         # Empty files don't need a license.
         if not contents:
             return True
 
         for l in licenses:
             if l.lower().strip() in contents.lower():
                 return True
--- a/tools/lint/mach_commands.py
+++ b/tools/lint/mach_commands.py
@@ -1,14 +1,12 @@
 # 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, print_function, unicode_literals
-
 import argparse
 import copy
 import os
 
 from mozbuild.base import (
     BuildEnvironmentNotFoundException,
     MachCommandBase,
 )
--- a/tools/lint/py2.yml
+++ b/tools/lint/py2.yml
@@ -25,14 +25,18 @@ py2:
         - testing/mozharness
         - testing/raptor
         - testing/tools
         - testing/web-platform
         - toolkit
         - tools/rb
         - tools/update-packaging
         - xpcom
+
+        # These paths are intentionally excluded (Python 3 only)
+        - python/mozlint
+        - tools/lint
     extensions: ['py']
     support-files:
         - 'tools/lint/python/*compat*'
     type: external
     payload: python.compat:lintpy2
     setup: python.compat:setuppy2
--- a/tools/lint/python/check_compat.py
+++ b/tools/lint/python/check_compat.py
@@ -1,15 +1,13 @@
 #!/usr/bin/env python
 # 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, print_function
-
 import ast
 import json
 import sys
 
 
 def parse_file(f):
     with open(f, 'rb') as fh:
         content = fh.read()
--- a/tools/lint/python/compat.py
+++ b/tools/lint/python/compat.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 import json
 import os
 from distutils.spawn import find_executable
 
 import mozfile
 from mozprocess import ProcessHandlerMixin
 
 from mozlint import result
--- a/tools/lint/python/flake8.py
+++ b/tools/lint/python/flake8.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 import json
 import os
 import platform
 import subprocess
 import sys
 
 import mozfile
 
--- a/tools/lint/python/flake8_requirements.txt
+++ b/tools/lint/python/flake8_requirements.txt
@@ -5,32 +5,16 @@ mccabe==0.6.1 \
     --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
     --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f
 pyflakes==2.1.0 \
     --hash=sha256:f277f9ca3e55de669fba45b7393a1449009cff5a37d1af10ebb76c52765269cd \
     --hash=sha256:5e8c00e30c464c99e0b501dc160b13a14af7f27d4dffb529c556e30a159e231d
 pycodestyle==2.5.0 \
     --hash=sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56 \
     --hash=sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c
-enum34==1.1.6; python_version < '3.0' \
-    --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \
-    --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \
-    --hash=sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1 \
-    --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850
-configparser==3.7.1; python_version < '3.0' \
-    --hash=sha256:c114ff90ee2e762db972fa205f02491b1f5cf3ff950decd8542c62970c9bedac \
-    --hash=sha256:df28e045fbff307a28795b18df6ac8662be3219435560ddb068c283afab1ea7a \
-    --hash=sha256:5bd5fa2a491dc3cfe920a3f2a107510d65eceae10e9c6e547b90261a4710df32
 setuptools==40.7.1 \
     --hash=sha256:252520b7969fb4f2fcaf08c014b2891041d56f31180ec0d581297a28597205ff \
     --hash=sha256:5926bbea397d0fcec2f7946f1691f5820ef0234247b2d5fa83d30cc216d613ec
 autopep8==1.4.3 \
     --hash=sha256:33d2b5325b7e1afb4240814fe982eea3a92ebea712869bfd08b3c0393404248c
 entrypoints==0.3 \
     --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \
     --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451
-typing==3.6.6; python_version < '3.0' \
-    --hash=sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a \
-    --hash=sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4 \
-    --hash=sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d
-functools32==3.2.3-2; python_version < '3.0' \
-    --hash=sha256:f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d \
-    --hash=sha256:89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0
--- a/tools/lint/python/l10n_lint.py
+++ b/tools/lint/python/l10n_lint.py
@@ -1,12 +1,11 @@
 # 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
 
 from datetime import datetime, timedelta
 import os
 
 from mozboot import util as mb_util
 from mozlint import result, pathutils
 from mozpack import path as mozpath
 import mozversioncontrol.repoupdate
--- a/tools/lint/rst/__init__.py
+++ b/tools/lint/rst/__init__.py
@@ -1,14 +1,12 @@
 # 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, print_function, unicode_literals
-
 import os
 import subprocess
 
 from mozlint import result
 from mozlint.pathutils import expand_exclusions
 from mozlint.util import pip
 from mozfile import which
 
--- a/tools/lint/rust/__init__.py
+++ b/tools/lint/rust/__init__.py
@@ -1,19 +1,17 @@
 # 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, print_function
-from collections import namedtuple
-
 import os
 import signal
 import re
 import subprocess
+from collections import namedtuple
 
 from mozfile import which
 from mozlint import result
 from mozlint.pathutils import expand_exclusions
 from mozprocess import ProcessHandler
 
 RUSTFMT_NOT_FOUND = """
 Could not find rustfmt! Install rustfmt and try again.
--- a/tools/lint/shell/__init__.py
+++ b/tools/lint/shell/__init__.py
@@ -1,14 +1,12 @@
 # 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, print_function
-
 import os
 import json
 import signal
 
 # py2-compat
 try:
     from json.decoder import JSONDecodeError
 except ImportError:
--- a/tools/lint/spell/__init__.py
+++ b/tools/lint/spell/__init__.py
@@ -1,29 +1,26 @@
 # 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, print_function
-
 import os
 import signal
 import re
 
 # py2-compat
 try:
     from json.decoder import JSONDecodeError
 except ImportError:
     JSONDecodeError = ValueError
 
 from mozfile import which
 from mozlint import result
 from mozlint.util import pip
 from mozprocess import ProcessHandlerMixin
-from six import PY3
 
 here = os.path.abspath(os.path.dirname(__file__))
 CODESPELL_REQUIREMENTS_PATH = os.path.join(here, 'codespell_requirements.txt')
 
 CODESPELL_NOT_FOUND = """
 Could not find codespell! Install codespell and try again.
 
     $ pip install -U --require-hashes -r {}
@@ -41,17 +38,17 @@ results = []
 CODESPELL_FORMAT_REGEX = re.compile(r'(.*):(.*): (.*) ==> (.*)$')
 
 
 class CodespellProcess(ProcessHandlerMixin):
     def __init__(self, config, *args, **kwargs):
         self.config = config
         kwargs = {
             'processOutputLine': [self.process_line],
-            'universal_newlines': PY3,
+            'universal_newlines': True,
         }
         ProcessHandlerMixin.__init__(self, *args, **kwargs)
 
     def process_line(self, line):
         try:
             match = CODESPELL_FORMAT_REGEX.match(line)
             abspath, line, typo, correct = match.groups()
         except AttributeError:
--- a/tools/lint/test/python.ini
+++ b/tools/lint/test/python.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 subsuite = mozlint
+skip-if = python == 2
 
 [test_eslint.py]
 skip-if = os == "win" || os == "mac"  # node not installed on worker
 [test_flake8.py]
 requirements = tools/lint/python/flake8_requirements.txt
 skip-if = os == "mac"  # pip unable to find 'flake8==3.5.0'
 [test_file_perm.py]
 skip-if = os == "win"
--- a/tools/lint/test/test_eslint.py
+++ b/tools/lint/test/test_eslint.py
@@ -1,10 +1,8 @@
-from __future__ import absolute_import, print_function
-
 import mozunit
 
 from conftest import build
 
 LINTER = 'eslint'
 
 
 def test_lint_with_global_exclude(lint, config, paths):
--- a/tools/lint/test/test_flake8.py
+++ b/tools/lint/test/test_flake8.py
@@ -1,10 +1,8 @@
-from __future__ import absolute_import, print_function
-
 import os
 
 import mozunit
 
 LINTER = 'flake8'
 
 
 def test_lint_single_file(lint, paths):
--- a/tools/lint/wpt/wpt.py
+++ b/tools/lint/wpt/wpt.py
@@ -1,16 +1,14 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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, print_function
-
 import json
 import os
 import sys
 
 from mozprocess import ProcessHandler
 
 from mozlint import result
 
--- a/tools/lint/yamllint_/__init__.py
+++ b/tools/lint/yamllint_/__init__.py
@@ -1,26 +1,22 @@
 # 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, print_function
-
 import re
 import os
 import signal
 import subprocess
 from collections import defaultdict
 
 from mozfile import which
 from mozlint import result
 from mozlint.pathutils import get_ancestors_by_name
 from mozprocess import ProcessHandlerMixin
-from six import string_types
-
 
 here = os.path.abspath(os.path.dirname(__file__))
 YAMLLINT_REQUIREMENTS_PATH = os.path.join(here, 'yamllint_requirements.txt')
 
 
 YAMLLINT_INSTALL_ERROR = """
 Unable to install correct version of yamllint
 Try to install it manually with:
@@ -108,17 +104,17 @@ def run_process(config, cmd):
     try:
         proc.wait()
     except KeyboardInterrupt:
         proc.kill()
 
 
 def gen_yamllint_args(cmdargs, paths=None, conf_file=None):
     args = cmdargs[:]
-    if isinstance(paths, string_types):
+    if isinstance(paths, str):
         paths = [paths]
     if conf_file and conf_file != 'default':
         return args + ['-c', conf_file] + paths
     return args + paths
 
 
 def lint(files, config, **lintargs):
     log = lintargs['log']