Repository: bazelbuild/bazel
Issue: Make android tools scripts Python3-compatible for building android apps
Labels: type: feature request

### Description of the problem / feature request / question:

Google's TFLite team has discovered that the TF Lite Android demo application cannot build via Bazel with Python 3, because the android tools Bazel uses aren't Python-3 compatible. The first breaking point looks like `third_party/gflags`, but past that there seem to be additional version incompatibilities.

### If possible, provide a minimal example to reproduce the problem:

Here's a very fast example:
```shell
$ git clone http://github.com/bazelbuild/bazel /tmp/bazel
$ cd /tmp/bazel
$ python3
>>> import tools.android.aar_embedded_jars_extractor
```
I'm not actually sure how to do the same as the above in python 2, but that sequence of commands results in the same errors we see when trying to build the demo app (`No module named 'gflags'`). I can sort of fix this with `$ pip install python-gflags`, which makes Python not even try to use the local files (this seems like an ultimately buggy workaround). The import is fixed, but the build (below) is still busted with a str-to-buffer message problem which I've included at the very bottom of the issue.

Here's how I ran the TF Lite app build. The build requires the [SDK setup steps for Tensorflow's WORKSPACE](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo).

```shell
$ virtualenv --python=python3 --system-site-packages /tmp/py3
$ git clone http://github.com/tensorflow/tensorflow /tmp/tf
$ cd /tmp/tf
$ source /tmp/py3/bin/activate
# prepare WORKSPACE, see link above
$ ./configure      # should default to /tmp/py3 python path
$ bazel build //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo --cxxopt='--std=c++11' --config=android_arm 
```

### Environment info

* Operating System: Ubuntu 14.04 LTS
* Bazel version (output of `bazel info release`): release 0.7.0

###  Have you found anything relevant by searching the web?

- #1580 seems generally relevant.
- [A relevant-looking issue from rules_docker](https://github.com/bazelbuild/rules_docker/issues/169)
- [Building the tflite demo app from source](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite/java/demo)
- [Where installing python-gflags was noted](https://github.com/mrmeku/brackets-closure-linter/issues/3)
- [An SO question on the str buffer error](https://stackoverflow.com/questions/5471158/typeerror-str-does-not-support-the-buffer-interface)

### Anything else, information or logs or outputs that would be helpful?

This incompatibility means that the TF Lite app can't be built (afaik) unless users install Python 2. Not a tremendous problem, but pretty inconvenient.

From the build commands above, I get something like this:

```
(py3)❯ bazel build //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo --cxxopt='--std=c++11' --config=android_arm --logging=0
WARNING: The major revision of the Android NDK referenced by android_ndk_repository rule 'androidndk' is 15. The major revisions supported by Bazel are [10, 11, 12, 13, 14]. Defaulting to revision 14.
INFO: Analysed target //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo (0 packages loaded).
INFO: Found 1 target...
ERROR: /usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/external/androidsdk/com.android.support/BUILD:4563:1: Filtering AAR native libs by architecture failed (Exit 1)
Traceback (most recent call last):
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_native_libs_zip_creator.runfiles/org_tensorflow/../bazel_tools/tools/android/aar_native_libs_zip_creator.py", line 26, in <module>
    from third_party.py import gflags
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_native_libs_zip_creator.runfiles/bazel_tools/third_party/py/gflags/__init__.py", line 1, in <module>
    from gflags import *
ImportError: No module named 'gflags'
Target //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 0.440s, Critical Path: 0.11s
FAILED: Build did NOT complete successfully
```
And here's the str-to-buffer problem after installing `pip install python-gflags`:

```
(py3)❯ bazel build //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo --cxxopt='--std=c++11' --config=android_arm --logging=0
WARNING: The major revision of the Android NDK referenced by android_ndk_repository rule 'androidndk' is 15. The major revisions supported by Bazel are [10, 11, 12, 13, 14]. Defaulting to revision 14.
INFO: Analysed target //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo (50 packages loaded).
INFO: Found 1 target...
ERROR: /usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/external/androidsdk/com.android.support/BUILD:4563:1: Extracting classes.jar and libs/*.jar from support-fragment-25.2.0.aar failed (Exit 1)
Traceback (most recent call last):
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_embedded_jars_extractor.runfiles/org_tensorflow/../bazel_tools/tools/android/aar_embedded_jars_extractor.py", line 92, in <module>
    main()
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_embedded_jars_extractor.runfiles/org_tensorflow/../bazel_tools/tools/android/aar_embedded_jars_extractor.py", line 87, in main
    _Main(FLAGS.input_aar, FLAGS.output_singlejar_param_file, FLAGS.output_dir)
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_embedded_jars_extractor.runfiles/org_tensorflow/../bazel_tools/tools/android/aar_embedded_jars_extractor.py", line 67, in _Main
    output_dir_orig)
  File "/usr/local/google/home/angerson/.cache/bazel/_bazel_angerson/7b7f16644e8d0eb6d37d6401f95d6305/execroot/org_tensorflow/bazel-out/host/bin/external/bazel_tools/tools/android/aar_embedded_jars_extractor.runfiles/org_tensorflow/../bazel_tools/tools/android/aar_embedded_jars_extractor.py", line 48, in ExtractEmbeddedJars
    singlejar_param_file.write("--exclude_build_data\n")
TypeError: 'str' does not support the buffer interface
Target //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo failed to build
Use --verbose_failures to see the command lines of failed build steps.
INFO: Elapsed time: 10.214s, Critical Path: 0.54s
FAILED: Build did NOT complete successfully
```

Issue comments:
I think that #3855 should resolve this issue, but I'll test it to confirm.
Hey thanks for bringing this to our attention, ccing some people that can help
@jin @dkelmer @ahumesky 
Thanks @aj-michael - I was able to reproduce @angersson's errors and can confirm that #3855 resolves the first one.

However, the second error persists. It can probably be resolved by updating modules in `tools/android` and `tools/build_defs` for compatibility with python3-style strings. Should be relatively simple, so I can probably make those changes soon.
As promised, I've created a branch containing [python 3 compatibility updates](https://github.com/bazelbuild/bazel/compare/master...akira-baruah:python3-tools) to bazel tools.

@angersson @aj-michael @jin @dkelmer @ahumesky you can test the fix by running:
```
git clone https://github.com/akira-baruah/bazel.git /tmp/bazel-akira
cd /tmp/bazel-akira
git checkout python3-tools  # branch with bugfixes
bazel build //src:bazel

cd <tensorflow repo>
/tmp/bazel-akira/bazel-bin/src/bazel build //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo --cxxopt='--std=c++11' --force_python=py3 --python_path=$(which python3) --config=android_arm --logging=0
```
If it looks good to you, I'll make a PR. Please note that this branch depends on PR #3855, so we would probably want to have that merged first.

Thanks!
I was able to replicate the fixes, but I'll need to fetch NDK 15 to fully test building the app in a clean environment due to #4068.
I verified build success with nvidia-docker, `gcr.io/tensorflow/tensorflow:latest-devel-gpu`, and this... awful script, run inside the container:

```shell
#!/usr/bin/env bash

set -euxo pipefail

TMPDIR=$(mktemp -d)
trap '' PIPE

git clone https://github.com/akira-baruah/bazel.git ${TMPDIR}/bazel-akira
cd ${TMPDIR}/bazel-akira
git checkout python3-tools  # branch with bugfixes
bazel build //src:bazel

cd ${TMPDIR}

curl -o ${TMPDIR}/sdk-tools.zip https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
unzip ${TMPDIR}/sdk-tools.zip -d ${TMPDIR}/sdk
echo "y" | ${TMPDIR}/sdk/tools/bin/sdkmanager "build-tools;26.0.1" "platforms;android-26" "extras;android;m2repository"

pip install virtualenv
virtualenv --python=python3 --system-site-packages ${TMPDIR}/py3
PS1=""  # Avoids `set -u`-related error with bin/activate
source ${TMPDIR}/py3/bin/activate

git clone http://github.com/tensorflow/tensorflow ${TMPDIR}/tf
cd ${TMPDIR}/tf

curl -o ${TMPDIR}/ndk.zip https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip
unzip ${TMPDIR}/ndk.zip -d ${TMPDIR}/ndk/

cat >> ${TMPDIR}/tf/WORKSPACE <<HEREDOC
android_sdk_repository(
    name = "androidsdk",
    api_level = 26,
    build_tools_version = "26.0.1",
    path = "${TMPDIR}/sdk",
)
android_ndk_repository(
    name="androidndk",
    path="${TMPDIR}/ndk/android-ndk-r14b",
    api_level=14)
HEREDOC

set +o pipefail
yes '' | ${TMPDIR}/tf/configure
set -o pipefail
cd ${TMPDIR}/tf
${TMPDIR}/bazel-akira/bazel-bin/src/bazel build //tensorflow/contrib/lite/java/demo/app/src/main:TfLiteCameraDemo --cxxopt='--std=c++11' --config=android_arm
```

Sweet.
@angersson now that gflags supports python 3 imports (see commits fb15f0f4e4885243d354a14997d09463594385bf, d926bc40260549b997a6a5a1e82d9e7999dbb65e, and 4d0e6265911199b1376d0f52e249625180a0500d), I've created PR #4265 to resolve this issue.

Pull request review comments:

Patch that resolved the issue:
diff --git a/tools/android/aar_embedded_jars_extractor.py b/tools/android/aar_embedded_jars_extractor.py
index 393d3f4c5c3732..92a2e5a950eefb 100644
--- a/tools/android/aar_embedded_jars_extractor.py
+++ b/tools/android/aar_embedded_jars_extractor.py
@@ -45,13 +45,14 @@ def ExtractEmbeddedJars(aar,
   if not output_dir_orig:
     output_dir_orig = output_dir
   jar_pattern = re.compile("^(classes|libs/.+)\\.jar$")
-  singlejar_param_file.write("--exclude_build_data\n")
+  singlejar_param_file.write(b"--exclude_build_data\n")
   for name in aar.namelist():
     if jar_pattern.match(name):
-      singlejar_param_file.write("--sources\n")
+      singlejar_param_file.write(b"--sources\n")
       # output_dir may be a temporary junction, so write the original
       # (unshortened) path to the params file
-      singlejar_param_file.write(output_dir_orig + "/" + name + "\n")
+      singlejar_param_file.write(
+          (output_dir_orig + "/" + name + "\n").encode("utf-8"))
       aar.extract(name, output_dir)
 
 
diff --git a/tools/android/aar_embedded_jars_extractor_test.py b/tools/android/aar_embedded_jars_extractor_test.py
index 30f150e8505af9..e4cc6d8d6f7af8 100644
--- a/tools/android/aar_embedded_jars_extractor_test.py
+++ b/tools/android/aar_embedded_jars_extractor_test.py
@@ -14,9 +14,9 @@
 
 """Tests for aar_embedded_jars_extractor."""
 
+import io
 import os
 import shutil
-import StringIO
 import unittest
 import zipfile
 
@@ -26,6 +26,11 @@
 class AarEmbeddedJarsExtractor(unittest.TestCase):
   """Unit tests for aar_embedded_jars_extractor.py."""
 
+  # Python 2 alias
+  if not hasattr(unittest.TestCase, 'assertCountEqual'):
+      def assertCountEqual(self, *args):
+          return self.assertItemsEqual(*args)
+
   def setUp(self):
     os.chdir(os.environ["TEST_TMPDIR"])
 
@@ -33,47 +38,47 @@ def tearDown(self):
     shutil.rmtree("out_dir")
 
   def testNoJars(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
-    param_file = StringIO.StringIO()
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
+    param_file = io.BytesIO()
     os.makedirs("out_dir")
     aar_embedded_jars_extractor.ExtractEmbeddedJars(aar, param_file, "out_dir")
     self.assertEqual([], os.listdir("out_dir"))
     param_file.seek(0)
-    self.assertEqual("--exclude_build_data\n", param_file.read())
+    self.assertEqual(b"--exclude_build_data\n", param_file.read())
 
   def testClassesJarAndLibsJars(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     aar.writestr("classes.jar", "")
     aar.writestr("libs/a.jar", "")
     aar.writestr("libs/b.jar", "")
-    param_file = StringIO.StringIO()
+    param_file = io.BytesIO()
     os.makedirs("out_dir")
     aar_embedded_jars_extractor.ExtractEmbeddedJars(aar, param_file, "out_dir")
-    self.assertItemsEqual(["classes.jar", "libs"], os.listdir("out_dir"))
-    self.assertItemsEqual(["a.jar", "b.jar"], os.listdir("out_dir/libs"))
+    self.assertCountEqual(["classes.jar", "libs"], os.listdir("out_dir"))
+    self.assertCountEqual(["a.jar", "b.jar"], os.listdir("out_dir/libs"))
     param_file.seek(0)
     self.assertEqual(
-        ["--exclude_build_data\n",
-         "--sources\n",
-         "out_dir/classes.jar\n",
-         "--sources\n",
-         "out_dir/libs/a.jar\n",
-         "--sources\n",
-         "out_dir/libs/b.jar\n"],
+        [b"--exclude_build_data\n",
+         b"--sources\n",
+         b"out_dir/classes.jar\n",
+         b"--sources\n",
+         b"out_dir/libs/a.jar\n",
+         b"--sources\n",
+         b"out_dir/libs/b.jar\n"],
         param_file.readlines())
 
   def testOnlyClassesJar(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     aar.writestr("classes.jar", "")
-    param_file = StringIO.StringIO()
+    param_file = io.BytesIO()
     os.makedirs("out_dir")
     aar_embedded_jars_extractor.ExtractEmbeddedJars(aar, param_file, "out_dir")
     self.assertEqual(["classes.jar"], os.listdir("out_dir"))
     param_file.seek(0)
     self.assertEqual(
-        ["--exclude_build_data\n",
-         "--sources\n",
-         "out_dir/classes.jar\n"],
+        [b"--exclude_build_data\n",
+         b"--sources\n",
+         b"out_dir/classes.jar\n"],
         param_file.readlines())
 
 
diff --git a/tools/android/aar_native_libs_zip_creator_test.py b/tools/android/aar_native_libs_zip_creator_test.py
index 0e0210e448396d..3ec71a617a7e00 100644
--- a/tools/android/aar_native_libs_zip_creator_test.py
+++ b/tools/android/aar_native_libs_zip_creator_test.py
@@ -14,7 +14,7 @@
 
 """Tests for aar_native_libs_zip_creator."""
 
-import StringIO
+import io
 import unittest
 import zipfile
 
@@ -25,25 +25,25 @@ class AarNativeLibsZipCreatorTest(unittest.TestCase):
   """Unit tests for aar_native_libs_zip_creator.py."""
 
   def testAarWithNoLibs(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
-    outzip = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
+    outzip = zipfile.ZipFile(io.BytesIO(), "w")
     aar_native_libs_zip_creator.CreateNativeLibsZip(aar, "x86", outzip)
     self.assertEquals([], outzip.namelist())
 
   def testAarWithMissingLibs(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     aar.writestr("jni/armeabi/foo.so", "")
-    outzip = zipfile.ZipFile(StringIO.StringIO(), "w")
+    outzip = zipfile.ZipFile(io.BytesIO(), "w")
     self.assertRaises(
         aar_native_libs_zip_creator.UnsupportedArchitectureException,
         aar_native_libs_zip_creator.CreateNativeLibsZip,
         aar, "x86", outzip)
 
   def testAarWithAllLibs(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     aar.writestr("jni/x86/foo.so", "")
     aar.writestr("jni/armeabi/foo.so", "")
-    outzip = zipfile.ZipFile(StringIO.StringIO(), "w")
+    outzip = zipfile.ZipFile(io.BytesIO(), "w")
     aar_native_libs_zip_creator.CreateNativeLibsZip(aar, "x86", outzip)
     self.assertIn("lib/x86/foo.so", outzip.namelist())
     self.assertNotIn("lib/armeabi/foo.so", outzip.namelist())
diff --git a/tools/android/aar_resources_extractor.py b/tools/android/aar_resources_extractor.py
index 61f9f4d3972035..5d875fbe12335e 100644
--- a/tools/android/aar_resources_extractor.py
+++ b/tools/android/aar_resources_extractor.py
@@ -78,11 +78,11 @@ def ExtractResources(aar, output_res_dir):
       with junction.TempJunction(os.path.dirname(empty_xml_filename)) as junc:
         xmlpath = os.path.join(junc, os.path.basename(empty_xml_filename))
         with open(xmlpath, "wb") as empty_xml:
-          empty_xml.write("<resources/>")
+          empty_xml.write(b"<resources/>")
     else:
       os.makedirs(os.path.dirname(empty_xml_filename))
       with open(empty_xml_filename, "wb") as empty_xml:
-        empty_xml.write("<resources/>")
+        empty_xml.write(b"<resources/>")
 
 
 def main():
diff --git a/tools/android/aar_resources_extractor_test.py b/tools/android/aar_resources_extractor_test.py
index d5f630b3526ce7..efd08c962e803b 100644
--- a/tools/android/aar_resources_extractor_test.py
+++ b/tools/android/aar_resources_extractor_test.py
@@ -14,9 +14,9 @@
 
 """Tests for aar_resources_extractor."""
 
+import io
 import os
 import shutil
-import StringIO
 import unittest
 import zipfile
 
@@ -30,6 +30,11 @@ def _HostPath(path):
 class AarResourcesExtractorTest(unittest.TestCase):
   """Unit tests for aar_resources_extractor.py."""
 
+  # Python 2 alias
+  if not hasattr(unittest.TestCase, 'assertCountEqual'):
+      def assertCountEqual(self, *args):
+          return self.assertItemsEqual(*args)
+
   def setUp(self):
     os.chdir(os.environ["TEST_TMPDIR"])
 
@@ -43,7 +48,7 @@ def DirContents(self, d):
     ]
 
   def testNoResources(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     os.makedirs("out_dir")
     aar_resources_extractor.ExtractResources(aar, "out_dir")
     self.assertEqual([_HostPath("out_dir/res/values/empty.xml")],
@@ -52,7 +57,7 @@ def testNoResources(self):
       self.assertEqual("<resources/>", empty_xml.read())
 
   def testContainsResources(self):
-    aar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    aar = zipfile.ZipFile(io.BytesIO(), "w")
     aar.writestr("res/values/values.xml", "some values")
     aar.writestr("res/layouts/layout.xml", "some layout")
     os.makedirs("out_dir")
@@ -61,7 +66,7 @@ def testContainsResources(self):
         _HostPath("out_dir/res/values/values.xml"),
         _HostPath("out_dir/res/layouts/layout.xml")
     ]
-    self.assertItemsEqual(expected_resources, self.DirContents("out_dir"))
+    self.assertCountEqual(expected_resources, self.DirContents("out_dir"))
     with open("out_dir/res/values/values.xml", "r") as values_xml:
       self.assertEqual("some values", values_xml.read())
     with open("out_dir/res/layouts/layout.xml", "r") as layout_xml:
diff --git a/tools/android/build_incremental_dexmanifest.py b/tools/android/build_incremental_dexmanifest.py
index 6c372656ec2d51..e912d62a939d59 100644
--- a/tools/android/build_incremental_dexmanifest.py
+++ b/tools/android/build_incremental_dexmanifest.py
@@ -121,7 +121,7 @@ def Run(self, argv):
         self.AddDex(input_filename, None, input_filename)
 
     with open(argv[0], "wb") as manifest:
-      manifest.write("\n".join(self.manifest_lines))
+      manifest.write(("\n".join(self.manifest_lines)).encode("utf-8"))
 
 
 def main(argv):
diff --git a/tools/android/incremental_install.py b/tools/android/incremental_install.py
index bad2e29c88a6c3..254db274fc76ab 100644
--- a/tools/android/incremental_install.py
+++ b/tools/android/incremental_install.py
@@ -221,7 +221,7 @@ def PushString(self, contents, remote):
     """Push a given string to a given path on the device in parallel."""
     local = self._CreateLocalFile()
     with open(local, "wb") as f:
-      f.write(contents)
+      f.write(contents.encode("utf-8"))
     return self.Push(local, remote)
 
   def Pull(self, remote):
@@ -237,7 +237,7 @@ def Pull(self, remote):
     try:
       self._Exec(["pull", remote, local])
       with open(local, "rb") as f:
-        return f.read()
+        return f.read().decode("utf-8")
     except (AdbError, IOError):
       return None
 
@@ -339,7 +339,7 @@ def ParseManifest(contents):
 def GetAppPackage(stub_datafile):
   """Returns the app package specified in a stub data file."""
   with open(stub_datafile, "rb") as f:
-    return f.readlines()[1].strip()
+    return f.readlines()[1].decode("utf-8").strip()
 
 
 def UploadDexes(adb, execroot, app_dir, temp_dir, dexmanifest, full_install):
@@ -605,7 +605,7 @@ def UploadNativeLibs(adb, native_lib_args, app_dir, full_install):
     f.result()
 
   install_manifest = [
-      name + " " + checksum for name, checksum in install_checksums.iteritems()]
+      name + " " + checksum for name, checksum in install_checksums.items()]
   adb.PushString("\n".join(install_manifest),
                  targetpath.join(app_dir, "native",
                                  "native_manifest")).result()
@@ -693,7 +693,7 @@ def SplitIncrementalInstall(adb, app_package, execroot, split_main_apk,
     adb.InstallMultiple(targetpath.join(execroot, apk), app_package)
 
   install_manifest = [
-      name + " " + checksum for name, checksum in install_checksums.iteritems()]
+      name + " " + checksum for name, checksum in install_checksums.items()]
   adb.PushString("\n".join(install_manifest),
                  targetpath.join(app_dir, "split_manifest")).result()
 
@@ -744,7 +744,7 @@ def IncrementalInstall(adb_path,
         VerifyInstallTimestamp(adb, app_package)
 
       with open(hostpath.join(execroot, dexmanifest), "rb") as f:
-        dexmanifest = f.read()
+        dexmanifest = f.read().decode("utf-8")
       UploadDexes(adb, execroot, app_dir, temp_dir, dexmanifest, bool(apk))
       # TODO(ahumesky): UploadDexes waits for all the dexes to be uploaded, and
       # then UploadResources is called. We could instead enqueue everything
@@ -776,16 +776,16 @@ def IncrementalInstall(adb_path,
     sys.exit("Error: Device unauthorized. Please check the confirmation "
              "dialog on your device.")
   except MultipleDevicesError as e:
-    sys.exit("Error: " + e.message + "\nTry specifying a device serial with "
+    sys.exit("Error: " + str(e) + "\nTry specifying a device serial with "
              "\"blaze mobile-install --adb_arg=-s --adb_arg=$ANDROID_SERIAL\"")
   except OldSdkException as e:
     sys.exit("Error: The device does not support the API level specified in "
              "the application's manifest. Check minSdkVersion in "
              "AndroidManifest.xml")
   except TimestampException as e:
-    sys.exit("Error:\n%s" % e.message)
+    sys.exit("Error:\n%s" % str(e))
   except AdbError as e:
-    sys.exit("Error:\n%s" % e.message)
+    sys.exit("Error:\n%s" % str(e))
   finally:
     shutil.rmtree(temp_dir, True)
 
diff --git a/tools/android/incremental_install_test.py b/tools/android/incremental_install_test.py
index 1181a591ef0496..5eee83fcad2ae2 100644
--- a/tools/android/incremental_install_test.py
+++ b/tools/android/incremental_install_test.py
@@ -49,7 +49,7 @@ def Exec(self, args):
     if cmd == "push":
       # "/test/adb push local remote"
       with open(args[2], "rb") as f:
-        content = f.read()
+        content = f.read().decode("utf-8")
       self.files[args[3]] = content
     elif cmd == "pull":
       # "/test/adb pull remote local"
@@ -58,7 +58,7 @@ def Exec(self, args):
       content = self.files.get(remote)
       if content is not None:
         with open(local, "wb") as f:
-          f.write(content)
+          f.write(content.encode("utf-8"))
       else:
         returncode = 1
         stderr = "remote object '%s' does not exist\n" % remote
@@ -69,7 +69,7 @@ def Exec(self, args):
     elif cmd == "install-multiple":
       if args[3] == "-p":
         with open(args[5], "rb") as f:
-          content = f.read()
+          content = f.read().decode("utf-8")
         self.split_apks.add(content)
       else:
         self.package_timestamp = self._last_package_timestamp
@@ -133,11 +133,12 @@ def setUp(self):
 
     # Write the stub datafile which contains the package name of the app.
     with open(self._STUB_DATAFILE, "wb") as f:
-      f.write("\n".join([self._OLD_APP_PACKGE, self._APP_PACKAGE]))
+      f.write(("\n".join([self._OLD_APP_PACKGE, self._APP_PACKAGE]))
+              .encode("utf-8"))
 
     # Write the local resource apk file.
     with open(self._RESOURCE_APK, "wb") as f:
-      f.write("resource apk")
+      f.write(b"resource apk")
 
     # Mock out subprocess.Popen to use our mock adb.
     self._popen_patch = mock.patch.object(incremental_install, "subprocess")
@@ -157,7 +158,7 @@ def _CreateZip(self, name="zip1", *files):
   def _CreateLocalManifest(self, *lines):
     content = "\n".join(lines)
     with open(self._DEXMANIFEST, "wb") as f:
-      f.write(content)
+      f.write(content.encode("utf-8"))
     return content
 
   def _CreateRemoteManifest(self, *lines):
@@ -205,7 +206,7 @@ def testUploadToPristineDevice(self):
     self._CreateZip()
 
     with open("dex1", "wb") as f:
-      f.write("content3")
+      f.write(b"content3")
 
     manifest = self._CreateLocalManifest(
         "zip1 zp1 ip1 0",
@@ -224,10 +225,10 @@ def testUploadToPristineDevice(self):
 
   def testSplitInstallToPristineDevice(self):
     with open("split1", "wb") as f:
-      f.write("split_content1")
+      f.write(b"split_content1")
 
     with open("main", "wb") as f:
-      f.write("main_Content")
+      f.write(b"main_Content")
 
     self._CallIncrementalInstall(
         incremental=False, split_main_apk="main", split_apks=["split1"])
@@ -235,10 +236,10 @@ def testSplitInstallToPristineDevice(self):
 
   def testSplitInstallUnchanged(self):
     with open("split1", "wb") as f:
-      f.write("split_content1")
+      f.write(b"split_content1")
 
     with open("main", "wb") as f:
-      f.write("main_Content")
+      f.write(b"main_Content")
 
     self._CallIncrementalInstall(
         incremental=False, split_main_apk="main", split_apks=["split1"])
@@ -250,17 +251,17 @@ def testSplitInstallUnchanged(self):
 
   def testSplitInstallChanges(self):
     with open("split1", "wb") as f:
-      f.write("split_content1")
+      f.write(b"split_content1")
 
     with open("main", "wb") as f:
-      f.write("main_Content")
+      f.write(b"main_Content")
 
     self._CallIncrementalInstall(
         incremental=False, split_main_apk="main", split_apks=["split1"])
     self.assertEqual(set(["split_content1"]), self._mock_adb.split_apks)
 
     with open("split1", "wb") as f:
-      f.write("split_content2")
+      f.write(b"split_content2")
     self._mock_adb.split_apks = set()
     self._CallIncrementalInstall(
         incremental=False, split_main_apk="main", split_apks=["split1"])
@@ -269,7 +270,7 @@ def testSplitInstallChanges(self):
   def testMissingNativeManifestWithIncrementalInstall(self):
     self._CreateZip()
     with open("liba.so", "wb") as f:
-      f.write("liba_1")
+      f.write(b"liba_1")
 
     # Upload a library to the device.
     native_libs = ["armeabi-v7a:liba.so"]
@@ -285,7 +286,7 @@ def testMissingNativeManifestWithIncrementalInstall(self):
   def testNonIncrementalInstallOverwritesNativeLibs(self):
     self._CreateZip()
     with open("liba.so", "wb") as f:
-      f.write("liba_1")
+      f.write(b"liba_1")
 
     # Upload a library to the device.
     native_libs = ["armeabi-v7a:liba.so"]
@@ -305,7 +306,7 @@ def testNonIncrementalInstallOverwritesNativeLibs(self):
   def testNativeAbiCompatibility(self):
     self._CreateZip()
     with open("liba.so", "wb") as f:
-      f.write("liba")
+      f.write(b"liba")
 
     native_libs = ["armeabi:liba.so"]
     self._mock_adb.SetAbi("arm64-v8a")
@@ -315,9 +316,9 @@ def testNativeAbiCompatibility(self):
   def testUploadNativeLibs(self):
     self._CreateZip()
     with open("liba.so", "wb") as f:
-      f.write("liba_1")
+      f.write(b"liba_1")
     with open("libb.so", "wb") as f:
-      f.write("libb_1")
+      f.write(b"libb_1")
 
     native_libs = ["armeabi-v7a:liba.so", "armeabi-v7a:libb.so"]
     self._CallIncrementalInstall(incremental=False, native_libs=native_libs)
@@ -326,7 +327,7 @@ def testUploadNativeLibs(self):
 
     # Change a library
     with open("libb.so", "wb") as f:
-      f.write("libb_2")
+      f.write(b"libb_2")
     self._CallIncrementalInstall(incremental=True, native_libs=native_libs)
     self.assertEqual("libb_2", self._GetDeviceFile("native/libb.so"))
 
@@ -520,7 +521,7 @@ def testStartCold(self):
     self._CreateZip()
 
     with open("dex1", "wb") as f:
-      f.write("content3")
+      f.write(b"content3")
 
     self._CreateLocalManifest(
         "zip1 zp1 ip1 0",
@@ -536,7 +537,7 @@ def testDebugStart(self):
     self._CreateZip()
 
     with open("dex1", "wb") as f:
-      f.write("content3")
+      f.write(b"content3")
 
     self._CreateLocalManifest(
         "zip1 zp1 ip1 0",
diff --git a/tools/android/merge_manifests.py b/tools/android/merge_manifests.py
index a12e53836c349d..520b03219c049c 100644
--- a/tools/android/merge_manifests.py
+++ b/tools/android/merge_manifests.py
@@ -379,7 +379,7 @@ def Merge(self):
       self._MergeTopLevelNamespaces(mergee_dom)
 
       for destination, values in sorted(
-          self._NODES_TO_COPY_FROM_MERGEE.iteritems()):
+          self._NODES_TO_COPY_FROM_MERGEE.items()):
         for node_to_copy in values:
           for node in mergee_dom.getElementsByTagName(node_to_copy):
             if self._IsDuplicate(node_to_copy, node):
diff --git a/tools/android/resource_extractor_test.py b/tools/android/resource_extractor_test.py
index de5b6a8b879485..db912ca19dcc14 100644
--- a/tools/android/resource_extractor_test.py
+++ b/tools/android/resource_extractor_test.py
@@ -14,7 +14,7 @@
 
 """Tests for resource_extractor."""
 
-import StringIO
+import io
 import unittest
 import zipfile
 
@@ -24,8 +24,13 @@
 class ResourceExtractorTest(unittest.TestCase):
   """Unit tests for resource_extractor.py."""
 
+  # Python 2 alias
+  if not hasattr(unittest.TestCase, 'assertCountEqual'):
+      def assertCountEqual(self, *args):
+          return self.assertItemsEqual(*args)
+
   def testJarWithEverything(self):
-    input_jar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    input_jar = zipfile.ZipFile(io.BytesIO(), "w")
 
     for path in (
         # Should not be included
@@ -56,17 +61,17 @@ def testJarWithEverything(self):
         "not_CVS/include",
         "META-INF/services/foo"):
       input_jar.writestr(path, "")
-    output_zip = zipfile.ZipFile(StringIO.StringIO(), "w")
+    output_zip = zipfile.ZipFile(io.BytesIO(), "w")
     resource_extractor.ExtractResources(input_jar, output_zip)
-    self.assertItemsEqual(("c", "a/b", "bar/a", "a/not_package.html",
+    self.assertCountEqual(("c", "a/b", "bar/a", "a/not_package.html",
                            "not_CVS/include", "META-INF/services/foo"),
                           output_zip.namelist())
 
   def testTimestampsAreTheSame(self):
-    input_jar = zipfile.ZipFile(StringIO.StringIO(), "w")
+    input_jar = zipfile.ZipFile(io.BytesIO(), "w")
     entry_info = zipfile.ZipInfo("a", (1982, 1, 1, 0, 0, 0))
     input_jar.writestr(entry_info, "")
-    output_zip = zipfile.ZipFile(StringIO.StringIO(), "w")
+    output_zip = zipfile.ZipFile(io.BytesIO(), "w")
     resource_extractor.ExtractResources(input_jar, output_zip)
     self.assertEqual((1982, 1, 1, 0, 0, 0), output_zip.getinfo("a").date_time)
 


Test patch: