py2to3: update more *.py files to PY3

Details:
- use six.ensure_*
- use absl.flags instead of gflags
- BUILD files with a BUILD.tools file: the BUILD
  uses "PY3" and BUILD.tools uses "PY2"
- BUILD files without a BUILD.tools file use the
  PY_BINARY_VERSION variable, whose value is "PY3"
  in the source tree but "PY2" in @bazel_tools.
  Rationale: this retains compatibility with
  Python2 runtimes for users who just want to run
  Bazel (and its embedded Python scripts) but
  don't build Bazel itself.
PiperOrigin-RevId: 278601455
diff --git a/src/create_embedded_tools.py b/src/create_embedded_tools.py
index 52adf63..78e6685 100644
--- a/src/create_embedded_tools.py
+++ b/src/create_embedded_tools.py
@@ -56,6 +56,7 @@
     ('*external/openjdk_*/file/*.zip', lambda x: 'jdk.zip'),
     ('*src/minimal_jdk.tar.gz', lambda x: 'jdk.tar.gz'),
     ('*src/minimal_jdk.zip', lambda x: 'jdk.zip'),
+    ('*.bzl.tools', lambda x: x[:-6]),
     ('*', lambda x: re.sub(r'^.*bazel-out/[^/]*/bin/', '', x, count=1)),
 ]
 
diff --git a/src/test/starlark/BUILD b/src/test/starlark/BUILD
index c560f7b..48e827e 100644
--- a/src/test/starlark/BUILD
+++ b/src/test/starlark/BUILD
@@ -1,9 +1,9 @@
+load("//tools/python:private/py_test_alias.bzl", "py_test_alias")
 load(
     "//tools/build_rules:test_rules.bzl",
-    "rule_test",
     "file_test",
+    "rule_test",
 )
-load("//tools/python:private/defs.bzl", "py_test")
 
 package(default_visibility = ["//src:__subpackages__"])
 
@@ -13,7 +13,7 @@
 )
 
 [
-    py_test(
+    py_test_alias(
         name = "starlark_test_" + test_file.replace(".", "_"),
         srcs = [
             "starlark_test.py",
@@ -25,7 +25,6 @@
             test_file,
         ],
         main = "starlark_test.py",
-        python_version = "PY2",
     )
     for test_file in glob(["testdata/*"])
 ]
diff --git a/tools/aquery_differ/BUILD b/tools/aquery_differ/BUILD
index 039d3af..5f7a4f3 100644
--- a/tools/aquery_differ/BUILD
+++ b/tools/aquery_differ/BUILD
@@ -12,7 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("//tools/python:private/defs.bzl", "py_library", "py_binary", "py_test")
+load("//tools/python:private/py_test_alias.bzl", "py_test_alias")
+load("//tools/python:private/defs.bzl", "py_binary", "py_library")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -33,8 +34,11 @@
 py_binary(
     name = "aquery_differ",
     srcs = ["aquery_differ.py"],
-    python_version = "PY2",
-    deps = [":aquery_differ_main_lib"],
+    python_version = "PY3",
+    deps = [
+        ":aquery_differ_main_lib",
+        "//third_party/py/six",
+    ],
 )
 
 py_library(
@@ -44,16 +48,17 @@
         ":aquery_differ_resolvers",
         "//src/main/protobuf:analysis_py_proto",
         "//third_party/py/abseil",
+        "//third_party/py/six",
     ],
 )
 
-py_test(
+py_test_alias(
     name = "aquery_differ_test",
     srcs = ["aquery_differ_test.py"],
-    python_version = "PY2",
     deps = [
         ":aquery_differ_main_lib",
         "//src/main/protobuf:analysis_py_proto",
         "//third_party/py/mock",
+        "//third_party/py/six",
     ],
 )
diff --git a/tools/aquery_differ/aquery_differ.py b/tools/aquery_differ/aquery_differ.py
index 5774b76..7776b98 100644
--- a/tools/aquery_differ/aquery_differ.py
+++ b/tools/aquery_differ/aquery_differ.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2018 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,12 +33,17 @@
 --attrs=inputs
 """
 
+from __future__ import absolute_import
+from __future__ import division
 from __future__ import print_function
 import difflib
 import os
 import sys
+
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
 from absl import app
 from absl import flags
+from six.moves import map
 from google.protobuf import text_format
 from src.main.protobuf import analysis_pb2
 from tools.aquery_differ.resolvers.dep_set_resolver import DepSetResolver
diff --git a/tools/aquery_differ/aquery_differ_test.py b/tools/aquery_differ/aquery_differ_test.py
index 4c51643..b5b8bec 100644
--- a/tools/aquery_differ/aquery_differ_test.py
+++ b/tools/aquery_differ/aquery_differ_test.py
@@ -1,3 +1,5 @@
+# Lint as: python2, python3
+# pylint: disable=g-direct-third-party-import
 # Copyright 2018 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,15 +15,17 @@
 # limitations under the License.
 
 import unittest
+
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+from third_party.py import mock
+import six
+if six.PY2:
+  from cStringIO import StringIO
+else:
+  from io import StringIO
+
 from src.main.protobuf import analysis_pb2
 from tools.aquery_differ import aquery_differ
-from third_party.py import mock
-try:
-  # Python 2
-  from cStringIO import StringIO
-except ImportError:
-  # Python 3
-  from io import StringIO
 
 
 def make_aquery_output(actions, artifact_paths):
diff --git a/tools/build_defs/hash/sha256.py b/tools/build_defs/hash/sha256.py
index ec43ae9..f1a4ccf3 100755
--- a/tools/build_defs/hash/sha256.py
+++ b/tools/build_defs/hash/sha256.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -15,6 +16,9 @@
 
 # TODO(dmarting): instead of this tool we should make SHA-256 of artifacts
 # available in Skylark.
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
 import hashlib
 import sys
 
diff --git a/tools/build_defs/pkg/BUILD b/tools/build_defs/pkg/BUILD
index 0f34d18..0edf04e 100644
--- a/tools/build_defs/pkg/BUILD
+++ b/tools/build_defs/pkg/BUILD
@@ -1,6 +1,8 @@
 # -*- coding: utf-8 -*-
+load("//tools/build_defs/pkg:pkg.bzl", "pkg_deb", "pkg_tar")
 load("//tools/config:common_settings.bzl", "bool_flag")
-load("//tools/python:private/defs.bzl", "py_library", "py_binary", "py_test")
+load("//tools/python:private/defs.bzl", "py_binary", "py_library", "py_test")
+load("//tools/python:private/version.bzl", "PY_BINARY_VERSION")
 
 licenses(["notice"])  # Apache 2.0
 
@@ -41,7 +43,7 @@
         "testenv.py",
     ],
     data = [":archive_testdata"],
-    python_version = "PY2",
+    python_version = PY_BINARY_VERSION,
     srcs_version = "PY2AND3",
     tags = [
         # archive.py requires xzcat, which is not available by default on Mac
@@ -62,12 +64,12 @@
 py_binary(
     name = "build_tar",
     srcs = ["build_tar.py"],
-    python_version = "PY2",
+    python_version = PY_BINARY_VERSION,
     srcs_version = "PY2AND3",
     visibility = ["//visibility:public"],
     deps = [
         ":archive",
-        "//third_party/py/gflags",
+        "//third_party/py/abseil",
     ],
 )
 
@@ -77,12 +79,12 @@
     deprecation = "The internal version of make_deb is deprecated. Please " +
                   "use the replacement for pkg_deb from " +
                   "https://github.com/bazelbuild/rules_pkg/blob/master/pkg.",
-    python_version = "PY2",
+    python_version = PY_BINARY_VERSION,
     srcs_version = "PY2AND3",
     visibility = ["//visibility:public"],
     deps = [
         ":archive",
-        "//third_party/py/gflags",
+        "//third_party/py/abseil",
     ],
 )
 
@@ -93,7 +95,7 @@
     deprecation = "The internal version of make_rpm is deprecated. Please " +
                   "use the replacement for pkg_rpm from " +
                   "https://github.com/bazelbuild/rules_pkg/blob/master/pkg.",
-    python_version = "PY2",
+    python_version = PY_BINARY_VERSION,
     srcs_version = "PY2AND3",
     visibility = ["//visibility:public"],
     deps = [
@@ -107,14 +109,14 @@
     srcs_version = "PY2AND3",
     visibility = ["//visibility:public"],
     deps = [
-        "//third_party/py/gflags",
+        "//third_party/py/abseil",
     ],
 )
 
 py_test(
     name = "make_rpm_test",
     srcs = ["make_rpm_test.py"],
-    python_version = "PY2",
+    python_version = PY_BINARY_VERSION,
     srcs_version = "PY2AND3",
     # rpmbuild is not available in windows
     tags = [
@@ -126,7 +128,6 @@
 )
 
 # tests
-load("//tools/build_defs/pkg:pkg.bzl", "pkg_deb", "pkg_tar")
 
 genrule(
     name = "generate_files",
diff --git a/tools/build_defs/pkg/archive.py b/tools/build_defs/pkg/archive.py
index 2a06dcf..52c2691 100644
--- a/tools/build_defs/pkg/archive.py
+++ b/tools/build_defs/pkg/archive.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,11 +15,15 @@
 """Archive manipulation library for the Docker rules."""
 
 # pylint: disable=g-import-not-at-top
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
 import gzip
 import io
 import os
 import subprocess
 import tarfile
+import six
 
 # Use a deterministic mtime that doesn't confuse other programs.
 # See: https://github.com/bazelbuild/bazel/issues/1299
@@ -82,7 +87,7 @@
   def __enter__(self):
     self.f = open(self.filename, 'rb')
     if self.f.read(len(self.MAGIC_STRING)) != self.MAGIC_STRING:
-      raise self.ArError('Not a ar file: ' + self.filename)
+      raise self.ArError('Not a ar file: ' + six.ensure_str(self.filename))
     return self
 
   def __exit__(self, t, v, traceback):
@@ -129,7 +134,7 @@
     # Support xz compression through xz... until we can use Py3
     self.xz = compression in ['xz', 'lzma']
     self.name = name
-    self.root_directory = root_directory.rstrip('/')
+    self.root_directory = six.ensure_str(root_directory).rstrip('/')
     if default_mtime is None:
       self.default_mtime = 0
     elif default_mtime == 'portable':
@@ -181,8 +186,8 @@
       TarFileWriter.Error: when the recursion depth has exceeded the
                            `depth` argument.
     """
-    if not (name == self.root_directory or name.startswith('/') or
-            name.startswith(self.root_directory + '/')):
+    if not (name == self.root_directory or six.ensure_str(name).startswith('/')
+            or name.startswith(self.root_directory + '/')):
       name = os.path.join(self.root_directory, name)
     if mtime is None:
       mtime = self.default_mtime
@@ -193,14 +198,15 @@
       # Add the x bit to directories to prevent non-traversable directories.
       # The x bit is set only to if the read bit is set.
       dirmode = (mode | ((0o444 & mode) >> 2)) if mode else mode
-      self.add_file(name + '/',
-                    tarfile.DIRTYPE,
-                    uid=uid,
-                    gid=gid,
-                    uname=uname,
-                    gname=gname,
-                    mtime=mtime,
-                    mode=dirmode)
+      self.add_file(
+          six.ensure_str(name) + '/',
+          tarfile.DIRTYPE,
+          uid=uid,
+          gid=gid,
+          uname=uname,
+          gname=gname,
+          mtime=mtime,
+          mode=dirmode)
       if depth <= 0:
         raise self.Error('Recursion depth exceeded, probably in '
                          'an infinite directory loop.')
@@ -225,15 +231,16 @@
 
   def _addfile(self, info, fileobj=None):
     """Add a file in the tar file if there is no conflict."""
-    if not info.name.endswith('/') and info.type == tarfile.DIRTYPE:
+    if not six.ensure_str(
+        info.name).endswith('/') and info.type == tarfile.DIRTYPE:
       # Enforce the ending / for directories so we correctly deduplicate.
       info.name += '/'
     if info.name not in self.members:
       self.tar.addfile(info, fileobj)
       self.members.add(info.name)
     elif info.type != tarfile.DIRTYPE:
-      print('Duplicate file in archive: %s, '
-            'picking first occurrence' % info.name)
+      print(('Duplicate file in archive: %s, '
+             'picking first occurrence' % info.name))
 
   def add_file(self,
                name,
@@ -302,7 +309,7 @@
     if link:
       tarinfo.linkname = link
     if content:
-      content_bytes = content.encode('utf-8')
+      content_bytes = six.ensure_binary(content, 'utf-8')
       tarinfo.size = len(content_bytes)
       self._addfile(tarinfo, io.BytesIO(content_bytes))
     elif file_content:
@@ -340,7 +347,7 @@
     """
     if root and root[0] not in ['/', '.']:
       # Root prefix should start with a '/', adds it if missing
-      root = '/' + root
+      root = '/' + six.ensure_str(root)
     compression = os.path.splitext(tar)[-1][1:]
     if compression == 'tgz':
       compression = 'gz'
@@ -372,9 +379,9 @@
         # prevent performance issues due to accidentally-introduced seeks
         # during intar traversal by opening in "streaming" mode. gz, bz2
         # are supported natively by python 2.7 and 3.x
-        inmode = 'r|' + compression
+        inmode = 'r|' + six.ensure_str(compression)
       else:
-        inmode = 'r:' + compression
+        inmode = 'r:' + six.ensure_str(compression)
       intar = tarfile.open(name=tar, mode=inmode)
     for tarinfo in intar:
       if name_filter is None or name_filter(tarinfo.name):
diff --git a/tools/build_defs/pkg/archive_test.py b/tools/build_defs/pkg/archive_test.py
index 2c52fbd..a2e0df4 100644
--- a/tools/build_defs/pkg/archive_test.py
+++ b/tools/build_defs/pkg/archive_test.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,11 +14,18 @@
 # limitations under the License.
 """Testing for archive."""
 
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import os
 import os.path
 import tarfile
 import unittest
 
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+import six
+
 from tools.build_defs.pkg import archive
 from tools.build_defs.pkg import testenv
 
@@ -64,10 +72,11 @@
 
   def assertSimpleFileContent(self, names):
     datafile = os.path.join(testenv.TESTDATA_PATH, "_".join(names) + ".ar")
-    content = [{"filename": n,
-                "size": len(n.encode("utf-8")),
-                "data": n.encode("utf-8")}
-               for n in names]
+    content = [{
+        "filename": n,
+        "size": len(six.ensure_binary(n, "utf-8")),
+        "data": six.ensure_binary(n, "utf-8")
+    } for n in names]
     self.assertArFileContent(datafile, content)
 
   def testAFile(self):
@@ -143,11 +152,13 @@
     with archive.TarFileWriter(self.tempfile) as f:
       for n in names:
         f.add_file(n, content=n)
-    content = ([{"name": "."}] +
-               [{"name": n,
-                 "size": len(n.encode("utf-8")),
-                 "data": n.encode("utf-8")}
-                for n in names])
+    content = ([{
+        "name": "."
+    }] + [{
+        "name": n,
+        "size": len(six.ensure_binary(n, "utf-8")),
+        "data": six.ensure_binary(n, "utf-8")
+    } for n in names])
     self.assertTarFileContent(self.tempfile, content)
 
   def testAddFile(self):
diff --git a/tools/build_defs/pkg/build_tar.py b/tools/build_defs/pkg/build_tar.py
index 087dbc4..85c07b9 100644
--- a/tools/build_defs/pkg/build_tar.py
+++ b/tools/build_defs/pkg/build_tar.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,78 +17,80 @@
 import json
 import os
 import os.path
-import sys
 import tarfile
 import tempfile
 
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+from absl import app
+from absl import flags
+
 from tools.build_defs.pkg import archive
-from third_party.py import gflags
 
-gflags.DEFINE_string('output', None, 'The output file, mandatory')
-gflags.MarkFlagAsRequired('output')
+flags.DEFINE_string('output', None, 'The output file, mandatory')
+flags.mark_flag_as_required('output')
 
-gflags.DEFINE_multistring('file', [], 'A file to add to the layer')
+flags.DEFINE_multi_string('file', [], 'A file to add to the layer')
 
-gflags.DEFINE_string('manifest', None,
-                     'JSON manifest of contents to add to the layer')
+flags.DEFINE_string('manifest', None,
+                    'JSON manifest of contents to add to the layer')
 
-gflags.DEFINE_string('mode', None,
-                     'Force the mode on the added files (in octal).')
+flags.DEFINE_string('mode', None,
+                    'Force the mode on the added files (in octal).')
 
-gflags.DEFINE_string(
+flags.DEFINE_string(
     'mtime', None, 'Set mtime on tar file entries. May be an integer or the'
     ' value "portable", to get the value 2000-01-01, which is'
     ' is usable with non *nix OSes')
 
-gflags.DEFINE_multistring('empty_file', [], 'An empty file to add to the layer')
+flags.DEFINE_multi_string('empty_file', [], 'An empty file to add to the layer')
 
-gflags.DEFINE_multistring('empty_dir', [], 'An empty dir to add to the layer')
+flags.DEFINE_multi_string('empty_dir', [], 'An empty dir to add to the layer')
 
-gflags.DEFINE_multistring('empty_root_dir', [],
+flags.DEFINE_multi_string('empty_root_dir', [],
                           'An empty dir to add to the layer')
 
-gflags.DEFINE_multistring('tar', [], 'A tar file to add to the layer')
+flags.DEFINE_multi_string('tar', [], 'A tar file to add to the layer')
 
-gflags.DEFINE_multistring('deb', [], 'A debian package to add to the layer')
+flags.DEFINE_multi_string('deb', [], 'A debian package to add to the layer')
 
-gflags.DEFINE_multistring(
+flags.DEFINE_multi_string(
     'link', [],
     'Add a symlink a inside the layer ponting to b if a:b is specified')
-gflags.RegisterValidator(
+flags.register_validator(
     'link',
     lambda l: all(value.find(':') > 0 for value in l),
     message='--link value should contains a : separator')
 
-gflags.DEFINE_string('directory', None,
-                     'Directory in which to store the file inside the layer')
+flags.DEFINE_string('directory', None,
+                    'Directory in which to store the file inside the layer')
 
-gflags.DEFINE_string('compression', None,
-                     'Compression (`gz` or `bz2`), default is none.')
+flags.DEFINE_string('compression', None,
+                    'Compression (`gz` or `bz2`), default is none.')
 
-gflags.DEFINE_multistring(
+flags.DEFINE_multi_string(
     'modes', None,
     'Specific mode to apply to specific file (from the file argument),'
     ' e.g., path/to/file=0455.')
 
-gflags.DEFINE_multistring(
+flags.DEFINE_multi_string(
     'owners', None, 'Specify the numeric owners of individual files, '
     'e.g. path/to/file=0.0.')
 
-gflags.DEFINE_string(
+flags.DEFINE_string(
     'owner', '0.0', 'Specify the numeric default owner of all files,'
     ' e.g., 0.0')
 
-gflags.DEFINE_string('owner_name', None,
-                     'Specify the owner name of all files, e.g. root.root.')
+flags.DEFINE_string('owner_name', None,
+                    'Specify the owner name of all files, e.g. root.root.')
 
-gflags.DEFINE_multistring(
+flags.DEFINE_multi_string(
     'owner_names', None, 'Specify the owner names of individual files, e.g. '
     'path/to/file=root.root.')
 
-gflags.DEFINE_string('root_directory', './',
-                     'Default root directory is named "."')
+flags.DEFINE_string('root_directory', './',
+                    'Default root directory is named "."')
 
-FLAGS = gflags.FLAGS
+FLAGS = flags.FLAGS
 
 
 class TarFile(object):
@@ -384,4 +387,4 @@
 
 
 if __name__ == '__main__':
-  main(FLAGS(sys.argv))
+  app.run(main)
diff --git a/tools/build_defs/pkg/make_deb.py b/tools/build_defs/pkg/make_deb.py
index 45fe47c..1bdb3c4 100644
--- a/tools/build_defs/pkg/make_deb.py
+++ b/tools/build_defs/pkg/make_deb.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,17 +14,23 @@
 # limitations under the License.
 """A simple cross-platform helper to create a debian package."""
 
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import gzip
 import hashlib
 from io import BytesIO
 import os
 import os.path
-import sys
 import tarfile
 import textwrap
 import time
 
-from third_party.py import gflags
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+from absl import app
+from absl import flags
+import six
 
 # list of debian fields : (name, mandatory, wrap[, default])
 # see http://www.debian.org/doc/debian-policy/ch-controlfields.html
@@ -48,28 +55,27 @@
     ('Urgency', False, False, 'medium'),
 ]
 
-gflags.DEFINE_string('output', None, 'The output file, mandatory')
-gflags.MarkFlagAsRequired('output')
+flags.DEFINE_string('output', None, 'The output file, mandatory')
+flags.mark_flag_as_required('output')
 
-gflags.DEFINE_string('changes', None, 'The changes output file, mandatory.')
-gflags.MarkFlagAsRequired('changes')
+flags.DEFINE_string('changes', None, 'The changes output file, mandatory.')
+flags.mark_flag_as_required('changes')
 
-gflags.DEFINE_string('data', None,
-                     'Path to the data tarball, mandatory')
-gflags.MarkFlagAsRequired('data')
+flags.DEFINE_string('data', None, 'Path to the data tarball, mandatory')
+flags.mark_flag_as_required('data')
 
-gflags.DEFINE_string('preinst', None,
-                     'The preinst script (prefix with @ to provide a path).')
-gflags.DEFINE_string('postinst', None,
-                     'The postinst script (prefix with @ to provide a path).')
-gflags.DEFINE_string('prerm', None,
-                     'The prerm script (prefix with @ to provide a path).')
-gflags.DEFINE_string('postrm', None,
-                     'The postrm script (prefix with @ to provide a path).')
-gflags.DEFINE_string('config', None,
-                     'The config script (prefix with @ to provide a path).')
-gflags.DEFINE_string('templates', None,
-                     'The templates file (prefix with @ to provide a path).')
+flags.DEFINE_string('preinst', None,
+                    'The preinst script (prefix with @ to provide a path).')
+flags.DEFINE_string('postinst', None,
+                    'The postinst script (prefix with @ to provide a path).')
+flags.DEFINE_string('prerm', None,
+                    'The prerm script (prefix with @ to provide a path).')
+flags.DEFINE_string('postrm', None,
+                    'The postrm script (prefix with @ to provide a path).')
+flags.DEFINE_string('config', None,
+                    'The config script (prefix with @ to provide a path).')
+flags.DEFINE_string('templates', None,
+                    'The templates file (prefix with @ to provide a path).')
 
 # size of chunks for copying package content to final .deb file
 # This is a wild guess, but I am not convinced of the value of doing much work
@@ -78,25 +84,25 @@
 
 # see
 # https://www.debian.org/doc/manuals/debian-faq/ch-pkg_basics.en.html#s-conffile
-gflags.DEFINE_multistring(
+flags.DEFINE_multi_string(
     'conffile', None,
     'List of conffiles (prefix item with @ to provide a path)')
 
 
-def MakeGflags():
+def Makeflags():
   """Creates a flag for each of the control file fields."""
   for field in DEBIAN_FIELDS:
     fieldname = field[0].replace('-', '_').lower()
     msg = 'The value for the %s content header entry.' % field[0]
     if len(field) > 3:
       if isinstance(field[3], list):
-        gflags.DEFINE_multistring(fieldname, field[3], msg)
+        flags.DEFINE_multi_string(fieldname, field[3], msg)
       else:
-        gflags.DEFINE_string(fieldname, field[3], msg)
+        flags.DEFINE_string(fieldname, field[3], msg)
     else:
-      gflags.DEFINE_string(fieldname, None, msg)
+      flags.DEFINE_string(fieldname, None, msg)
     if field[1]:
-      gflags.MarkFlagAsRequired(fieldname)
+      flags.mark_flag_as_required(fieldname)
 
 
 def ConvertToFileLike(content, content_len, converter):
@@ -137,19 +143,19 @@
 
 def MakeDebianControlField(name, value, wrap=False):
   """Add a field to a debian control file."""
-  result = name + ': '
+  result = six.ensure_str(name) + ': '
   if isinstance(value, str):
-    value = value.decode('utf-8')
+    value = six.ensure_str(value, 'utf-8')
   if isinstance(value, list):
-    value = u', '.join(value)
+    value = ', '.join(six.ensure_str(v) for v in value)
   if wrap:
-    result += u' '.join(value.split('\n'))
+    result += ' '.join(six.ensure_str(value).split('\n'))
     result = textwrap.fill(result,
                            break_on_hyphens=False,
                            break_long_words=False)
   else:
     result += value
-  return result.replace(u'\n', u'\n ') + u'\n'
+  return six.ensure_str(result).replace('\n', '\n ') + '\n'
 
 
 def CreateDebControl(extrafiles=None, **kwargs):
@@ -166,15 +172,16 @@
   with gzip.GzipFile('control.tar.gz', mode='w', fileobj=tar, mtime=0) as gz:
     with tarfile.open('control.tar.gz', mode='w', fileobj=gz) as f:
       tarinfo = tarfile.TarInfo('control')
+      controlfile_utf8 = six.ensure_binary(controlfile, 'utf-8')
       # Don't discard unicode characters when computing the size
-      tarinfo.size = len(controlfile.encode('utf-8'))
-      f.addfile(tarinfo, fileobj=BytesIO(controlfile.encode('utf-8')))
+      tarinfo.size = len(controlfile_utf8)
+      f.addfile(tarinfo, fileobj=BytesIO(controlfile_utf8))
       if extrafiles:
         for name, (data, mode) in extrafiles.items():
           tarinfo = tarfile.TarInfo(name)
           tarinfo.size = len(data)
           tarinfo.mode = mode
-          f.addfile(tarinfo, fileobj=BytesIO(data.encode('utf-8')))
+          f.addfile(tarinfo, fileobj=BytesIO(six.ensure_binary(data, 'utf-8')))
   control = tar.getvalue()
   tar.close()
   return control
@@ -214,7 +221,7 @@
     AddArFileEntry(f, 'debian-binary', b'2.0\n')
     AddArFileEntry(f, 'control.tar.gz', control)
     # Tries to preserve the extension name
-    ext = os.path.basename(data).split('.')[-2:]
+    ext = six.ensure_str(os.path.basename(data)).split('.')[-2:]
     if len(ext) < 2:
       ext = 'tar'
     elif ext[1] == 'tgz':
@@ -303,15 +310,15 @@
           '\n' + ' '.join([checksums['sha256'], debsize, deb_basename]))
   ])
   with open(output, 'w') as changes_fh:
-    changes_fh.write(changesdata.encode('utf-8'))
+    changes_fh.write(six.ensure_binary(changesdata, 'utf-8'))
 
 
 def GetFlagValue(flagvalue, strip=True):
   if flagvalue:
-    flagvalue = flagvalue.decode('utf-8')
+    flagvalue = six.ensure_text(flagvalue, 'utf-8')
     if flagvalue[0] == '@':
       with open(flagvalue[1:], 'r') as f:
-        flagvalue = f.read().decode('utf-8')
+        flagvalue = six.ensure_text(f.read(), 'utf-8')
     if strip:
       return flagvalue.strip()
   return flagvalue
@@ -362,6 +369,6 @@
       urgency=FLAGS.urgency)
 
 if __name__ == '__main__':
-  MakeGflags()
-  FLAGS = gflags.FLAGS
-  main(FLAGS(sys.argv))
+  Makeflags()
+  FLAGS = flags.FLAGS
+  app.run(main)
diff --git a/tools/build_defs/pkg/make_rpm.py b/tools/build_defs/pkg/make_rpm.py
index 1a2bfaf..e2b2018 100644
--- a/tools/build_defs/pkg/make_rpm.py
+++ b/tools/build_defs/pkg/make_rpm.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2017 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,26 +24,27 @@
 import re
 import shutil
 import subprocess
-import sys
 import tempfile
 
-# pylint: disable=g-direct-third-party-import
-from third_party.py import gflags
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+from absl import app
+from absl import flags
+import six
 
-gflags.DEFINE_string('rpmbuild', '', 'Path to rpmbuild executable')
-gflags.DEFINE_string('name', '', 'The name of the software being packaged.')
-gflags.DEFINE_string('version', '',
-                     'The version of the software being packaged.')
-gflags.DEFINE_string('release', '',
-                     'The release of the software being packaged.')
-gflags.DEFINE_string('arch', '',
-                     'The CPU architecture of the software being packaged.')
+flags.DEFINE_string('rpmbuild', '', 'Path to rpmbuild executable')
+flags.DEFINE_string('name', '', 'The name of the software being packaged.')
+flags.DEFINE_string('version', '',
+                    'The version of the software being packaged.')
+flags.DEFINE_string('release', '',
+                    'The release of the software being packaged.')
+flags.DEFINE_string('arch', '',
+                    'The CPU architecture of the software being packaged.')
 
-gflags.DEFINE_string('spec_file', '',
-                     'The file containing the RPM specification.')
-gflags.DEFINE_string('out_file', '',
-                     'The destination to save the resulting RPM file to.')
-gflags.DEFINE_boolean('debug', False, 'Print debug messages.')
+flags.DEFINE_string('spec_file', '',
+                    'The file containing the RPM specification.')
+flags.DEFINE_string('out_file', '',
+                    'The destination to save the resulting RPM file to.')
+flags.DEFINE_boolean('debug', False, 'Print debug messages.')
 
 
 # Setup to safely create a temporary directory and clean it up when done.
@@ -125,10 +127,10 @@
     for line in fileinput.input(input_file):
       if replacements:
         for prefix, text in replacements.items():
-          if line.startswith(prefix):
-            line = prefix + ' ' + text + '\n'
+          if six.ensure_str(line).startswith(prefix):
+            line = prefix + ' ' + six.ensure_str(text) + '\n'
             break
-      output.write(line)
+      output.write(six.ensure_str(line))
 
 
 def IsExe(fpath):
@@ -301,5 +303,5 @@
 
 
 if __name__ == '__main__':
-  FLAGS = gflags.FLAGS
-  main(FLAGS(sys.argv))
+  FLAGS = flags.FLAGS
+  app.run(main)
diff --git a/tools/jdk/BUILD b/tools/jdk/BUILD
index 6cac7d0..42ba16d 100644
--- a/tools/jdk/BUILD
+++ b/tools/jdk/BUILD
@@ -428,7 +428,7 @@
         "proguard_whitelister.py",
     ],
     deps = [
-        "//third_party/py/gflags",
+        "//third_party/py/abseil",
     ],
 )
 
diff --git a/tools/jdk/proguard_whitelister.py b/tools/jdk/proguard_whitelister.py
index afd8eba..77be841 100644
--- a/tools/jdk/proguard_whitelister.py
+++ b/tools/jdk/proguard_whitelister.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,15 +22,21 @@
 binary through a library dependency.
 """
 
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import re
-import sys
 
-from third_party.py import gflags
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+from absl import app
+from absl import flags
+import six
 
-gflags.DEFINE_string('path', None, 'Path to the proguard config to validate')
-gflags.DEFINE_string('output', None, 'Where to put the validated config')
+flags.DEFINE_string('path', None, 'Path to the proguard config to validate')
+flags.DEFINE_string('output', None, 'Where to put the validated config')
 
-FLAGS = gflags.FLAGS
+FLAGS = flags.FLAGS
 PROGUARD_COMMENTS_PATTERN = '#.*(\n|$)'
 
 
@@ -60,7 +67,7 @@
 
   def _Validate(self, config):
     """Checks the config for illegal arguments."""
-    config = re.sub(PROGUARD_COMMENTS_PATTERN, '', config)
+    config = re.sub(PROGUARD_COMMENTS_PATTERN, '', six.ensure_str(config))
     args = re.compile('(?:^-|\n-)').split(config)
 
     invalid_configs = []
@@ -81,11 +88,10 @@
     return False
 
 
-def main():
+def main(unused_argv):
   validator = ProguardConfigValidator(FLAGS.path, FLAGS.output)
   validator.ValidateAndWriteOutput()
 
 
 if __name__ == '__main__':
-  FLAGS(sys.argv)
-  main()
+  app.run(main)
diff --git a/tools/jdk/proguard_whitelister_test.py b/tools/jdk/proguard_whitelister_test.py
index 07d97ea..4556bdd 100644
--- a/tools/jdk/proguard_whitelister_test.py
+++ b/tools/jdk/proguard_whitelister_test.py
@@ -1,3 +1,4 @@
+# Lint as: python2, python3
 # Copyright 2015 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,9 +13,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
 import os
 import unittest
 
+# Do not edit this line. Copybara replaces it with PY2 migration helper.
+import six
+
 from tools.jdk import proguard_whitelister
 
 
@@ -37,7 +45,7 @@
     tmpdir = os.environ["TEST_TMPDIR"]
     input_path = os.path.join(tmpdir, "proguard_whitelister_test_input.pgcfg")
     with open(input_path, "w") as f:
-      f.write(config)
+      f.write(six.ensure_str(config))
     output_path = os.path.join(tmpdir, "proguard_whitelister_test_output.pgcfg")
     validator = self._CreateValidator(input_path, output_path)
     try:
@@ -45,7 +53,7 @@
       self.fail()
     except RuntimeError as e:
       for invalid_arg in invalid_args:
-        self.assertTrue(invalid_arg in str(e))
+        self.assertTrue(six.ensure_str(invalid_arg) in str(e))
 
   def testInvalidNoteConfig(self):
     self._TestInvalidConfig(["-dontnote"], """\
diff --git a/tools/python/BUILD b/tools/python/BUILD
index 3d168df..82db259 100644
--- a/tools/python/BUILD
+++ b/tools/python/BUILD
@@ -12,7 +12,10 @@
 
 filegroup(
     name = "embedded_tools",
-    srcs = glob(["**"]) + [
+    srcs = glob(
+        ["**"],
+        exclude = ["private/version.bzl"],
+    ) + [
         "//tools/python/runfiles:embedded_tools",
     ],
     visibility = ["//tools:__pkg__"],
@@ -43,6 +46,5 @@
         ":py2wrapper.sh",
         ":py2wrapper_nonstrict.sh",
     ],
-    python_version = "PY2",
     deps = ["//src/test/py/bazel:test_base"],
 )
diff --git a/tools/python/BUILD.tools b/tools/python/BUILD.tools
index 90f9ff7..0fb695b 100644
--- a/tools/python/BUILD.tools
+++ b/tools/python/BUILD.tools
@@ -53,6 +53,8 @@
     "toolchain.bzl",
     "utils.bzl",
     "private/defs.bzl",
+    "private/py_test_alias.bzl",
+    "private/version.bzl",
     # write_file.bzl fortunately doesn't need to be exposed because it's only
     # used in this BUILD file.
 ])
@@ -61,6 +63,8 @@
     name = "bzl_srcs",
     srcs = glob(["*.bzl"]) + [
         "private/defs.bzl",
+        "private/py_test_alias.bzl",
+        "private/version.bzl",
     ],
     visibility = ["//tools:__pkg__"],
 )
diff --git a/tools/python/private/py_test_alias.bzl b/tools/python/private/py_test_alias.bzl
new file mode 100644
index 0000000..bdfd665
--- /dev/null
+++ b/tools/python/private/py_test_alias.bzl
@@ -0,0 +1,26 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Only code in @bazel_tools is allowed to load() this.
+
+Defines an alias for py_test(). The Google-internal version of this rule is a
+macro that generates a py_test for PY2 and PY3, to help migrating scripts.
+Bazel's Python scripts don't need that macro, so we alias it to py_test.
+"""
+
+load(":private/defs.bzl", "py_test")
+
+# TODO(bazel-team): delete this alias, replace with py_test everywhere as part
+# of fixing https://github.com/bazelbuild/bazel/issues/10127
+py_test_alias = py_test
diff --git a/tools/python/private/version.bzl b/tools/python/private/version.bzl
new file mode 100644
index 0000000..cb9734c
--- /dev/null
+++ b/tools/python/private/version.bzl
@@ -0,0 +1,28 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This is a Bazel-internal file; do not load() it!
+
+'python_version' value for py_binary and py_test rules in the Bazel source tree.
+See also "version.bzl.tools", which defines the value for targets in the
+@bazel_tools repository.
+
+We use PY3 in the source tree because PY2 reaches end of life in December 2019.
+Building Bazel requires PY3.
+
+We use PY2 in @bazel_tools to retain PY2 compatibility for users who run Bazel
+with Python 2.
+"""
+
+PY_BINARY_VERSION = "PY3"
diff --git a/tools/python/private/version.bzl.tools b/tools/python/private/version.bzl.tools
new file mode 100644
index 0000000..5a8b2c8
--- /dev/null
+++ b/tools/python/private/version.bzl.tools
@@ -0,0 +1,30 @@
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This is a Bazel-internal file; do not load() it!
+
+'python_version' value for py_binary and py_test rules in the @bazel_tools
+repository. See also "version.bzl", which defines the value for targets in the
+Bazel source tree.
+
+We use PY3 in the source tree because PY2 reaches end of life in December 2019.
+Building Bazel requires PY3.
+
+We use PY2 in @bazel_tools to retain PY2 compatibility for users who run Bazel
+with Python 2.
+"""
+
+# TODO(bazel-team): delete this variable and use PY3 everywhere as part of
+# fixing https://github.com/bazelbuild/bazel/issues/10127.
+PY_BINARY_VERSION = "PY2"